feat: implement closures with upvalue support and statement/expression separation

Add closure objects that wrap functions with captured upvalues, enabling
first-class closures that close over local variables from enclosing scopes.
Introduce new bytecode instructions (CODE_CLOSURE, CODE_LOAD_UPVALUE,
CODE_STORE_UPVALUE, CODE_CLOSE_UPVALUE, CODE_RETURN) and a CompilerUpvalue
tracking system with a MAX_UPVALUES limit of 256. Refactor the grammar to
strictly separate statements from expressions, fixing a bug where block
expressions incorrectly popped locals while temporaries remained on the
stack. Rename internal functions (wrenCallFunction, wrenDebugDumpInstruction,
wrenDebugDumpStack) and add Upvalue allocation, closure creation, and
debug dump support for the new instructions.
This commit is contained in:
Bob Nystrom
2013-12-04 15:43:50 +00:00
parent 7e7a508096
commit 5b05c477c6
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