diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 341d8e48..8303e73d 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -69,6 +69,7 @@ typedef enum TOKEN_BANG, TOKEN_TILDE, TOKEN_QUESTION, + TOKEN_AT, TOKEN_EQ, TOKEN_LT, TOKEN_GT, @@ -886,6 +887,7 @@ static void nextToken(Parser* parser) case '-': makeToken(parser, TOKEN_MINUS); return; case '~': makeToken(parser, TOKEN_TILDE); return; case '?': makeToken(parser, TOKEN_QUESTION); return; + case '@': makeToken(parser, TOKEN_AT); return; case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return; case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return; @@ -1996,6 +1998,17 @@ static ClassCompiler* getEnclosingClass(Compiler* compiler) return compiler == NULL ? NULL : compiler->enclosingClass; } +// Returns `true` if the compiler is currently inside a class definition. +// +// Otherwise, reports an error and returns `false`. +static bool ensureInsideClass(Compiler* compiler, const char* syntax) +{ + if (getEnclosingClass(compiler) != NULL) return true; + + error(compiler, "Cannot use '%s' outside of a class definition.", syntax); + return false; +} + static void field(Compiler* compiler, bool allowAssignment) { // Initialize it with a fake value so we can keep parsing and minimize the @@ -2280,12 +2293,7 @@ static void super_(Compiler* compiler, bool allowAssignment) static void this_(Compiler* compiler, bool allowAssignment) { - if (getEnclosingClass(compiler) == NULL) - { - error(compiler, "Cannot use 'this' outside of a method."); - return; - } - + if (!ensureInsideClass(compiler, "this")) return; loadThis(compiler); } @@ -2366,6 +2374,18 @@ static void conditional(Compiler* compiler, bool allowAssignment) patchJump(compiler, elseJump); } +// A method call on this like "@method()". +static void thisCall(Compiler* compiler, bool allowAssignment) +{ + if (!ensureInsideClass(compiler, "@")) return; + + loadThis(compiler); + consume(compiler, TOKEN_NAME, "Expect method name after '@'."); + namedCall(compiler, allowAssignment, CODE_CALL_0); + + // TODO: Infix and subscript operators? +} + void infixOp(Compiler* compiler, bool allowAssignment) { GrammarRule* rule = getRule(compiler->parser->previous.type); @@ -2557,6 +2577,7 @@ GrammarRule rules[] = /* TOKEN_BANG */ PREFIX_OPERATOR("!"), /* TOKEN_TILDE */ PREFIX_OPERATOR("~"), /* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional), + /* TOKEN_AT */ PREFIX(thisCall), /* TOKEN_EQ */ UNUSED, /* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "<"), /* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, ">"), diff --git a/test/language/constructor/no_parameter_list.wren b/test/language/constructor/no_parameter_list.wren index 5c0e232a..1f65ed1e 100644 --- a/test/language/constructor/no_parameter_list.wren +++ b/test/language/constructor/no_parameter_list.wren @@ -1,5 +1,5 @@ class Foo { - this new { // expect error + construct new { // expect error System.print("ok") } } diff --git a/test/language/self_call/ignores_locals.wren b/test/language/self_call/ignores_locals.wren new file mode 100644 index 00000000..d3887fe8 --- /dev/null +++ b/test/language/self_call/ignores_locals.wren @@ -0,0 +1,12 @@ +class Foo { + construct new() {} + + bar { "getter" } + + test() { + var bar = "local" + System.print(@bar) // expect: getter + } +} + +Foo.new().test() diff --git a/test/language/self_call/in_closure.wren b/test/language/self_call/in_closure.wren new file mode 100644 index 00000000..a0fcc283 --- /dev/null +++ b/test/language/self_call/in_closure.wren @@ -0,0 +1,13 @@ +class Foo { + construct new(field) { + _field = field + } + + method() { + System.print(_field) + } + + makeClosure() { Fn.new { @method() } } +} + +Foo.new("value").makeClosure().call() // expect: value diff --git a/test/language/self_call/inherited_methods.wren b/test/language/self_call/inherited_methods.wren new file mode 100644 index 00000000..0fd5b659 --- /dev/null +++ b/test/language/self_call/inherited_methods.wren @@ -0,0 +1,27 @@ +class Foo { + construct new() {} + + getter { + System.print("getter") + } + + setter=(value) { + System.print("setter") + } + + method(a) { + System.print("method") + } +} + +class Bar is Foo { + construct new() {} + + test() { + @getter // expect: getter + @setter = "value" // expect: setter + @method("arg") // expect: method + } +} + +Bar.new().test() diff --git a/test/language/self_call/instance_methods.wren b/test/language/self_call/instance_methods.wren new file mode 100644 index 00000000..3fe26307 --- /dev/null +++ b/test/language/self_call/instance_methods.wren @@ -0,0 +1,25 @@ +class Foo { + construct new() {} + + getter { + System.print("getter") + } + + setter=(value) { + System.print("setter") + } + + method(a) { + System.print("method") + } + + test() { + @getter // expect: getter + @setter = "value" // expect: setter + @method("arg") // expect: method + } +} + +Foo.new().test() + +// TODO: Operators. diff --git a/test/language/self_call/nested_class.wren b/test/language/self_call/nested_class.wren new file mode 100644 index 00000000..0e053d72 --- /dev/null +++ b/test/language/self_call/nested_class.wren @@ -0,0 +1,51 @@ +class Outer { + construct new() {} + + getter { + System.print("outer getter") + } + + setter=(value) { + System.print("outer setter") + } + + method(a) { + System.print("outer method") + } + + test() { + @getter // expect: outer getter + @setter = "value" // expect: outer setter + @method("arg") // expect: outer method + + class Inner { + construct new() {} + + getter { + System.print("inner getter") + } + + setter=(value) { + System.print("inner setter") + } + + method(a) { + System.print("inner method") + } + + test() { + @getter // expect: inner getter + @setter = "value" // expect: inner setter + @method("arg") // expect: inner method + } + } + + Inner.new().test() + + @getter // expect: outer getter + @setter = "value" // expect: outer setter + @method("arg") // expect: outer method + } +} + +Outer.new().test() diff --git a/test/language/self_call/outside_class.wren b/test/language/self_call/outside_class.wren new file mode 100644 index 00000000..02bedd03 --- /dev/null +++ b/test/language/self_call/outside_class.wren @@ -0,0 +1 @@ +@method() // expect error diff --git a/test/language/self_call/static_methods.wren b/test/language/self_call/static_methods.wren new file mode 100644 index 00000000..ad2a2167 --- /dev/null +++ b/test/language/self_call/static_methods.wren @@ -0,0 +1,21 @@ +class Foo { + static getter { + System.print("getter") + } + + static setter=(value) { + System.print("setter") + } + + static method(a) { + System.print("method") + } + + static test() { + @getter // expect: getter + @setter = "value" // expect: setter + @method("arg") // expect: method + } +} + +Foo.test() diff --git a/test/language/this/closure.wren b/test/language/this/closure.wren index 60d49a58..845d4344 100644 --- a/test/language/this/closure.wren +++ b/test/language/this/closure.wren @@ -1,8 +1,8 @@ class Foo { construct new() {} - getClosure { Fn.new { toString } } + getClosure() { Fn.new { toString } } toString { "Foo" } } -var closure = Foo.new().getClosure +var closure = Foo.new().getClosure() System.print(closure.call()) // expect: Foo