From 8ed0cde91caa2c896303fa8c9daa94c3ebc86198 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Mon, 21 Dec 2015 08:04:39 -0800 Subject: [PATCH] Allow "var" in a class body for defining "properties". A property is a field with an implicit getter, setter, and optional class body initializer. It's handy for defining publicly visible state in a class. When modules are classes, this is needed for "top level" variables. Right now, a class var gets both a getter and setter. It would be nice to also have something like "val" for properties that are publicly visible but not settable. Also, still need to support "static var" for metaclass properties. --- src/vm/wren_compiler.c | 130 +++++++++++++++--- test/benchmark/delta_blue.wren | 20 +-- .../language/class_var/initializer_order.wren | 23 ++++ .../class_var/newline_after_equals.wren | 7 + .../language/class_var/newline_after_var.wren | 5 + test/language/class_var/with_initializer.wren | 9 ++ .../class_var/without_initializer.wren | 12 ++ 7 files changed, 174 insertions(+), 32 deletions(-) create mode 100644 test/language/class_var/initializer_order.wren create mode 100644 test/language/class_var/newline_after_equals.wren create mode 100644 test/language/class_var/newline_after_var.wren create mode 100644 test/language/class_var/with_initializer.wren create mode 100644 test/language/class_var/without_initializer.wren 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.