From 756fe0d9205d102fe192eff23035c417cc0c0719 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Wed, 12 Feb 2014 17:33:35 -0800 Subject: [PATCH] Make "this" the implicit receiver! --- benchmark/delta_blue.wren | 78 +++++++++---------- builtin/core.wren | 2 +- src/wren_compiler.c | 25 ++++-- src/wren_core.c | 2 +- test/implicit_receiver/inherited_methods.wren | 23 ++++++ test/implicit_receiver/instance_methods.wren | 23 ++++++ .../locals_shadow_getter.wren | 17 ++++ .../locals_shadow_setter.wren | 20 +++++ test/implicit_receiver/nested_class.wren | 47 +++++++++++ test/implicit_receiver/static_methods.wren | 21 +++++ test/this/closure.wren | 2 +- test/this/nested_class.wren | 6 +- test/this/nested_closure.wren | 2 +- 13 files changed, 217 insertions(+), 51 deletions(-) create mode 100644 test/implicit_receiver/inherited_methods.wren create mode 100644 test/implicit_receiver/instance_methods.wren create mode 100644 test/implicit_receiver/locals_shadow_getter.wren create mode 100644 test/implicit_receiver/locals_shadow_setter.wren create mode 100644 test/implicit_receiver/nested_class.wren create mode 100644 test/implicit_receiver/static_methods.wren diff --git a/benchmark/delta_blue.wren b/benchmark/delta_blue.wren index 51f82a28..cb24420f 100644 --- a/benchmark/delta_blue.wren +++ b/benchmark/delta_blue.wren @@ -101,7 +101,7 @@ class Constraint { // Activate this constraint and attempt to satisfy it. addConstraint { - this.addToGraph + addToGraph planner.incrementalAdd(this) } @@ -111,16 +111,16 @@ class Constraint { // there is one, or nil, if there isn't. // Assume: I am not already satisfied. satisfy(mark) { - this.chooseMethod(mark) - if (!this.isSatisfied) { + chooseMethod(mark) + if (!isSatisfied) { if (_strength == REQUIRED) { IO.print("Could not satisfy a required constraint!") } return null } - this.markInputs(mark) - var out = this.output + markInputs(mark) + var out = output var overridden = out.determinedBy if (overridden != null) overridden.markUnsatisfied out.determinedBy = this @@ -130,8 +130,8 @@ class Constraint { } destroyConstraint { - if (this.isSatisfied) planner.incrementalRemove(this) - this.removeFromGraph + if (isSatisfied) planner.incrementalRemove(this) + removeFromGraph } // Normal constraints are not input constraints. An input constraint @@ -146,7 +146,7 @@ class UnaryConstraint is Constraint { super(strength) _satisfied = false _myOutput = myOutput - this.addConstraint + addConstraint } // Adds this constraint to the constraint graph. @@ -158,7 +158,7 @@ class UnaryConstraint is Constraint { // Decides if this constraint can be satisfied and records that decision. chooseMethod(mark) { _satisfied = (_myOutput.mark != mark) && - Strength.stronger(this.strength, _myOutput.walkStrength) + Strength.stronger(strength, _myOutput.walkStrength) } // Returns true if this constraint is satisfied in the current solution. @@ -175,9 +175,9 @@ class UnaryConstraint is Constraint { // 'stay', the value for the current output of this constraint. Assume // this constraint is satisfied. recalculate { - _myOutput.walkStrength = this.strength - _myOutput.stay = !this.isInput - if (_myOutput.stay) this.execute // Stay optimization. + _myOutput.walkStrength = strength + _myOutput.stay = !isInput + if (_myOutput.stay) execute // Stay optimization. } // Records that this constraint is unsatisfied. @@ -235,7 +235,7 @@ class BinaryConstraint is Constraint { _v1 = v1 _v2 = v2 _direction = NONE - this.addConstraint + addConstraint } direction { return _direction } @@ -248,7 +248,7 @@ class BinaryConstraint is Constraint { chooseMethod(mark) { if (_v1.mark == mark) { if (_v2.mark != mark && - Strength.stronger(this.strength, _v2.walkStrength)) { + Strength.stronger(strength, _v2.walkStrength)) { _direction = FORWARD } else { _direction = NONE @@ -257,7 +257,7 @@ class BinaryConstraint is Constraint { if (_v2.mark == mark) { if (_v1.mark != mark && - Strength.stronger(this.strength, _v1.walkStrength)) { + Strength.stronger(strength, _v1.walkStrength)) { _direction = BACKWARD } else { _direction = NONE @@ -265,13 +265,13 @@ class BinaryConstraint is Constraint { } if (Strength.weaker(_v1.walkStrength, _v2.walkStrength)) { - if (Strength.stronger(this.strength, _v1.walkStrength)) { + if (Strength.stronger(strength, _v1.walkStrength)) { _direction = BACKWARD } else { _direction = NONE } } else { - if (Strength.stronger(this.strength, _v2.walkStrength)) { + if (Strength.stronger(strength, _v2.walkStrength)) { _direction = FORWARD } else { _direction = BACKWARD @@ -291,7 +291,7 @@ class BinaryConstraint is Constraint { // Mark the input variable with the given mark. markInputs(mark) { - this.input.mark = mark + input.mark = mark } // Returns the current input variable @@ -310,11 +310,11 @@ class BinaryConstraint is Constraint { // 'stay', the value for the current output of this // constraint. Assume this constraint is satisfied. recalculate { - var ihn = this.input - var out = this.output - out.walkStrength = Strength.weakest(this.strength, ihn.walkStrength) + var ihn = input + var out = output + out.walkStrength = Strength.weakest(strength, ihn.walkStrength) out.stay = ihn.stay - if (out.stay) this.execute + if (out.stay) execute } // Record the fact that this constraint is unsatisfied. @@ -323,7 +323,7 @@ class BinaryConstraint is Constraint { } inputsKnown(mark) { - var i = this.input + var i = input return i.mark == mark || i.stay || i.determinedBy == null } @@ -365,11 +365,11 @@ class ScaleConstraint is BinaryConstraint { // Enforce this constraint. Assume that it is satisfied. execute { - if (this.direction == FORWARD) { - this.v2.value = this.v1.value * _scale.value + _offset.value; + if (direction == FORWARD) { + v2.value = v1.value * _scale.value + _offset.value; } else { // TODO: Is this the same semantics as ~/? - this.v1.value = ((this.v2.value - _offset.value) / _scale.value).floor; + v1.value = ((v2.value - _offset.value) / _scale.value).floor; } } @@ -377,11 +377,11 @@ class ScaleConstraint is BinaryConstraint { // 'stay', the value for the current output of this constraint. Assume // this constraint is satisfied. recalculate { - var ihn = this.input - var out = this.output - out.walkStrength = Strength.weakest(this.strength, ihn.walkStrength) + var ihn = input + var out = output + out.walkStrength = Strength.weakest(strength, ihn.walkStrength) out.stay = ihn.stay && _scale.stay && _offset.stay - if (out.stay) this.execute + if (out.stay) execute } } @@ -393,7 +393,7 @@ class EqualityConstraint is BinaryConstraint { // Enforce this constraint. Assume that it is satisfied. execute { - this.output.value = this.input.value + output.value = input.value } } @@ -484,7 +484,7 @@ class Planner { // the algorithm to avoid getting into an infinite loop even if the // constraint graph has an inadvertent cycle. incrementalAdd(constraint) { - var mark = this.newMark + var mark = newMark var overridden = constraint.satisfy(mark) while (overridden != null) { overridden = overridden.satisfy(mark) @@ -504,12 +504,12 @@ class Planner { var out = constraint.output constraint.markUnsatisfied constraint.removeFromGraph - var unsatisfied = this.removePropagateFrom(out) + var unsatisfied = removePropagateFrom(out) var strength = REQUIRED while (true) { for (i in 0...unsatisfied.count) { var u = unsatisfied[i] - if (u.strength == strength) this.incrementalAdd(u) + if (u.strength == strength) incrementalAdd(u) } strength = strength.nextWeaker if (strength == WEAKEST) break @@ -540,7 +540,7 @@ class Planner { // any constraint. // Assume: [sources] are all satisfied. makePlan(sources) { - var mark = this.newMark + var mark = newMark var plan = new Plan var todo = sources while (todo.count > 0) { @@ -548,7 +548,7 @@ class Planner { if (constraint.output.mark != mark && constraint.inputsKnown(mark)) { plan.addConstraint(constraint) constraint.output.mark = mark - this.addConstraintsConsumingTo(constraint.output, todo) + addConstraintsConsumingTo(constraint.output, todo) } } return plan @@ -563,7 +563,7 @@ class Planner { // if not in plan already and eligible for inclusion. if (constraint.isInput && constraint.isSatisfied) sources.add(constraint) } - return this.makePlan(sources) + return makePlan(sources) } // Recompute the walkabout strengths and stay flags of all variables @@ -582,12 +582,12 @@ class Planner { while (todo.count > 0) { var d = todo.removeAt(-1) if (d.output.mark == mark) { - this.incrementalRemove(constraint) + incrementalRemove(constraint) return false } d.recalculate - this.addConstraintsConsumingTo(d.output, todo) + addConstraintsConsumingTo(d.output, todo) } return true diff --git a/builtin/core.wren b/builtin/core.wren index 4ea15bb6..12c23005 100644 --- a/builtin/core.wren +++ b/builtin/core.wren @@ -1,7 +1,7 @@ class List { toString { var result = "[" - for (i in 0...this.count) { + for (i in 0...count) { if (i > 0) result = result + ", " result = result + this[i].toString } diff --git a/src/wren_compiler.c b/src/wren_compiler.c index 5e765e3b..1682093f 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -1434,13 +1434,12 @@ static void methodCall(Compiler* compiler, Code instruction, methodSymbol(compiler, name, length)); } -// Compiles an expression that starts with ".name". That includes getters, -// method calls with arguments, and setter calls. +// Compiles a call whose name is the previously consumed token. This includes +// getters, method calls with arguments, and setter calls. static void namedCall(Compiler* compiler, bool allowAssignment, Code instruction) { // Build the method name. - consume(compiler, TOKEN_NAME, "Expect method name after '.'."); char name[MAX_METHOD_SIGNATURE]; int length = copyName(compiler, name); @@ -1703,9 +1702,23 @@ static void name(Compiler* compiler, bool allowAssignment) Code loadInstruction; int index = resolveName(compiler, token->start, token->length, &loadInstruction); - if (index == -1) error(compiler, "Undefined variable."); + if (index != -1) + { + variable(compiler, allowAssignment, index, loadInstruction); + return; + } - variable(compiler, allowAssignment, index, loadInstruction); + // Otherwise, if we are inside a class, it's a getter call with an implicit + // receiver. + ClassCompiler* classCompiler = getEnclosingClass(compiler); + if (classCompiler == NULL) + { + error(compiler, "Undefined variable."); + return; + } + + loadThis(compiler); + namedCall(compiler, allowAssignment, CODE_CALL_0); } static void null(Compiler* compiler, bool allowAssignment) @@ -1781,6 +1794,7 @@ static void super_(Compiler* compiler, bool allowAssignment) if (match(compiler, TOKEN_DOT)) { // Compile the superclass call. + consume(compiler, TOKEN_NAME, "Expect method name after 'super.'."); namedCall(compiler, allowAssignment, CODE_SUPER_0); } else @@ -1851,6 +1865,7 @@ static void subscript(Compiler* compiler, bool allowAssignment) void call(Compiler* compiler, bool allowAssignment) { + consume(compiler, TOKEN_NAME, "Expect method name after '.'."); namedCall(compiler, allowAssignment, CODE_CALL_0); } diff --git a/src/wren_core.c b/src/wren_core.c index 28a85aad..4777bc2b 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -44,7 +44,7 @@ static const char* libSource = "class List {\n" " toString {\n" " var result = \"[\"\n" -" for (i in 0...this.count) {\n" +" for (i in 0...count) {\n" " if (i > 0) result = result + \", \"\n" " result = result + this[i].toString\n" " }\n" diff --git a/test/implicit_receiver/inherited_methods.wren b/test/implicit_receiver/inherited_methods.wren new file mode 100644 index 00000000..c1edbfff --- /dev/null +++ b/test/implicit_receiver/inherited_methods.wren @@ -0,0 +1,23 @@ +class Foo { + getter { + IO.print("getter") + } + + setter = value { + IO.print("setter") + } + + method(a) { + IO.print("method") + } +} + +class Bar is Foo { + test { + getter // expect: getter + setter = "value" // expect: setter + method("arg") // expect: method + } +} + +(new Bar).test diff --git a/test/implicit_receiver/instance_methods.wren b/test/implicit_receiver/instance_methods.wren new file mode 100644 index 00000000..8092b415 --- /dev/null +++ b/test/implicit_receiver/instance_methods.wren @@ -0,0 +1,23 @@ +class Foo { + getter { + IO.print("getter") + } + + setter = value { + IO.print("setter") + } + + method(a) { + IO.print("method") + } + + test { + getter // expect: getter + setter = "value" // expect: setter + method("arg") // expect: method + } +} + +(new Foo).test + +// TODO: Need to decide how these interact with globals. diff --git a/test/implicit_receiver/locals_shadow_getter.wren b/test/implicit_receiver/locals_shadow_getter.wren new file mode 100644 index 00000000..b4f2870e --- /dev/null +++ b/test/implicit_receiver/locals_shadow_getter.wren @@ -0,0 +1,17 @@ +class Foo { + bar { return "getter" } + + test { + IO.print(bar) // expect: getter + + { + IO.print(bar) // expect: getter + var bar = "local" + IO.print(bar) // expect: local + } + + IO.print(bar) // expect: getter + } +} + +(new Foo).test diff --git a/test/implicit_receiver/locals_shadow_setter.wren b/test/implicit_receiver/locals_shadow_setter.wren new file mode 100644 index 00000000..3ca911eb --- /dev/null +++ b/test/implicit_receiver/locals_shadow_setter.wren @@ -0,0 +1,20 @@ +class Foo { + bar = value { + IO.print("setter") + return value + } + + test { + bar = "value" // expect: setter + + { + bar = "value" // expect: setter + var bar = "local" + bar = "value" // no expectation + } + + bar = "value" // expect: setter + } +} + +(new Foo).test diff --git a/test/implicit_receiver/nested_class.wren b/test/implicit_receiver/nested_class.wren new file mode 100644 index 00000000..1d88e4d8 --- /dev/null +++ b/test/implicit_receiver/nested_class.wren @@ -0,0 +1,47 @@ +class Outer { + getter { + IO.print("outer getter") + } + + setter = value { + IO.print("outer setter") + } + + method(a) { + IO.print("outer method") + } + + test { + getter // expect: outer getter + setter = "value" // expect: outer setter + method("arg") // expect: outer method + + class Inner { + getter { + IO.print("inner getter") + } + + setter = value { + IO.print("inner setter") + } + + method(a) { + IO.print("inner method") + } + + test { + getter // expect: inner getter + setter = "value" // expect: inner setter + method("arg") // expect: inner method + } + } + + (new Inner).test + + getter // expect: outer getter + setter = "value" // expect: outer setter + method("arg") // expect: outer method + } +} + +(new Outer).test diff --git a/test/implicit_receiver/static_methods.wren b/test/implicit_receiver/static_methods.wren new file mode 100644 index 00000000..3c4594eb --- /dev/null +++ b/test/implicit_receiver/static_methods.wren @@ -0,0 +1,21 @@ +class Foo { + static getter { + IO.print("getter") + } + + static setter = value { + IO.print("setter") + } + + static method(a) { + IO.print("method") + } + + static test { + getter // expect: getter + setter = "value" // expect: setter + method("arg") // expect: method + } +} + +Foo.test diff --git a/test/this/closure.wren b/test/this/closure.wren index 013a8b42..46ddd28c 100644 --- a/test/this/closure.wren +++ b/test/this/closure.wren @@ -1,7 +1,7 @@ class Foo { getClosure { return fn { - return this.toString + return toString } } diff --git a/test/this/nested_class.wren b/test/this/nested_class.wren index f6abeff0..66c6f2c1 100644 --- a/test/this/nested_class.wren +++ b/test/this/nested_class.wren @@ -1,13 +1,13 @@ class Outer { method { - IO.print(this.toString) // expect: Outer + IO.print(this) // expect: Outer fn { - IO.print(this.toString) // expect: Outer + IO.print(this) // expect: Outer class Inner { method { - IO.print(this.toString) // expect: Inner + IO.print(this) // expect: Inner } toString { return "Inner" } } diff --git a/test/this/nested_closure.wren b/test/this/nested_closure.wren index 10b22fe6..2229994b 100644 --- a/test/this/nested_closure.wren +++ b/test/this/nested_closure.wren @@ -3,7 +3,7 @@ class Foo { return fn { return fn { return fn { - return this.toString + return toString } } }