diff --git a/Makefile b/Makefile index c402486a..493b5868 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ test: debug benchmark: release @ $(MAKE) -f util/wren.mk test - @ ./util/benchmark.py $(suite) + @ ./util/benchmark.py -l wren $(suite) # Generate the Wren site. docs: diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 617bcbe8..2aae0d57 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -277,21 +277,7 @@ typedef struct int arity; } Signature; -// Bookkeeping information for compiling a class definition. -typedef struct -{ - // Symbol table for the fields of the class. - SymbolTable fields; - - // True if the class being compiled is a foreign class. - bool isForeign; - - // True if the current method being compiled is static. - bool inStatic; - - // The signature of the method being compiled. - Signature* signature; -} ClassCompiler; +typedef struct sClassCompiler ClassCompiler; struct sCompiler { @@ -353,6 +339,28 @@ struct sCompiler IntBuffer debugSourceLines; }; +// Bookkeeping information for compiling a class definition. +struct sClassCompiler +{ + // Symbol table for the fields of the class. + SymbolTable fields; + + // True if the class being compiled is a foreign class. + bool isForeign; + + // True if the current method being compiled is static. + bool inStatic; + + // The signature of the method being compiled. + Signature* signature; + + // The compiler used for body code. + // + // Any code inside a class body that is not a method definition will get + // compiled into this function which is then called by each constructor. + Compiler body; +}; + // The stack effect of each opcode. The index in the array is the opcode, and // the value is the stack effect of that instruction. static const int stackEffects[] = { @@ -1436,6 +1444,22 @@ static void loadLocal(Compiler* compiler, int slot) emitByteArg(compiler, CODE_LOAD_LOCAL, slot); } +// Loads the receiver of the currently enclosing method. Correctly handles +// functions defined inside methods. +static void loadThis(Compiler* compiler) +{ + Code loadInstruction; + int index = resolveNonmodule(compiler, "this", 4, &loadInstruction); + if (loadInstruction == CODE_LOAD_LOCAL) + { + loadLocal(compiler, index); + } + else + { + emitByteArg(compiler, loadInstruction, index); + } +} + // Discards memory owned by [compiler]. static void freeCompiler(Compiler* compiler) { @@ -1616,31 +1640,6 @@ static bool finishBlock(Compiler* compiler) return false; } -// Parses a method or function body, after the initial "{" has been consumed. -// -// It [isInitializer] is `true`, this is the body of a constructor initializer. -// In that case, this adds the code to ensure it returns `this`. -static void finishBody(Compiler* compiler, bool isInitializer) -{ - bool isExpressionBody = finishBlock(compiler); - - if (isInitializer) - { - // If the initializer body evaluates to a value, discard it. - if (isExpressionBody) emitOp(compiler, CODE_POP); - - // The receiver is always stored in the first local slot. - emitOp(compiler, CODE_LOAD_LOCAL_0); - } - else if (!isExpressionBody) - { - // Implicitly return null in statement bodies. - emitOp(compiler, CODE_NULL); - } - - emitOp(compiler, CODE_RETURN); -} - // The VM can only handle a certain number of parameters, so check that we // haven't exceeded that and give a usable error. static void validateNumParameters(Compiler* compiler, int numArgs) @@ -1815,6 +1814,43 @@ static void callMethod(Compiler* compiler, int numArgs, const char* name, emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol); } +// Parses a method or function body, after the initial "{" has been consumed. +// +// It [isInitializer] is `true`, this is the body of a constructor initializer. +// In that case, this adds the code to execute the code in the class body and +// make the method return `this`. +static void finishBody(Compiler* compiler, bool isInitializer) +{ + if (isInitializer) + { + // Execute the class body code before the initializer code. + loadThis(compiler); + callMethod(compiler, 0, "", 6); + emitOp(compiler, CODE_POP); + // TODO: This regresses performance on classes that don't have any code in + // their body and where calling this is a no-op. See if there's a way we + // can elide the call when not needed. + } + + bool isExpressionBody = finishBlock(compiler); + + if (isInitializer) + { + // If the initializer body evaluates to a value, discard it. + if (isExpressionBody) emitOp(compiler, CODE_POP); + + // The receiver is always stored in the first local slot. + emitOp(compiler, CODE_LOAD_LOCAL_0); + } + else if (!isExpressionBody) + { + // Implicitly return null in statement bodies. + emitOp(compiler, CODE_NULL); + } + + emitOp(compiler, CODE_RETURN); +} + // Compiles an (optional) argument list for a method call with [methodSignature] // and then calls it. static void methodCall(Compiler* compiler, Code instruction, @@ -1915,22 +1951,6 @@ static void namedCall(Compiler* compiler, bool allowAssignment, } } -// Loads the receiver of the currently enclosing method. Correctly handles -// functions defined inside methods. -static void loadThis(Compiler* compiler) -{ - Code loadInstruction; - int index = resolveNonmodule(compiler, "this", 4, &loadInstruction); - if (loadInstruction == CODE_LOAD_LOCAL) - { - loadLocal(compiler, index); - } - else - { - emitByteArg(compiler, loadInstruction, index); - } -} - // Pushes the value for a module-level variable implicitly imported from core. static void loadCoreVariable(Compiler* compiler, const char* name) { @@ -3128,25 +3148,21 @@ static void defineMethod(Compiler* compiler, int classSlot, bool isStatic, // // Returns `true` if it compiled successfully, or `false` if the method couldn't // be parsed. -static bool method(Compiler* compiler, int classSlot) +static bool method(Compiler* compiler, int classSlot, bool isForeign, + bool isConstructor) { - // TODO: What about foreign constructors? - bool isForeign = match(compiler, TOKEN_FOREIGN); - compiler->enclosingClass->inStatic = match(compiler, TOKEN_STATIC); - SignatureFn signatureFn; // Methods are declared using "construct" for constructors or "def" for // everything else. - if (match(compiler, TOKEN_CONSTRUCT)) + if (isConstructor) { signatureFn = constructorSignature; } else { - consume(compiler, TOKEN_DEF, "Expect 'def' before method definition."); signatureFn = rules[compiler->parser->current.type].method; - nextToken(compiler->parser); + nextToken(compiler->parser); } if (signatureFn == NULL) @@ -3264,6 +3280,8 @@ static void classDefinition(Compiler* compiler, bool isForeign) wrenSymbolTableInit(&classCompiler.fields); compiler->enclosingClass = &classCompiler; + + initCompiler(&classCompiler.body, compiler->parser, compiler, false); // Compile the method definitions. consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration."); @@ -3271,14 +3289,52 @@ static void classDefinition(Compiler* compiler, bool isForeign) while (!match(compiler, TOKEN_RIGHT_BRACE)) { - if (!method(compiler, slot)) break; + // Determine if we're defining a method or executing code in the class body. + // Methods can have a couple of different preambles using some combinations + // of "foreign", "static", "construct", and "def". + compiler->enclosingClass->inStatic = false; + + if (match(compiler, TOKEN_FOREIGN)) + { + compiler->enclosingClass->inStatic = match(compiler, TOKEN_STATIC); + consume(compiler, TOKEN_DEF, + "Expect 'def' before foreign method declaration."); + method(compiler, slot, true, false); + } + else if (match(compiler, TOKEN_STATIC)) + { + compiler->enclosingClass->inStatic = true; + consume(compiler, TOKEN_DEF, "Expect 'def' after 'static'."); + method(compiler, slot, false, false); + } + else if (match(compiler, TOKEN_DEF)) + { + method(compiler, slot, false, false); + } + else if (match(compiler, TOKEN_CONSTRUCT)) + { + method(compiler, slot, false, true); + } + else + { + // Any other code gets compiled into the body method. + statement(&classCompiler.body); + } - // Don't require a newline after the last definition. + // Don't require a newline after the last definition or statement. if (match(compiler, TOKEN_RIGHT_BRACE)) break; consumeLine(compiler, "Expect newline after definition in class."); } + // Define a method to contain all the code in the class body. + // TODO: Better debug name including class name. + emitOp(&classCompiler.body, CODE_NULL); + emitOp(&classCompiler.body, CODE_RETURN); + endCompiler(&classCompiler.body, "", 6); + int bodySymbol = methodSymbol(compiler, "", 6); + defineMethod(compiler, slot, false, bodySymbol); + // Update the class with the number of fields. if (!isForeign) { diff --git a/test/language/class/code_in_body.wren b/test/language/class/code_in_body.wren new file mode 100644 index 00000000..aeadde52 --- /dev/null +++ b/test/language/class/code_in_body.wren @@ -0,0 +1,28 @@ +class Foo { + System.print("one") + + construct new() { + System.print("constructor") + } + + System.print("two") + + construct other() { + System.print("other constructor") + } +} + +// Does not execute body until object is constructed. +System.print("before") // expect: before + +// Executes body before constructor body. +Foo.new() // expect: one + // expect: two + // expect: constructor + +// Executes body for each constructor. +Foo.other() // expect: one + // expect: two + // expect: other constructor + +// TODO: Calling (and not calling) superclass constructor in subclass. diff --git a/test/language/class/this_in_body.wren b/test/language/class/this_in_body.wren new file mode 100644 index 00000000..dc96c0da --- /dev/null +++ b/test/language/class/this_in_body.wren @@ -0,0 +1,9 @@ +class Foo { + construct new() {} + + System.print(this) + + def toString { "foo" } +} + +Foo.new() // expect: foo diff --git a/test/language/field/field_in_body.wren b/test/language/field/field_in_body.wren new file mode 100644 index 00000000..69684318 --- /dev/null +++ b/test/language/field/field_in_body.wren @@ -0,0 +1,16 @@ +class Foo { + construct new() { + System.print("constructor %(_field)") + } + + System.print("before %(_field)") + + _field = "initialized" + + System.print("after %(_field)") +} + +Foo.new() +// expect: before null +// expect: after initialized +// expect: constructor initialized diff --git a/test/language/class/field_in_foreign_class.wren b/test/language/field/field_in_foreign_class.wren similarity index 100% rename from test/language/class/field_in_foreign_class.wren rename to test/language/field/field_in_foreign_class.wren diff --git a/test/language/class/foreign_class_inherit_fields.wren b/test/language/foreign/foreign_class_inherit_fields.wren similarity index 100% rename from test/language/class/foreign_class_inherit_fields.wren rename to test/language/foreign/foreign_class_inherit_fields.wren