mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 22:28:45 +01:00
Setters!
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
||||
|
||||
18
test/setter/associativity.wren
Normal file
18
test/setter/associativity.wren
Normal 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
|
||||
6
test/setter/grouping.wren
Normal file
6
test/setter/grouping.wren
Normal file
@ -0,0 +1,6 @@
|
||||
class Foo {
|
||||
bar = value { return value }
|
||||
}
|
||||
|
||||
var foo = new Foo
|
||||
(foo.bar) = "value" // expect error
|
||||
6
test/setter/infix_operator.wren
Normal file
6
test/setter/infix_operator.wren
Normal file
@ -0,0 +1,6 @@
|
||||
class Foo {
|
||||
bar = value { return value }
|
||||
}
|
||||
|
||||
var foo = new Foo
|
||||
"a" + foo.bar = "value" // expect error
|
||||
8
test/setter/instance.wren
Normal file
8
test/setter/instance.wren
Normal 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
6
test/setter/is.wren
Normal file
@ -0,0 +1,6 @@
|
||||
class Foo {
|
||||
bar = value { return value }
|
||||
}
|
||||
|
||||
var foo = new Foo
|
||||
a is foo.bar = "value" // expect error
|
||||
6
test/setter/prefix_operator.wren
Normal file
6
test/setter/prefix_operator.wren
Normal 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
6
test/setter/result.wren
Normal file
@ -0,0 +1,6 @@
|
||||
class Foo {
|
||||
bar = value { return "result" }
|
||||
}
|
||||
|
||||
var foo = new Foo
|
||||
io.write(foo.bar = "value") // expect: result
|
||||
8
test/setter/same_name_as_method.wren
Normal file
8
test/setter/same_name_as_method.wren
Normal 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
7
test/setter/static.wren
Normal file
@ -0,0 +1,7 @@
|
||||
class Foo {
|
||||
static bar = value {
|
||||
io.write(value)
|
||||
}
|
||||
}
|
||||
|
||||
Foo.bar = "value" // expect: value
|
||||
@ -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.
|
||||
|
||||
Reference in New Issue
Block a user