diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 2aae0d57..ee839fe1 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -2064,14 +2064,19 @@ static ClassCompiler* getEnclosingClass(Compiler* compiler) return compiler == NULL ? NULL : compiler->enclosingClass; } -static void field(Compiler* compiler, bool allowAssignment) +// Determines the field index in the current enclosing class for a field with +// [name]. +// +// Implicitly defines the field if this is the first time it's used. Reports an +// error and returns `255` if the field cannot be defined. +static int findField(Compiler* compiler, const char* name, int length) { // Initialize it with a fake value so we can keep parsing and minimize the // number of cascaded errors. int field = 255; - + ClassCompiler* enclosingClass = getEnclosingClass(compiler); - + if (enclosingClass == NULL) { error(compiler, "Cannot reference a field outside of a class definition."); @@ -2088,37 +2093,67 @@ static void field(Compiler* compiler, bool allowAssignment) { // Look up the field, or implicitly define it. field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields, - compiler->parser->previous.start, - compiler->parser->previous.length); - + name, length); + if (field >= MAX_FIELDS) { error(compiler, "A class can only have %d fields.", MAX_FIELDS); } } + return field; +} + +static void loadField(Compiler* compiler, int field) +{ + // TODO: Is != NULL check right here? + // If we're directly inside a method, use a more optimal instruction. + if (compiler->parent != NULL && + compiler->parent->enclosingClass != NULL) + { + emitByteArg(compiler, CODE_LOAD_FIELD_THIS, field); + } + else + { + loadThis(compiler); + emitByteArg(compiler, CODE_LOAD_FIELD, field); + } +} + +static void storeField(Compiler* compiler, int field) +{ + // TODO: Is != NULL check right here? + // If we're directly inside a method, use a more optimal instruction. + if (compiler->parent != NULL && + compiler->parent->enclosingClass != NULL) + { + emitByteArg(compiler, CODE_STORE_FIELD_THIS, field); + } + else + { + loadThis(compiler); + emitByteArg(compiler, CODE_STORE_FIELD, field); + } +} + +static void field(Compiler* compiler, bool allowAssignment) +{ + int field = findField(compiler, + compiler->parser->previous.start, + compiler->parser->previous.length); + // If there's an "=" after a field name, it's an assignment. - bool isLoad = true; if (match(compiler, TOKEN_EQ)) { if (!allowAssignment) error(compiler, "Invalid assignment."); // Compile the right-hand side. expression(compiler); - isLoad = false; - } - - // If we're directly inside a method, use a more optimal instruction. - if (compiler->parent != NULL && - compiler->parent->enclosingClass == enclosingClass) - { - emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS, - field); + storeField(compiler, field); } else { - loadThis(compiler); - emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field); + loadField(compiler, field); } } @@ -3227,6 +3262,60 @@ static bool method(Compiler* compiler, int classSlot, bool isForeign, return true; } +static void property(Compiler* compiler, int classSlot) +{ + consume(compiler, TOKEN_NAME, "Expect property name after 'var'."); + + const char* propertyName = compiler->parser->previous.start; + int propertyLength = compiler->parser->previous.length; + + // Prepend "_" to make a field corresponding to the property. + // TODO: Put limit on field length. + char fieldName[256]; + fieldName[0] = '_'; + memcpy(&fieldName[1], propertyName, propertyLength); + int field = findField(compiler, fieldName, propertyLength + 1); + + // Synthesize the getter. + Compiler getterCompiler; + initCompiler(&getterCompiler, compiler->parser, compiler, false); + + // It loads and returns the corresponding field. + loadField(&getterCompiler, field); + emitOp(&getterCompiler, CODE_RETURN); + endCompiler(&getterCompiler, propertyName, propertyLength); + + // Define the method for it. + int getterSymbol = methodSymbol(compiler, propertyName, propertyLength); + defineMethod(compiler, classSlot, compiler->enclosingClass->inStatic, + getterSymbol); + + // Synthesize the setter. + Compiler setterCompiler; + initCompiler(&setterCompiler, compiler->parser, compiler, false); + + // It stores and returns the corresponding field. + storeField(&setterCompiler, field); + emitOp(&setterCompiler, CODE_RETURN); + endCompiler(&setterCompiler, propertyName, propertyLength); + + // Define the method for it. + Signature setterSignature = { propertyName, propertyLength, SIG_SETTER, 1 }; + int setterSymbol = signatureSymbol(compiler, &setterSignature); + defineMethod(compiler, classSlot, compiler->enclosingClass->inStatic, + setterSymbol); + + // Compile the initializer into the class body if there is one. + if (match(compiler, TOKEN_EQ)) + { + ignoreNewlines(compiler); + + expression(&compiler->enclosingClass->body); + storeField(&compiler->enclosingClass->body, field); + emitOp(&compiler->enclosingClass->body, CODE_POP); + } +} + // Compiles a class definition. Assumes the "class" token has already been // consumed (along with a possibly preceding "foreign" token). static void classDefinition(Compiler* compiler, bool isForeign) @@ -3315,6 +3404,11 @@ static void classDefinition(Compiler* compiler, bool isForeign) { method(compiler, slot, false, true); } + else if (match(compiler, TOKEN_VAR)) + { + // TODO: Static properties. + property(compiler, slot); + } else { // Any other code gets compiled into the body method. diff --git a/test/benchmark/delta_blue.wren b/test/benchmark/delta_blue.wren index 2814dcec..44ba8f63 100644 --- a/test/benchmark/delta_blue.wren +++ b/test/benchmark/delta_blue.wren @@ -376,25 +376,17 @@ class EqualityConstraint is BinaryConstraint { class Variable { construct new(name, value) { _constraints = [] - _determinedBy = null - _mark = 0 - _walkStrength = WEAKEST - _stay = true _name = name _value = value } def constraints { _constraints } - def determinedBy { _determinedBy } - def determinedBy=(value) { _determinedBy = value } - def mark { _mark } - def mark=(value) { _mark = value } - def walkStrength { _walkStrength } - def walkStrength=(value) { _walkStrength = value } - def stay { _stay } - def stay=(value) { _stay = value } - def value { _value } - def value=(newValue) { _value = newValue } + + var determinedBy + var mark = 0 + var walkStrength = WEAKEST + var stay = true + var value // Add the given constraint to the set of all constraints that refer // this variable. diff --git a/test/language/class_var/initializer_order.wren b/test/language/class_var/initializer_order.wren new file mode 100644 index 00000000..5a4fdcc0 --- /dev/null +++ b/test/language/class_var/initializer_order.wren @@ -0,0 +1,23 @@ +class Foo { + System.print("one") + + construct new() { + System.print("six") + } + + var bar = System.print("two") + + System.print("three") + + var baz = System.print("four") + + System.print("five") +} + +var foo = Foo.new() +// expect: one +// expect: two +// expect: three +// expect: four +// expect: five +// expect: six diff --git a/test/language/class_var/newline_after_equals.wren b/test/language/class_var/newline_after_equals.wren new file mode 100644 index 00000000..c2a7c897 --- /dev/null +++ b/test/language/class_var/newline_after_equals.wren @@ -0,0 +1,7 @@ +class Foo { + construct new() {} + var bar = + "value" +} + +System.print(Foo.new().bar) // expect: value diff --git a/test/language/class_var/newline_after_var.wren b/test/language/class_var/newline_after_var.wren new file mode 100644 index 00000000..80c92b9e --- /dev/null +++ b/test/language/class_var/newline_after_var.wren @@ -0,0 +1,5 @@ +class Foo { + construct new() {} + var // expect error + bar +} diff --git a/test/language/class_var/with_initializer.wren b/test/language/class_var/with_initializer.wren new file mode 100644 index 00000000..6c731d3f --- /dev/null +++ b/test/language/class_var/with_initializer.wren @@ -0,0 +1,9 @@ +class Foo { + construct new() {} + var bar = "init" +} + +var foo = Foo.new() +System.print(foo.bar) // expect: init +System.print(foo.bar = "value") // expect: value +System.print(foo.bar) // expect: value diff --git a/test/language/class_var/without_initializer.wren b/test/language/class_var/without_initializer.wren new file mode 100644 index 00000000..e1b17d9f --- /dev/null +++ b/test/language/class_var/without_initializer.wren @@ -0,0 +1,12 @@ +class Foo { + construct new() {} + var bar +} + +var foo = Foo.new() +System.print(foo.bar) // expect: null +System.print(foo.bar = "value") // expect: value +System.print(foo.bar) // expect: value + +// TODO: Duplicate. +// TODO: static var.