Allow "@method()" syntax for explicit self sends, like CoffeeScript.

This commit is contained in:
Bob Nystrom
2015-11-30 21:44:12 -08:00
parent 37b70db1b0
commit 2e425ae840
10 changed files with 180 additions and 9 deletions

View File

@ -69,6 +69,7 @@ typedef enum
TOKEN_BANG, TOKEN_BANG,
TOKEN_TILDE, TOKEN_TILDE,
TOKEN_QUESTION, TOKEN_QUESTION,
TOKEN_AT,
TOKEN_EQ, TOKEN_EQ,
TOKEN_LT, TOKEN_LT,
TOKEN_GT, TOKEN_GT,
@ -886,6 +887,7 @@ static void nextToken(Parser* parser)
case '-': makeToken(parser, TOKEN_MINUS); return; case '-': makeToken(parser, TOKEN_MINUS); return;
case '~': makeToken(parser, TOKEN_TILDE); return; case '~': makeToken(parser, TOKEN_TILDE); return;
case '?': makeToken(parser, TOKEN_QUESTION); 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_PIPEPIPE, TOKEN_PIPE); return;
case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return; case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return;
@ -1996,6 +1998,17 @@ static ClassCompiler* getEnclosingClass(Compiler* compiler)
return compiler == NULL ? NULL : compiler->enclosingClass; 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) static void field(Compiler* compiler, bool allowAssignment)
{ {
// Initialize it with a fake value so we can keep parsing and minimize the // 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) static void this_(Compiler* compiler, bool allowAssignment)
{ {
if (getEnclosingClass(compiler) == NULL) if (!ensureInsideClass(compiler, "this")) return;
{
error(compiler, "Cannot use 'this' outside of a method.");
return;
}
loadThis(compiler); loadThis(compiler);
} }
@ -2366,6 +2374,18 @@ static void conditional(Compiler* compiler, bool allowAssignment)
patchJump(compiler, elseJump); 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) void infixOp(Compiler* compiler, bool allowAssignment)
{ {
GrammarRule* rule = getRule(compiler->parser->previous.type); GrammarRule* rule = getRule(compiler->parser->previous.type);
@ -2557,6 +2577,7 @@ GrammarRule rules[] =
/* TOKEN_BANG */ PREFIX_OPERATOR("!"), /* TOKEN_BANG */ PREFIX_OPERATOR("!"),
/* TOKEN_TILDE */ PREFIX_OPERATOR("~"), /* TOKEN_TILDE */ PREFIX_OPERATOR("~"),
/* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional), /* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional),
/* TOKEN_AT */ PREFIX(thisCall),
/* TOKEN_EQ */ UNUSED, /* TOKEN_EQ */ UNUSED,
/* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "<"), /* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "<"),
/* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, ">"), /* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, ">"),

View File

@ -1,5 +1,5 @@
class Foo { class Foo {
this new { // expect error construct new { // expect error
System.print("ok") System.print("ok")
} }
} }

View File

@ -0,0 +1,12 @@
class Foo {
construct new() {}
bar { "getter" }
test() {
var bar = "local"
System.print(@bar) // expect: getter
}
}
Foo.new().test()

View File

@ -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

View File

@ -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()

View File

@ -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.

View File

@ -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()

View File

@ -0,0 +1 @@
@method() // expect error

View File

@ -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()

View File

@ -1,8 +1,8 @@
class Foo { class Foo {
construct new() {} construct new() {}
getClosure { Fn.new { toString } } getClosure() { Fn.new { toString } }
toString { "Foo" } toString { "Foo" }
} }
var closure = Foo.new().getClosure var closure = Foo.new().getClosure()
System.print(closure.call()) // expect: Foo System.print(closure.call()) // expect: Foo