Get closures working!

In the process, I had to change the grammar. There is now a strong
separation between statements and expressions. The code was just wrong
before when it popped locals at the end of a block scope because there
could be temporaries on the stack if the block was in expression
position. This fixes that.

Still need to implement closing over `this`.
This commit is contained in:
Bob Nystrom
2013-12-04 07:43:50 -08:00
parent c14b115c02
commit 157944aa27
32 changed files with 1063 additions and 416 deletions
+25
View File
@@ -0,0 +1,25 @@
var f = null
var g = null
{
var local = "local"
f = fn {
io.write(local)
local = "after f"
io.write(local)
}
g = fn {
io.write(local)
local = "after g"
io.write(local)
}
}
f.call
// expect: local
// expect: after f
g.call
// expect: after f
// expect: after g
@@ -0,0 +1,9 @@
var f = null
fn(param) {
f = fn {
io.write(param)
}
}.call("param")
f.call // expect: param
@@ -0,0 +1,12 @@
var f = null
class Foo {
method(param) {
f = fn {
io.write(param)
}
}
}
Foo.new.method("param")
f.call // expect: param
@@ -0,0 +1,10 @@
var f = null
{
var local = "local"
f = fn {
io.write(local)
}
}
f.call // expect: local
@@ -0,0 +1,14 @@
var foo = null
{
var local = "local"
class Foo {
method {
io.write(local)
}
}
foo = Foo.new
}
foo.method // expect: local
+21
View File
@@ -0,0 +1,21 @@
var f = null
fn {
var a = "a"
fn {
var b = "b"
fn {
var c = "c"
f = fn {
io.write(a)
io.write(b)
io.write(c)
}
}.call
}.call
}.call
f.call
// expect: a
// expect: b
// expect: c
@@ -0,0 +1,6 @@
{
var local = "local"
fn {
io.write(local) // expect: local
}.call
}
+10
View File
@@ -0,0 +1,10 @@
{
var local = "local"
class Foo {
method {
io.write(local)
}
}
Foo.new.method // expect: local
}
@@ -0,0 +1,13 @@
var f = null
{
var a = "a"
f = fn {
io.write(a)
io.write(a)
}
}
f.call
// expect: a
// expect: a
+19
View File
@@ -0,0 +1,19 @@
{
var f = null
{
var a = "a"
f = fn io.write(a)
}
{
// Since a is out of scope, the local slot will be reused by b. Make sure
// that f still closes over a.
var b = "b"
f.call // expect: a
}
}
// TODO(bob): Closing over this.
// TODO(bob): Close over fn/method parameter.
// TODO(bob): Maximum number of closed-over variables (directly and/or indirect).
+6
View File
@@ -0,0 +1,6 @@
var global = "global"
// TODO(bob): Forward reference to global declared after use.
fn {
io.write(global) // expect: global
}.call
+15
View File
@@ -0,0 +1,15 @@
var global = "global"
// TODO(bob): Forward reference to global declared after use.
class Foo {
method {
io.write(global)
}
static classMethod {
io.write(global)
}
}
Foo.new.method // expect: global
Foo.classMethod // expect: global
+1 -1
View File
@@ -3,7 +3,7 @@ class Foo {
this baz { io.write("this baz") }
this bar(arg) { io.write("this bar " + arg) }
toString { "Foo" }
toString { return "Foo" }
}
// Different names.
+1 -1
View File
@@ -4,7 +4,7 @@ class Foo {
this new(a) { io.write(a) }
this new(a, b) { io.write(a + b) }
toString { "Foo" }
toString { return "Foo" }
}
// Can overload by arity.
+1
View File
@@ -27,3 +27,4 @@ foo.write
// TODO(bob): Inherited fields.
// TODO(bob): Trying to get or set a field outside of a class.
// TODO(bob): Fields in nested classes.
// TODO(bob): Closing over fields.
+2 -25
View File
@@ -6,27 +6,12 @@ if (false) io.write("bad")
if (true) io.write("good") else io.write("bad") // expect: good
if (false) io.write("bad") else io.write("good") // expect: good
// Allow statements for then branch.
// Allow blocks for branches.
if (true) { io.write("block") } // expect: block
if (true) if (true) io.write("double") // expect: double
// Allow statements for else branch.
if (false) null else { io.write("block") } // expect: block
if (false) null else if (true) io.write("double") // expect: double
// Return the 'then' expression if the condition is true.
var a = if (true) "good"
io.write(a) // expect: good
// Return null if the condition is false and there is no else.
var b = if (false) "bad"
io.write(b) // expect: null
// Return the 'else' expression if the condition is false.
var c = if (false) "bad" else "good"
io.write(c) // expect: good
// Assignment in if condition.
var a = false
if (a = true) io.write(a) // expect: true
// Newline after "if".
@@ -37,14 +22,6 @@ if
if (false) io.write("bad") else
io.write("good") // expect: good
// Definition in then arm.
if (true) var a = io.write("ok") // expect: ok
if (true) class Foo {} // no error
// Definition in else arm.
if (false) null else var a = io.write("ok") // expect: ok
if (true) null else class Foo {} // no error
// Only false is falsy.
if (0) io.write(0) // expect: 0
if (null) io.write(null) // expect: null
+17 -17
View File
@@ -1,21 +1,21 @@
class Foo {
method { 0 }
method(a) { a }
method(a, b) { a + b }
method(a, b, c) { a + b + c }
method(a, b, c, d) { a + b + c + d }
method(a, b, c, d, e) { a + b + c + d + e }
method(a, b, c, d, e, f) { a + b + c + d + e + f }
method(a, b, c, d, e, f, g) { a + b + c + d + e + f + g }
method(a, b, c, d, e, f, g, h) { a + b + c + d + e + f + g + h }
method(a, b, c, d, e, f, g, h, i) { a + b + c + d + e + f + g + h + i }
method(a, b, c, d, e, f, g, h, i, j) { a + b + c + d + e + f + g + h + i + j }
method(a, b, c, d, e, f, g, h, i, j, k) { a + b + c + d + e + f + g + h + i + j + k}
method(a, b, c, d, e, f, g, h, i, j, k, l) { a + b + c + d + e + f + g + h + i + j + k + l}
method(a, b, c, d, e, f, g, h, i, j, k, l, m) { a + b + c + d + e + f + g + h + i + j + k + l + m}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n) { a + b + c + d + e + f + g + h + i + j + k + l + m + n}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) { a + b + c + d + e + f + g + h + i + j + k + l + m + n + o}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p}
method { return 0 }
method(a) { return a }
method(a, b) { return a + b }
method(a, b, c) { return a + b + c }
method(a, b, c, d) { return a + b + c + d }
method(a, b, c, d, e) { return a + b + c + d + e }
method(a, b, c, d, e, f) { return a + b + c + d + e + f }
method(a, b, c, d, e, f, g) { return a + b + c + d + e + f + g }
method(a, b, c, d, e, f, g, h) { return a + b + c + d + e + f + g + h }
method(a, b, c, d, e, f, g, h, i) { return a + b + c + d + e + f + g + h + i }
method(a, b, c, d, e, f, g, h, i, j) { return a + b + c + d + e + f + g + h + i + j }
method(a, b, c, d, e, f, g, h, i, j, k) { return a + b + c + d + e + f + g + h + i + j + k}
method(a, b, c, d, e, f, g, h, i, j, k, l) { return a + b + c + d + e + f + g + h + i + j + k + l}
method(a, b, c, d, e, f, g, h, i, j, k, l, m) { return a + b + c + d + e + f + g + h + i + j + k + l + m}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n) { return a + b + c + d + e + f + g + h + i + j + k + l + m + n}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) { return a + b + c + d + e + f + g + h + i + j + k + l + m + n + o}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { return a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p}
}
var foo = Foo.new
+13 -13
View File
@@ -1,18 +1,18 @@
class Foo {
+ other { "infix + " + other }
- other { "infix - " + other }
* other { "infix * " + other }
/ other { "infix / " + other }
% other { "infix % " + other }
< other { "infix < " + other }
> other { "infix > " + other }
<= other { "infix <= " + other }
>= other { "infix >= " + other }
== other { "infix == " + other }
!= other { "infix != " + other }
+ other { return "infix + " + other }
- other { return "infix - " + other }
* other { return "infix * " + other }
/ other { return "infix / " + other }
% other { return "infix % " + other }
< other { return "infix < " + other }
> other { return "infix > " + other }
<= other { return "infix <= " + other }
>= other { return "infix >= " + other }
== other { return "infix == " + other }
!= other { return "infix != " + other }
! { "prefix !" }
- { "prefix -" }
! { return "prefix !" }
- { return "prefix -" }
}
var foo = Foo.new
+4 -4
View File
@@ -1,9 +1,9 @@
class Foo {
bar { "on instance" }
static bar { "on metaclass" }
bar { return "on instance" }
static bar { return "on metaclass" }
bar(arg) { "on instance " + arg }
static bar(arg) { "on metaclass " + arg }
bar(arg) { return "on instance " + arg }
static bar(arg) { return "on metaclass " + arg }
}
io.write("on metaclass " + "arg") // expect: on metaclass arg
+3 -3
View File
@@ -1,6 +1,6 @@
class Foo {
bar { this }
baz { "baz" }
bar { return this }
baz { return "baz" }
}
Foo.new.bar.baz
io.write(Foo.new.bar.baz) // expect: baz
+6 -2
View File
@@ -1,9 +1,13 @@
// Create a local scope for the 'then' expression.
var a = "out"
if (true) var a = "in"
if (true) {
var a = "in"
}
io.write(a) // expect: out
// Create a local scope for the 'else' expression.
var b = "out"
if (false) "dummy" else var b = "in"
if (false) "dummy" else {
var b = "in"
}
io.write(b) // expect: out
+3 -3
View File
@@ -1,7 +1,7 @@
// Body has its own scope.
var a = "outer"
var i = 0
while ((i = i + 1) <= 1) var a = "inner"
while ((i = i + 1) <= 1) {
var a = "inner"
}
io.write(a) // expect: outer
// TODO(bob): What about condition?
+2 -13
View File
@@ -8,8 +8,8 @@ while (c < 3) io.write(c = c + 1)
// Block body.
var a = 0
while (a < 3) {
io.write(a)
a = a + 1
io.write(a)
a = a + 1
}
// expect: 0
// expect: 1
@@ -22,14 +22,3 @@ while
// expect: 1
// expect: 2
// expect: 3
// Result is null.
var e = 0
var f = while (e < 3) {
e = e + 1
}
io.write(f) // expect: null
// Definition body.
while (false) var a = "ok" // no error
while (false) class Foo {} // no error