This commit is contained in:
Bob Nystrom
2013-12-20 07:05:31 -08:00
parent 61e745d548
commit e83d0bb360
11 changed files with 199 additions and 82 deletions

View File

@ -882,7 +882,7 @@ static int findUpvalue(Compiler* compiler)
// scope.
compiler->parent->locals[local].isUpvalue = true;
return addUpvalue(compiler, 1, local);
return addUpvalue(compiler, true, local);
}
// See if it's an upvalue in the immediately enclosing function. In other
@ -894,7 +894,7 @@ static int findUpvalue(Compiler* compiler)
int upvalue = findUpvalue(compiler->parent);
if (upvalue != -1)
{
return addUpvalue(compiler, 0, upvalue);
return addUpvalue(compiler, false, upvalue);
}
// If we got here, we walked all the way up the parent chain and couldn't
@ -1129,7 +1129,8 @@ static void methodCall(Compiler* compiler, Code instruction,
emit(compiler, symbol);
}
// Compiles the method name and argument list for a "<...>.name(...)" call.
// Compiles an expression that starts with ".name". That includes getters,
// method calls with arguments, and setter calls.
static void namedCall(Compiler* compiler, bool allowAssignment,
Code instruction)
{
@ -1138,9 +1139,25 @@ static void namedCall(Compiler* compiler, bool allowAssignment,
char name[MAX_METHOD_SIGNATURE];
int length = copyName(compiler, name);
// TODO: Check for "=" here and set assignment and return.
if (match(compiler, TOKEN_EQ))
{
if (!allowAssignment) error(compiler, "Invalid assignment.");
methodCall(compiler, instruction, name, length);
name[length++] = '=';
name[length++] = ' ';
// Compile the assigned value.
// TODO: Allow assignment here.
expression(compiler);
int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length);
emit(compiler, instruction + 1);
emit(compiler, symbol);
}
else
{
methodCall(compiler, instruction, name, length);
}
}
static void grouping(Compiler* compiler, bool allowAssignment)
@ -1176,7 +1193,7 @@ static void unaryOp(Compiler* compiler, bool allowAssignment)
GrammarRule* rule = &rules[compiler->parser->previous.type];
// Compile the argument.
parsePrecedence(compiler, 0, PREC_UNARY + 1);
parsePrecedence(compiler, false, PREC_UNARY + 1);
// Call the operator method on the left-hand side.
int symbol = ensureSymbol(&compiler->parser->vm->methods, rule->name, 1);
@ -1474,7 +1491,7 @@ void call(Compiler* compiler, bool allowAssignment)
void is(Compiler* compiler, bool allowAssignment)
{
// Compile the right-hand side.
parsePrecedence(compiler, 0, PREC_CALL);
parsePrecedence(compiler, false, PREC_CALL);
emit(compiler, CODE_IS);
}
@ -1485,7 +1502,7 @@ void and(Compiler* compiler, bool allowAssignment)
emit(compiler, CODE_AND);
int jump = emit(compiler, 255);
parsePrecedence(compiler, 0, PREC_LOGIC);
parsePrecedence(compiler, false, PREC_LOGIC);
patchJump(compiler, jump);
}
@ -1496,7 +1513,7 @@ void or(Compiler* compiler, bool allowAssignment)
emit(compiler, CODE_OR);
int jump = emit(compiler, 255);
parsePrecedence(compiler, 0, PREC_LOGIC);
parsePrecedence(compiler, false, PREC_LOGIC);
patchJump(compiler, jump);
}
@ -1506,7 +1523,7 @@ void infixOp(Compiler* compiler, bool allowAssignment)
GrammarRule* rule = &rules[compiler->parser->previous.type];
// Compile the right-hand side.
parsePrecedence(compiler, 0, rule->precedence + 1);
parsePrecedence(compiler, false, rule->precedence + 1);
// Call the operator method on the left-hand side.
int symbol = ensureSymbol(&compiler->parser->vm->methods,
@ -1546,6 +1563,26 @@ void mixedSignature(Compiler* compiler, char* name, int* length)
}
}
// Compiles a method signature for a named method or setter.
void namedSignature(Compiler* compiler, char* name, int* length)
{
if (match(compiler, TOKEN_EQ))
{
// It's a setter.
// TODO: Allow setters with parameters? Like: foo.bar(1, 2) = "blah"
name[(*length)++] = '=';
name[(*length)++] = ' ';
// Parse the value parameter.
declareVariable(compiler);
}
else
{
// Regular named method with an optional parameter list.
parameterList(compiler, name, length);
}
}
// Compiles a method signature for a constructor.
void constructorSignature(Compiler* compiler, char* name, int* length)
{
@ -1609,7 +1646,7 @@ GrammarRule rules[] =
/* TOKEN_VAR */ UNUSED,
/* TOKEN_WHILE */ UNUSED,
/* TOKEN_FIELD */ PREFIX(field),
/* TOKEN_NAME */ { name, NULL, parameterList, PREC_NONE, NULL },
/* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL },
/* TOKEN_NUMBER */ PREFIX(number),
/* TOKEN_STRING */ PREFIX(string),
/* TOKEN_LINE */ UNUSED,
@ -1644,7 +1681,7 @@ void parsePrecedence(Compiler* compiler, bool allowAssignment,
// on the stack.
void expression(Compiler* compiler)
{
parsePrecedence(compiler, 1, PREC_LOWEST);
parsePrecedence(compiler, true, PREC_LOWEST);
}
// Compiles a method definition inside a class body.
@ -1716,82 +1753,90 @@ void block(Compiler* compiler)
emit(compiler, CODE_POP);
}
// Compiles a class definition. Assumes the "class" token has already been
// consumed.
static void classStatement(Compiler* compiler)
{
// Create a variable to store the class in.
// TODO: Allow anonymous classes?
int symbol = declareVariable(compiler);
// Load the superclass (if there is one).
if (match(compiler, TOKEN_IS))
{
parsePrecedence(compiler, false, PREC_CALL);
emit(compiler, CODE_SUBCLASS);
}
else
{
// Create the empty class.
emit(compiler, CODE_CLASS);
}
// Store a placeholder for the number of fields argument. We don't know
// the value until we've compiled all the methods to see which fields are
// used.
int numFieldsInstruction = emit(compiler, 255);
// Set up a symbol table for the class's fields. We'll initially compile
// them to slots starting at zero. When the method is bound to the close
// the bytecode will be adjusted by [wrenBindMethod] to take inherited
// fields into account.
SymbolTable* previousFields = compiler->fields;
SymbolTable fields;
initSymbolTable(&fields);
compiler->fields = &fields;
// Compile the method definitions.
consume(compiler, TOKEN_LEFT_BRACE, "Expect '}' after class body.");
while (!match(compiler, TOKEN_RIGHT_BRACE))
{
Code instruction = CODE_METHOD_INSTANCE;
bool isConstructor = false;
if (match(compiler, TOKEN_STATIC))
{
instruction = CODE_METHOD_STATIC;
// TODO: Need to handle fields inside static methods correctly.
// Currently, they're compiled as instance fields, which will be wrong
// wrong wrong given that the receiver is actually the class obj.
}
else if (peek(compiler) == TOKEN_NEW)
{
// If the method name is "new", it's a constructor.
isConstructor = true;
}
SignatureFn signature = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
if (signature == NULL)
{
error(compiler, "Expect method definition.");
break;
}
method(compiler, instruction, isConstructor, signature);
consume(compiler, TOKEN_LINE,
"Expect newline after definition in class.");
}
// Update the class with the number of fields.
compiler->fn->bytecode[numFieldsInstruction] = fields.count;
compiler->fields = previousFields;
// Store it in its name.
defineVariable(compiler, symbol);
}
// Compiles a statement. These can only appear at the top-level or within
// curly blocks. Unlike expressions, these do not leave a value on the stack.
void statement(Compiler* compiler)
{
if (match(compiler, TOKEN_CLASS))
{
// Create a variable to store the class in.
int symbol = declareVariable(compiler);
// Load the superclass (if there is one).
if (match(compiler, TOKEN_IS))
{
parsePrecedence(compiler, 0, PREC_CALL);
emit(compiler, CODE_SUBCLASS);
}
else
{
// Create the empty class.
emit(compiler, CODE_CLASS);
}
// Store a placeholder for the number of fields argument. We don't know
// the value until we've compiled all the methods to see which fields are
// used.
int numFieldsInstruction = emit(compiler, 255);
// Set up a symbol table for the class's fields. We'll initially compile
// them to slots starting at zero. When the method is bound to the close
// the bytecode will be adjusted by [wrenBindMethod] to take inherited
// fields into account.
SymbolTable* previousFields = compiler->fields;
SymbolTable fields;
initSymbolTable(&fields);
compiler->fields = &fields;
// Compile the method definitions.
consume(compiler, TOKEN_LEFT_BRACE, "Expect '}' after class body.");
while (!match(compiler, TOKEN_RIGHT_BRACE))
{
Code instruction = CODE_METHOD_INSTANCE;
bool isConstructor = false;
if (match(compiler, TOKEN_STATIC))
{
instruction = CODE_METHOD_STATIC;
// TODO: Need to handle fields inside static methods correctly.
// Currently, they're compiled as instance fields, which will be wrong
// wrong wrong given that the receiver is actually the class obj.
}
else if (peek(compiler) == TOKEN_NEW)
{
// If the method name is "new", it's a constructor.
isConstructor = true;
}
SignatureFn signature = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
if (signature == NULL)
{
error(compiler, "Expect method definition.");
break;
}
method(compiler, instruction, isConstructor, signature);
consume(compiler, TOKEN_LINE,
"Expect newline after definition in class.");
}
// Update the class with the number of fields.
compiler->fn->bytecode[numFieldsInstruction] = fields.count;
compiler->fields = previousFields;
// Store it in its name.
defineVariable(compiler, symbol);
classStatement(compiler);
return;
}

View File

@ -0,0 +1,18 @@
class Foo {
new(value) { _value = value }
toString { return _value }
bar = value {
_value = value
return value
}
}
var a = new Foo("a")
var b = new Foo("b")
var c = new Foo("c")
// Assignment is right-associative.
a.bar = b.bar = c.bar = "d"
io.write(a.toString) // expect: d
io.write(b.toString) // expect: d
io.write(c.toString) // expect: d

View File

@ -0,0 +1,6 @@
class Foo {
bar = value { return value }
}
var foo = new Foo
(foo.bar) = "value" // expect error

View File

@ -0,0 +1,6 @@
class Foo {
bar = value { return value }
}
var foo = new Foo
"a" + foo.bar = "value" // expect error

View File

@ -0,0 +1,8 @@
class Foo {
bar = value {
io.write(value)
}
}
var foo = new Foo
foo.bar = "value" // expect: value

6
test/setter/is.wren Normal file
View File

@ -0,0 +1,6 @@
class Foo {
bar = value { return value }
}
var foo = new Foo
a is foo.bar = "value" // expect error

View File

@ -0,0 +1,6 @@
class Foo {
bar = value { return value }
}
var foo = new Foo
!foo.bar = "value" // expect error

6
test/setter/result.wren Normal file
View File

@ -0,0 +1,6 @@
class Foo {
bar = value { return "result" }
}
var foo = new Foo
io.write(foo.bar = "value") // expect: result

View File

@ -0,0 +1,8 @@
class Foo {
bar = value { io.write("set") }
bar { io.write("get") }
}
var foo = new Foo
foo.bar = "value" // expect: set
foo.bar // expect: get

7
test/setter/static.wren Normal file
View File

@ -0,0 +1,7 @@
class Foo {
static bar = value {
io.write(value)
}
}
Foo.bar = "value" // expect: value

View File

@ -20,3 +20,4 @@ class Derived is Base {
// TODO: Calling super outside of a class.
// TODO: Super calls inside nested functions in methods.
// TODO: Super where there is no inherited method.
// TODO: Super setter calls.