variable definition (operator ":=") is now required not optional

This commit is contained in:
Daniel Kang 2019-01-10 22:34:28 -08:00
parent 54bea46d19
commit d870ebf72e
20 changed files with 473 additions and 232 deletions

View file

@ -14,6 +14,42 @@ import (
func main() {
runFib(35)
runFibTailCall(35)
}
func runFibTailCall(n int) {
start := time.Now()
nativeResult := fibTC(n, 0)
nativeTime := time.Since(start)
input := `
fib := func(x, s) {
if x == 0 {
return s
} else if x == 1 {
return 1 + s
} else {
return fib(x-1, fib(x-2, s))
}
}
` + fmt.Sprintf("out = fib(%d, 0)", n)
parseTime, compileTime, runTime, result, err := runBench([]byte(input))
if err != nil {
panic(err)
}
if nativeResult != int(result.(*objects.Int).Value) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value)))
}
fmt.Println("-------------------------------------")
fmt.Printf("fibonacci(%d) = %d (tail-call)\n", n, nativeResult)
fmt.Println("-------------------------------------")
fmt.Printf("Go: %s\n", nativeTime)
fmt.Printf("Parser: %s\n", parseTime)
fmt.Printf("Compile: %s\n", compileTime)
fmt.Printf("VM: %s\n", runTime)
}
func runFib(n int) {
@ -42,7 +78,8 @@ fib := func(x) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value)))
}
fmt.Printf("fib(%d) = %d\n", n, nativeResult)
fmt.Println("-------------------------------------")
fmt.Printf("fibonacci(%d) = %d\n", n, nativeResult)
fmt.Println("-------------------------------------")
fmt.Printf("Go: %s\n", nativeTime)
fmt.Printf("Parser: %s\n", parseTime)
@ -60,6 +97,16 @@ func fib(n int) int {
}
}
func fibTC(n, s int) int {
if n == 0 {
return s
} else if n == 1 {
return 1 + s
} else {
return fibTC(n-1, fibTC(n-2, s))
}
}
func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration, runTime time.Duration, result objects.Object, err error) {
var astFile *ast.File
parseTime, astFile, err = parse(input)

View file

@ -42,10 +42,6 @@ func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error {
}
symbol = c.symbolTable.Define(ident)
} else if op == token.Assign {
if !exists {
symbol = c.symbolTable.Define(ident)
}
} else {
if !exists {
return fmt.Errorf("unresolved reference '%s'", ident)
@ -109,7 +105,11 @@ func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error {
if numSel > 0 {
c.emit(OpSetSelLocal, symbol.Index, numSel)
} else {
c.emit(OpSetLocal, symbol.Index)
if op == token.Define {
c.emit(OpDefineLocal, symbol.Index)
} else {
c.emit(OpSetLocal, symbol.Index)
}
}
case ScopeFree:
if numSel > 0 {

View file

@ -74,7 +74,7 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
// for-in statement is compiled like following:
//
// for :it = iterator(iterable); :it.next(); {
// for :it := iterator(iterable); :it.next(); {
// k, v := :it.get() // DEFINE operator
//
// ... body ...
@ -93,7 +93,7 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
if itSymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, itSymbol.Index)
} else {
c.emit(OpSetLocal, itSymbol.Index)
c.emit(OpDefineLocal, itSymbol.Index)
}
// pre-condition position
@ -126,7 +126,7 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
if keySymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, keySymbol.Index)
} else {
c.emit(OpSetLocal, keySymbol.Index)
c.emit(OpDefineLocal, keySymbol.Index)
}
}
@ -142,7 +142,7 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
if valueSymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, valueSymbol.Index)
} else {
c.emit(OpSetLocal, valueSymbol.Index)
c.emit(OpDefineLocal, valueSymbol.Index)
}
}

View file

@ -619,7 +619,7 @@ func TestCompiler_Compile(t *testing.T) {
intObject(55),
compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 0),
compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpReturnValue, 1)))))
@ -633,7 +633,7 @@ func TestCompiler_Compile(t *testing.T) {
intObject(23),
compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 0),
compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpConstant, 1),
compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0),
@ -649,9 +649,9 @@ func TestCompiler_Compile(t *testing.T) {
intObject(77),
compiledFunction(2, 0,
compiler.MakeInstruction(compiler.OpConstant, 0),
compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpConstant, 1),
compiler.MakeInstruction(compiler.OpSetLocal, 1),
compiler.MakeInstruction(compiler.OpDefineLocal, 1),
compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 1),
compiler.MakeInstruction(compiler.OpAdd),
@ -792,7 +792,7 @@ func() {
intObject(88),
compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 3),
compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
compiler.MakeInstruction(compiler.OpGetFree, 0),
compiler.MakeInstruction(compiler.OpAdd),
@ -803,14 +803,14 @@ func() {
compiler.MakeInstruction(compiler.OpReturnValue, 1)),
compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 2),
compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpGetFree, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpClosure, 4, 2),
compiler.MakeInstruction(compiler.OpReturnValue, 1)),
compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 1),
compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpClosure, 5, 1),
compiler.MakeInstruction(compiler.OpReturnValue, 1)))))

View file

@ -45,6 +45,7 @@ var definitions = map[Opcode]*Definition{
OpReturnValue: {Name: "RETVAL", Operands: []int{1}},
OpGetLocal: {Name: "GETL", Operands: []int{1}},
OpSetLocal: {Name: "SETL", Operands: []int{1}},
OpDefineLocal: {Name: "DEFL", Operands: []int{1}},
OpSetSelLocal: {Name: "SETSL", Operands: []int{1, 1}},
OpGetBuiltin: {Name: "BUILTIN", Operands: []int{1}},
OpClosure: {Name: "CLOSURE", Operands: []int{2, 1}},

View file

@ -30,9 +30,6 @@ const (
OpOrJump
OpJump
OpNull
OpGetGlobal
OpSetGlobal
OpSetSelGlobal
OpArray
OpMap
OpIndex
@ -40,14 +37,18 @@ const (
OpCall
OpReturn
OpReturnValue
OpGetGlobal
OpSetGlobal
OpSetSelGlobal
OpGetLocal
OpSetLocal
OpDefineLocal
OpSetSelLocal
OpGetBuiltin
OpClosure
OpGetFree
OpSetFree
OpSetSelFree
OpGetBuiltin
OpClosure
OpIteratorInit
OpIteratorNext
OpIteratorKey

View file

@ -11,34 +11,34 @@ type M map[string]interface{}
func TestCompiled_Get(t *testing.T) {
// simple script
c := compile(t, `a = 5`, nil)
c := compile(t, `a := 5`, nil)
compiledRun(t, c)
compiledGet(t, c, "a", int64(5))
// user-defined variables
compileError(t, `a = b`, nil) // compile error because "b" is not defined
c = compile(t, `a = b`, M{"b": "foo"}) // now compile with b = "foo" defined
compiledGet(t, c, "a", nil) // a = undefined; because it's before Compiled.Run()
compiledRun(t, c) // Compiled.Run()
compiledGet(t, c, "a", "foo") // a = "foo"
compileError(t, `a := b`, nil) // compile error because "b" is not defined
c = compile(t, `a := b`, M{"b": "foo"}) // now compile with b = "foo" defined
compiledGet(t, c, "a", nil) // a = undefined; because it's before Compiled.Run()
compiledRun(t, c) // Compiled.Run()
compiledGet(t, c, "a", "foo") // a = "foo"
}
func TestCompiled_GetAll(t *testing.T) {
c := compile(t, `a = 5`, nil)
c := compile(t, `a := 5`, nil)
compiledRun(t, c)
compiledGetAll(t, c, M{"a": int64(5)})
c = compile(t, `a = b`, M{"b": "foo"})
c = compile(t, `a := b`, M{"b": "foo"})
compiledRun(t, c)
compiledGetAll(t, c, M{"a": "foo", "b": "foo"})
c = compile(t, `a = b; b = 5`, M{"b": "foo"})
c = compile(t, `a := b; b = 5`, M{"b": "foo"})
compiledRun(t, c)
compiledGetAll(t, c, M{"a": "foo", "b": int64(5)})
}
func TestCompiled_IsDefined(t *testing.T) {
c := compile(t, `a = 5`, nil)
c := compile(t, `a := 5`, nil)
compiledIsDefined(t, c, "a", false) // a is not defined before Run()
compiledRun(t, c)
compiledIsDefined(t, c, "a", true)

View file

@ -8,7 +8,7 @@ import (
)
func TestScript_Add(t *testing.T) {
s := script.New([]byte(`a = b`))
s := script.New([]byte(`a := b`))
assert.NoError(t, s.Add("b", 5)) // b = 5
assert.NoError(t, s.Add("b", "foo")) // b = "foo" (re-define before compilation)
c, err := s.Compile()
@ -19,7 +19,7 @@ func TestScript_Add(t *testing.T) {
}
func TestScript_Remove(t *testing.T) {
s := script.New([]byte(`a = b`))
s := script.New([]byte(`a := b`))
err := s.Add("b", 5)
assert.NoError(t, err)
assert.True(t, s.Remove("b")) // b is removed

100
vm/vm.go
View file

@ -81,6 +81,7 @@ func (v *VM) Run() error {
if err := v.push(&v.constants[cidx]); err != nil {
return err
}
case compiler.OpNull:
if err := v.push(&undefinedObj); err != nil {
return err
@ -530,14 +531,29 @@ func (v *VM) Run() error {
if err := v.push(&undefinedObj); err != nil {
return err
}
case compiler.OpDefineLocal:
localIndex := compiler.ReadUint8(ins[ip+1:])
curFrame.ip += 1
sp := curFrame.basePointer + int(localIndex)
// local variables can be mutated by other actions
// so always store the copy of popped value
val := v.popValue()
v.stack[sp] = &val
case compiler.OpSetLocal:
localIndex := compiler.ReadUint8(ins[ip+1:])
curFrame.ip += 1
sp := curFrame.basePointer + int(localIndex)
val := v.pop()
v.stack[sp] = val
// update pointee of v.stack[sp] instead of replacing the pointer itself.
// this is needed because there can be free variables referencing the same local variables.
val := v.pop()
*v.stack[sp] = *val // also use a copy of popped value
case compiler.OpSetSelLocal:
localIndex := compiler.ReadUint8(ins[ip+1:])
numSelectors := int(compiler.ReadUint8(ins[ip+2:]))
@ -559,7 +575,7 @@ func (v *VM) Run() error {
}
// RHS value
val := v.pop()
val := v.pop() // no need to copy value here; selectorAssign uses copy of value
sp := curFrame.basePointer + int(localIndex)
@ -632,6 +648,7 @@ func (v *VM) Run() error {
val := v.pop()
*v.frames[v.framesIndex-1].freeVars[freeIndex] = *val
case compiler.OpIteratorInit:
var iterator objects.Object
@ -650,6 +667,7 @@ func (v *VM) Run() error {
if err := v.push(&iterator); err != nil {
return err
}
case compiler.OpIteratorNext:
iterator := v.pop()
b := (*iterator).(objects.Iterator).Next()
@ -716,6 +734,13 @@ func (v *VM) pop() *objects.Object {
return o
}
func (v *VM) popValue() objects.Object {
o := v.stack[v.sp-1]
v.sp--
return *o
}
func (v *VM) pushClosure(constIndex, numFree int) error {
c := v.constants[constIndex]
@ -853,6 +878,61 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
fn.NumParameters, numArgs)
}
// check if this is a tail-call (recursive call right before return)
curFrame := &(v.frames[v.framesIndex-1])
if fn == curFrame.fn {
nextOp := compiler.Opcode(curFrame.fn.Instructions[curFrame.ip+1])
if nextOp == compiler.OpReturnValue ||
(nextOp == compiler.OpPop &&
compiler.OpReturn == compiler.Opcode(curFrame.fn.Instructions[curFrame.ip+2])) {
// stack before tail-call
//
// |--------|
// | | <- SP current
// |--------|
// | *ARG2 | for next function (tail-call)
// |--------|
// | *ARG1 | for next function (tail-call)
// |--------|
// | LOCAL3 | for current function
// |--------|
// | LOCAL2 | for current function
// |--------|
// | ARG2 | for current function
// |--------|
// | ARG1 | <- BP for current function
// |--------|
for i := 0; i < numArgs; i++ {
v.stack[curFrame.basePointer+i] = v.stack[v.sp-numArgs+i]
}
v.sp -= numArgs
curFrame.ip = -1
// stack after tail-call
//
// |--------|
// | |
// |--------|
// | *ARG2 |
// |--------|
// | *ARG1 | <- SP current
// |--------|
// | LOCAL3 | for current function
// |--------|
// | LOCAL2 | for current function
// |--------|
// | *ARG2 | (copied)
// |--------|
// | *ARG1 | <- BP (copied)
// |--------|
return nil
}
}
v.frames[v.framesIndex].fn = fn
v.frames[v.framesIndex].freeVars = freeVars
v.frames[v.framesIndex].ip = -1
@ -861,6 +941,20 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
v.sp = v.sp - numArgs + fn.NumLocals
// stack after the function call
//
// |--------|
// | | <- SP after function call
// |--------|
// | LOCAL4 | (BP+3)
// |--------|
// | LOCAL3 | (BP+2) <- SP before function call
// |--------|
// | ARG2 | (BP+1)
// |--------|
// | ARG1 | (BP+0) <- BP
// |--------|
return nil
}

View file

@ -10,14 +10,14 @@ func TestArray(t *testing.T) {
expect(t, `out = [1, 2, 3][0]`, 1)
expect(t, `out = [1, 2, 3][1]`, 2)
expect(t, `out = [1, 2, 3][2]`, 3)
expect(t, `i = 0; out = [1, 2, 3][i]`, 1)
expect(t, `i := 0; out = [1, 2, 3][i]`, 1)
expect(t, `out = [1, 2, 3][1 + 1]`, 3)
expect(t, `arr = [1, 2, 3]; out = arr[2]`, 3)
expect(t, `arr = [1, 2, 3]; out = arr[0] + arr[1] + arr[2]`, 6)
expect(t, `arr = [1, 2, 3]; i = arr[0]; out = arr[i]`, 2)
expect(t, `arr := [1, 2, 3]; out = arr[2]`, 3)
expect(t, `arr := [1, 2, 3]; out = arr[0] + arr[1] + arr[2]`, 6)
expect(t, `arr := [1, 2, 3]; i := arr[0]; out = arr[i]`, 2)
expect(t, `out = [1, 2, 3][1+1]`, 3)
expect(t, `a = 1; out = [1, 2, 3][a+1]`, 3)
expect(t, `a := 1; out = [1, 2, 3][a+1]`, 3)
expect(t, `out = [1, 2, 3][:]`, ARR{1, 2, 3})
expect(t, `out = [1, 2, 3][0:3]`, ARR{1, 2, 3})
@ -27,10 +27,11 @@ func TestArray(t *testing.T) {
expect(t, `out = [1, 2, 3][1:1]`, ARR{})
expect(t, `out = [1, 2, 3][3-2:1+1]`, ARR{2})
expect(t, `a = 1; out = [1, 2, 3][a-1:a+1]`, ARR{1, 2})
expect(t, `a := 1; out = [1, 2, 3][a-1:a+1]`, ARR{1, 2})
// array copy-by-reference
expect(t, `a1 = [1, 2, 3]; a2 = a1; a1[0] = 5; out = a2`, ARR{5, 2, 3})
expect(t, `a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2`, ARR{5, 2, 3})
expect(t, `func () { a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2 }()`, ARR{5, 2, 3})
expectError(t, `[1, 2, 3][3]`)
expectError(t, `[1, 2, 3][-1]`)

View file

@ -8,18 +8,17 @@ func TestAssignment(t *testing.T) {
expect(t, `a := 1; a = 2; out = a`, 2)
expect(t, `a := 1; a = 2; out = a`, 2)
expect(t, `a := 1; a = a + 4; out = a`, 5)
expect(t, `a := 1; f1 = func() { a = 2; return a }; out = f1()`, 2)
expect(t, `a := 1; f1 = func() { a := 3; a = 2; return a }; out = f1()`, 2)
expect(t, `a := 1; f1 := func() { a = 2; return a }; out = f1()`, 2)
expect(t, `a := 1; f1 := func() { a := 3; a = 2; return a }; out = f1()`, 2)
// define operator ":=" is optional
expect(t, `a = 1; out = a`, 1)
expect(t, `a = 1; a = 2; out = a`, 2)
expect(t, `a = 1; func() { a = 2 }(); out = a`, 2)
expect(t, `a = 1; func() { a := 2 }(); out = a`, 1) // "a := 2" defines a new local variable 'a'
expect(t, `a = 1; func() { b = 2; out = b }()`, 2)
expect(t, `a := 1; out = a`, 1)
expect(t, `a := 1; a = 2; out = a`, 2)
expect(t, `a := 1; func() { a = 2 }(); out = a`, 2)
expect(t, `a := 1; func() { a := 2 }(); out = a`, 1) // "a := 2" defines a new local variable 'a'
expect(t, `a := 1; func() { b := 2; out = b }()`, 2)
expect(t, `
out = func() {
a = 2
a := 2
func() {
a = 3 // captured from outer scope
}()
@ -29,7 +28,7 @@ out = func() {
expect(t, `
func() {
a = 5
a := 5
out = func() {
a := 4
return a
@ -39,14 +38,14 @@ func() {
expectError(t, `a := 1; a := 2`) // redeclared in the same scope
expectError(t, `func() { a := 1; a := 2 }()`) // redeclared in the same scope
expect(t, `a = 1; a += 2; out = a`, 3)
expect(t, `a = 1; a += 4 - 2;; out = a`, 3)
expect(t, `a = 3; a -= 1;; out = a`, 2)
expect(t, `a = 3; a -= 5 - 4;; out = a`, 2)
expect(t, `a = 2; a *= 4;; out = a`, 8)
expect(t, `a = 2; a *= 1 + 3;; out = a`, 8)
expect(t, `a = 10; a /= 2;; out = a`, 5)
expect(t, `a = 10; a /= 5 - 3;; out = a`, 5)
expect(t, `a := 1; a += 2; out = a`, 3)
expect(t, `a := 1; a += 4 - 2;; out = a`, 3)
expect(t, `a := 3; a -= 1;; out = a`, 2)
expect(t, `a := 3; a -= 5 - 4;; out = a`, 2)
expect(t, `a := 2; a *= 4;; out = a`, 8)
expect(t, `a := 2; a *= 1 + 3;; out = a`, 8)
expect(t, `a := 10; a /= 2;; out = a`, 5)
expect(t, `a := 10; a /= 5 - 3;; out = a`, 5)
// +=, -=, *=, /= operator does not define new variable
expectError(t, `a += 4`)
@ -55,9 +54,9 @@ func() {
expectError(t, `a /= 4`)
expect(t, `
f1 = func() {
f2 = func() {
a = 1
f1 := func() {
f2 := func() {
a := 1
a += 2 // it's a statement, not an expression
return a
};
@ -66,20 +65,20 @@ f1 = func() {
};
out = f1();`, 3)
expect(t, `f1 = func() { f2 = func() { a = 1; a += 4 - 2; return a }; return f2(); }; out = f1()`, 3)
expect(t, `f1 = func() { f2 = func() { a = 3; a -= 1; return a }; return f2(); }; out = f1()`, 2)
expect(t, `f1 = func() { f2 = func() { a = 3; a -= 5 - 4; return a }; return f2(); }; out = f1()`, 2)
expect(t, `f1 = func() { f2 = func() { a = 2; a *= 4; return a }; return f2(); }; out = f1()`, 8)
expect(t, `f1 = func() { f2 = func() { a = 2; a *= 1 + 3; return a }; return f2(); }; out = f1()`, 8)
expect(t, `f1 = func() { f2 = func() { a = 10; a /= 2; return a }; return f2(); }; out = f1()`, 5)
expect(t, `f1 = func() { f2 = func() { a = 10; a /= 5 - 3; return a }; return f2(); }; out = f1()`, 5)
expect(t, `f1 := func() { f2 := func() { a := 1; a += 4 - 2; return a }; return f2(); }; out = f1()`, 3)
expect(t, `f1 := func() { f2 := func() { a := 3; a -= 1; return a }; return f2(); }; out = f1()`, 2)
expect(t, `f1 := func() { f2 := func() { a := 3; a -= 5 - 4; return a }; return f2(); }; out = f1()`, 2)
expect(t, `f1 := func() { f2 := func() { a := 2; a *= 4; return a }; return f2(); }; out = f1()`, 8)
expect(t, `f1 := func() { f2 := func() { a := 2; a *= 1 + 3; return a }; return f2(); }; out = f1()`, 8)
expect(t, `f1 := func() { f2 := func() { a := 10; a /= 2; return a }; return f2(); }; out = f1()`, 5)
expect(t, `f1 := func() { f2 := func() { a := 10; a /= 5 - 3; return a }; return f2(); }; out = f1()`, 5)
expect(t, `a = 1; f1 = func() { f2 = func() { a += 2; return a }; return f2(); }; out = f1()`, 3)
expect(t, `a := 1; f1 := func() { f2 := func() { a += 2; return a }; return f2(); }; out = f1()`, 3)
expect(t, `
f1 = func(a) {
f1 := func(a) {
return func(b) {
c = a
c := a
c += b * 2
return c
}
@ -90,7 +89,7 @@ out = f1();`, 3)
expect(t, `
out = func() {
a = 1
a := 1
func() {
a = 2
func() {
@ -106,8 +105,8 @@ out = f1();`, 3)
// write on free variables
expect(t, `
f1 = func() {
a = 5
f1 := func() {
a := 5
return func() {
a += 3
@ -118,14 +117,14 @@ out = f1();`, 3)
`, 8)
expect(t, `
it = func(seq, fn) {
it := func(seq, fn) {
fn(seq[0])
fn(seq[1])
fn(seq[2])
}
foo = func(a) {
b = 0
foo := func(a) {
b := 0
it([1, 2, 3], func(x) {
b = x + a
})
@ -136,14 +135,14 @@ out = f1();`, 3)
`, 5)
expect(t, `
it = func(seq, fn) {
it := func(seq, fn) {
fn(seq[0])
fn(seq[1])
fn(seq[2])
}
foo = func(a) {
b = 0
foo := func(a) {
b := 0
it([1, 2, 3], func(x) {
b += x + a
})
@ -155,7 +154,7 @@ out = f1();`, 3)
expect(t, `
out = func() {
a = 1
a := 1
func() {
a = 2
}()
@ -164,25 +163,25 @@ out = func() {
`, 2)
expect(t, `
f = func() {
a = 1
f := func() {
a := 1
return {
b: func() { a += 3 },
c: func() { a += 2 },
d: func() { return a }
}
}
m = f()
m := f()
m.b()
m.c()
out = m.d()
`, 6)
expect(t, `
each = func(s, x) { for i=0; i<len(s); i++ { x(s[i]) } }
each := func(s, x) { for i:=0; i<len(s); i++ { x(s[i]) } }
out = func() {
a = 100
a := 100
each([1, 2, 3], func(x) {
a += x
})
@ -194,11 +193,11 @@ out = func() {
`, 136)
// assigning different type value
expect(t, `a = 1; a = "foo"; out = a`, "foo") // global
expect(t, `func() { a = 1; a = "foo"; out = a }()`, "foo") // local
expect(t, `a := 1; a = "foo"; out = a`, "foo") // global
expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local
expect(t, `
out = func() {
a = 5
a := 5
return func() {
a = "foo"
return a
@ -206,18 +205,18 @@ out = func() {
}()`, "foo") // free
// variables declared in if/for blocks
expect(t, `for a:=0; a<5; a++ {}; a = "foo"; out = a`, "foo")
expect(t, `func() { for a:=0; a<5; a++ {}; a = "foo"; out = a }()`, "foo")
expect(t, `for a:=0; a<5; a++ {}; a := "foo"; out = a`, "foo")
expect(t, `func() { for a:=0; a<5; a++ {}; a := "foo"; out = a }()`, "foo")
// selectors
expect(t, `a=[1,2,3]; a[1] = 5; out = a[1]`, 5)
expect(t, `a=[1,2,3]; a[1] += 5; out = a[1]`, 7)
expect(t, `a={b:1,c:2}; a.b = 5; out = a.b`, 5)
expect(t, `a={b:1,c:2}; a.b += 5; out = a.b`, 6)
expect(t, `a={b:1,c:2}; a.b += a.c; out = a.b`, 3)
expect(t, `a={b:1,c:2}; a.b += a.c; out = a.c`, 2)
expect(t, `a:=[1,2,3]; a[1] = 5; out = a[1]`, 5)
expect(t, `a:=[1,2,3]; a[1] += 5; out = a[1]`, 7)
expect(t, `a:={b:1,c:2}; a.b = 5; out = a.b`, 5)
expect(t, `a:={b:1,c:2}; a.b += 5; out = a.b`, 6)
expect(t, `a:={b:1,c:2}; a.b += a.c; out = a.b`, 3)
expect(t, `a:={b:1,c:2}; a.b += a.c; out = a.c`, 2)
expect(t, `
a = {
a := {
b: [1, 2, 3],
c: {
d: 8,
@ -230,7 +229,7 @@ out = a["c"]["f"][1]
`, 10)
expect(t, `
a = {
a := {
b: [1, 2, 3],
c: {
d: 8,
@ -243,7 +242,7 @@ out = a.c.h
`, "bar")
expectError(t, `
a = {
a := {
b: [1, 2, 3],
c: {
d: 8,

View file

@ -3,9 +3,9 @@ package vm_test
import "testing"
func TestCall(t *testing.T) {
expect(t, `a = { b: func(x) { return x + 2 } }; out = a.b(5)`, 7)
expect(t, `a = { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, 7)
expect(t, `a = { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, 7)
expect(t, `a := { b: func(x) { return x + 2 } }; out = a.b(5)`, 7)
expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, 7)
expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, 7)
// "this" binding
// expect(t, `

View file

@ -24,21 +24,21 @@ func TestFor(t *testing.T) {
}()`, 5)
expect(t, `
for a=1; a<=10; a++ {
for a:=1; a<=10; a++ {
out += a
}`, 55)
expect(t, `
for a=1; a<=3; a++ {
for b=3; b<=6; b++ {
for a:=1; a<=3; a++ {
for b:=3; b<=6; b++ {
out += b
}
}`, 54)
expect(t, `
out = func() {
sum = 0
for a=1; a<=10; a++ {
sum := 0
for a:=1; a<=10; a++ {
sum += a
}
return sum
@ -46,17 +46,17 @@ func TestFor(t *testing.T) {
expect(t, `
out = func() {
sum = 0
for a=1; a<=4; a++ {
for b=3; b<=5; b++ {
sum := 0
for a:=1; a<=4; a++ {
for b:=3; b<=5; b++ {
sum += b
}
}
return sum
}()`, 48) // (3+4+5 * 4
}()`, 48) // (3+4+5) * 4
expect(t, `
a = 1
a := 1
for ; a<=10; a++ {
if a == 5 {
break
@ -65,7 +65,7 @@ func TestFor(t *testing.T) {
out = a`, 5)
expect(t, `
for a=1; a<=10; a++ {
for a:=1; a<=10; a++ {
if a == 3 {
continue
}
@ -76,9 +76,9 @@ func TestFor(t *testing.T) {
}`, 12) // 1 + 2 + 4 + 5
expect(t, `
for a=1; a<=10; {
for a:=1; a<=10; {
if a == 3 {
a++
a++
continue
}
out += a

View file

@ -6,20 +6,20 @@ import (
func TestFunction(t *testing.T) {
// function with no "return" statement returns "invalid" value.
expect(t, `f1 = func() {}; out = f1();`, undefined())
expect(t, `f1 = func() {}; f2 = func() { return f1(); }; f1(); out = f2();`, undefined())
expect(t, `f = func(x) { x; }; out = f(5);`, undefined())
expect(t, `f1 := func() {}; out = f1();`, undefined())
expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, undefined())
expect(t, `f := func(x) { x; }; out = f(5);`, undefined())
expect(t, `f = func(x) { return x; }; out = f(5);`, 5)
expect(t, `f = func(x) { return x * 2; }; out = f(5);`, 10)
expect(t, `f = func(x, y) { return x + y; }; out = f(5, 5);`, 10)
expect(t, `f = func(x, y) { return x + y; }; out = f(5 + 5, f(5, 5));`, 20)
expect(t, `f := func(x) { return x; }; out = f(5);`, 5)
expect(t, `f := func(x) { return x * 2; }; out = f(5);`, 10)
expect(t, `f := func(x, y) { return x + y; }; out = f(5, 5);`, 10)
expect(t, `f := func(x, y) { return x + y; }; out = f(5 + 5, f(5, 5));`, 20)
expect(t, `out = func(x) { return x; }(5)`, 5)
expect(t, `x = 10; f = func(x) { return x; }; f(5); out = x;`, 10)
expect(t, `x := 10; f := func(x) { return x; }; f(5); out = x;`, 10)
expect(t, `
f2 = func(a) {
f1 = func(a) {
f2 := func(a) {
f1 := func(a) {
return a * 2;
};
@ -31,93 +31,102 @@ out = f2(10);
// closures
expect(t, `
newAdder = func(x) {
newAdder := func(x) {
return func(y) { return x + y };
};
add2 = newAdder(2);
add2 := newAdder(2);
out = add2(5);
`, 7)
// function as a argument
expect(t, `
add = func(a, b) { return a + b };
sub = func(a, b) { return a - b };
applyFunc = func(a, b, f) { return f(a, b) };
add := func(a, b) { return a + b };
sub := func(a, b) { return a - b };
applyFunc := func(a, b, f) { return f(a, b) };
out = applyFunc(applyFunc(2, 2, add), 3, sub);
`, 1)
expect(t, `f1 = func() { return 5 + 10; }; out = f1();`, 15)
expect(t, `f1 = func() { return 1 }; f2 = func() { return 2 }; out = f1() + f2()`, 3)
expect(t, `f1 = func() { return 1 }; f2 = func() { return f1() + 2 }; f3 = func() { return f2() + 3 }; out = f3()`, 6)
expect(t, `f1 = func() { return 99; 100 }; out = f1();`, 99)
expect(t, `f1 = func() { return 99; return 100 }; out = f1();`, 99)
expect(t, `f1 = func() { return 33; }; f2 = func() { return f1 }; out = f2()();`, 33)
expect(t, `one = func() { one = 1; return one }; out = one()`, 1)
expect(t, `three = func() { one = 1; two = 2; return one + two }; out = three()`, 3)
expect(t, `three = func() { one = 1; two = 2; return one + two }; seven = func() { three = 3; four = 4; return three + four }; out = three() + seven()`, 10)
expect(t, `foo1 = func() { foo = 50; return foo}; foo2 = func() { foo = 100; return foo }; out = foo1() + foo2()`, 150)
expect(t, `f1 := func() { return 5 + 10; }; out = f1();`, 15)
expect(t, `f1 := func() { return 1 }; f2 := func() { return 2 }; out = f1() + f2()`, 3)
expect(t, `f1 := func() { return 1 }; f2 := func() { return f1() + 2 }; f3 := func() { return f2() + 3 }; out = f3()`, 6)
expect(t, `f1 := func() { return 99; 100 }; out = f1();`, 99)
expect(t, `f1 := func() { return 99; return 100 }; out = f1();`, 99)
expect(t, `f1 := func() { return 33; }; f2 := func() { return f1 }; out = f2()();`, 33)
expect(t, `one := func() { one = 1; return one }; out = one()`, 1)
expect(t, `three := func() { one := 1; two := 2; return one + two }; out = three()`, 3)
expect(t, `three := func() { one := 1; two := 2; return one + two }; seven := func() { three := 3; four := 4; return three + four }; out = three() + seven()`, 10)
expect(t, `
g = 50;
minusOne = func() {
n = 1;
foo1 := func() {
foo := 50
return foo
}
foo2 := func() {
foo := 100
return foo
}
out = foo1() + foo2()`, 150)
expect(t, `
g := 50;
minusOne := func() {
n := 1;
return g - n;
};
minusTwo = func() {
n = 2;
minusTwo := func() {
n := 2;
return g - n;
};
out = minusOne() + minusTwo()
`, 97)
expect(t, `
f1 = func() {
f2 = func() { return 1; }
f1 := func() {
f2 := func() { return 1; }
return f2
};
out = f1()()
`, 1)
expect(t, `
f1 = func(a) { return a; };
f1 := func(a) { return a; };
out = f1(4)`, 4)
expect(t, `
f1 = func(a, b) { return a + b; };
f1 := func(a, b) { return a + b; };
out = f1(1, 2)`, 3)
expect(t, `
sum = func(a, b) {
c = a + b;
sum := func(a, b) {
c := a + b;
return c;
};
out = sum(1, 2);`, 3)
expect(t, `
sum = func(a, b) {
c = a + b;
sum := func(a, b) {
c := a + b;
return c;
};
out = sum(1, 2) + sum(3, 4);`, 10)
expect(t, `
sum = func(a, b) {
c = a + b
sum := func(a, b) {
c := a + b
return c
};
outer = func() {
outer := func() {
return sum(1, 2) + sum(3, 4)
};
out = outer();`, 10)
expect(t, `
g = 10;
g := 10;
sum = func(a, b) {
c = a + b;
sum := func(a, b) {
c := a + b;
return c + g;
}
outer = func() {
outer := func() {
return sum(1, 2) + sum(3, 4) + g;
}
@ -129,65 +138,65 @@ out = outer() + g
expectError(t, `func(a, b) { return a + b; }(1)`)
expect(t, `
f1 = func(a) {
f1 := func(a) {
return func() { return a; };
};
f2 = f1(99);
f2 := f1(99);
out = f2()
`, 99)
expect(t, `
f1 = func(a, b) {
f1 := func(a, b) {
return func(c) { return a + b + c };
};
f2 = f1(1, 2);
f2 := f1(1, 2);
out = f2(8);
`, 11)
expect(t, `
f1 = func(a, b) {
c = a + b;
f1 := func(a, b) {
c := a + b;
return func(d) { return c + d };
};
f2 = f1(1, 2);
f2 := f1(1, 2);
out = f2(8);
`, 11)
expect(t, `
f1 = func(a, b) {
c = a + b;
f1 := func(a, b) {
c := a + b;
return func(d) {
e = d + c;
e := d + c;
return func(f) { return e + f };
}
};
f2 = f1(1, 2);
f3 = f2(3);
f2 := f1(1, 2);
f3 := f2(3);
out = f3(8);
`, 14)
expect(t, `
a = 1;
f1 = func(b) {
a := 1;
f1 := func(b) {
return func(c) {
return func(d) { return a + b + c + d }
};
};
f2 = f1(2);
f3 = f2(3);
f2 := f1(2);
f3 := f2(3);
out = f3(8);
`, 14)
expect(t, `
f1 = func(a, b) {
one = func() { return a; };
two = func() { return b; };
f1 := func(a, b) {
one := func() { return a; };
two := func() { return b; };
return func() { return one() + two(); }
};
f2 = f1(9, 90);
f2 := f1(9, 90);
out = f2();
`, 99)
// fibonacci
expect(t, `
fib = func(x) {
fib := func(x) {
if(x == 0) {
return 0;
} else {

View file

@ -26,11 +26,11 @@ func TestIf(t *testing.T) {
expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, 30)
expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { if (1 == 2) { out = 31 } else if (2 == 3) { out = 32 } else { out = 33 } }`, 33)
expect(t, `if a=0; a<1 { out = 10 }`, 10)
expect(t, `a=0; if a++; a==1 { out = 10 }`, 10)
expect(t, `if a:=0; a<1 { out = 10 }`, 10)
expect(t, `a:=0; if a++; a==1 { out = 10 }`, 10)
expect(t, `
func() {
a = 1
a := 1
if a++; a > 1 {
out = a
}
@ -38,7 +38,7 @@ func() {
`, 2)
expect(t, `
func() {
a = 1
a := 1
if a++; a == 1 {
out = 10
} else {
@ -48,7 +48,7 @@ func() {
`, 20)
expect(t, `
func() {
a = 1
a := 1
func() {
if a++; a > 1 {

View file

@ -30,12 +30,12 @@ func TestLogical(t *testing.T) {
expect(t, `out = 0 || (0 && 2)`, 0)
expect(t, `out = 0 || (2 && 0)`, 0)
expect(t, `t=func() {out = 3; return true}; f=func() {out = 7; return false}; t() && f()`, 7)
expect(t, `t=func() {out = 3; return true}; f=func() {out = 7; return false}; f() && t()`, 7)
expect(t, `t=func() {out = 3; return true}; f=func() {out = 7; return false}; f() || t()`, 3)
expect(t, `t=func() {out = 3; return true}; f=func() {out = 7; return false}; t() || f()`, 3)
expect(t, `t=func() {out = 3; return true}; f=func() {out = 7; return false}; !t() && f()`, 3)
expect(t, `t=func() {out = 3; return true}; f=func() {out = 7; return false}; !f() && t()`, 3)
expect(t, `t=func() {out = 3; return true}; f=func() {out = 7; return false}; !f() || t()`, 7)
expect(t, `t=func() {out = 3; return true}; f=func() {out = 7; return false}; !t() || f()`, 7)
expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() && f()`, 7)
expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() && t()`, 7)
expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() || t()`, 3)
expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() || f()`, 3)
expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() && f()`, 3)
expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() && t()`, 3)
expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() || t()`, 7)
expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() || f()`, 7)
}

View file

@ -18,11 +18,11 @@ out = {
expect(t, `out = {foo: 5}["foo"]`, 5)
expect(t, `out = {foo: 5}["bar"]`, undefined())
expect(t, `key = "foo"; out = {foo: 5}[key]`, 5)
expect(t, `key := "foo"; out = {foo: 5}[key]`, 5)
expect(t, `out = {}["foo"]`, undefined())
expect(t, `
m = {
m := {
foo: func(x) {
return x * 2
}
@ -31,6 +31,8 @@ out = m["foo"](2) + m["foo"](3)
`, 10)
// map assignment is copy-by-reference
expect(t, `m1 = {k1: 1, k2: "foo"}; m2 = m1; m1.k1 = 5; out = m2.k1`, 5)
expect(t, `m1 = {k1: 1, k2: "foo"}; m2 = m1; m2.k1 = 3; out = m1.k1`, 3)
expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1`, 5)
expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1`, 3)
expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1 }()`, 5)
expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1 }()`, 3)
}

View file

@ -20,5 +20,5 @@ func TestReturn(t *testing.T) {
}
}()`, 10)
expect(t, `f1 = func() { return 2 * 5; }; out = f1()`, 10)
expect(t, `f1 := func() { return 2 * 5; }; out = f1()`, 10)
}

View file

@ -5,12 +5,12 @@ import (
)
func TestSelector(t *testing.T) {
expect(t, `a = {k1: 5, k2: "foo"}; out = a.k1`, 5)
expect(t, `a = {k1: 5, k2: "foo"}; out = a.k2`, "foo")
expect(t, `a = {k1: 5, k2: "foo"}; out = a.k3`, undefined())
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k1`, 5)
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, "foo")
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, undefined())
expect(t, `
a = {
a := {
b: {
c: 4,
a: false
@ -20,7 +20,7 @@ a = {
out = a.b.c`, 4)
expectError(t, `
a = {
a := {
b: {
c: 4,
a: false
@ -30,7 +30,7 @@ a = {
out = a.x.c`)
expectError(t, `
a = {
a := {
b: {
c: 4,
a: false
@ -39,26 +39,26 @@ a = {
}
out = a.x.y`)
expect(t, `a = {b: 1, c: "foo"}; a.b = 2; out = a.b`, 2)
expect(t, `a = {b: 1, c: "foo"}; a.c = 2; out = a.c`, 2) // type not checked on sub-field
expect(t, `a = {b: {c: 1}}; a.b.c = 2; out = a.b.c`, 2)
expect(t, `a = {b: 1}; a.c = 2; out = a`, MAP{"b": 1, "c": 2})
expect(t, `a = {b: {c: 1}}; a.b.d = 2; out = a`, MAP{"b": MAP{"c": 1, "d": 2}})
expect(t, `a := {b: 1, c: "foo"}; a.b = 2; out = a.b`, 2)
expect(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, 2) // type not checked on sub-field
expect(t, `a := {b: {c: 1}}; a.b.c = 2; out = a.b.c`, 2)
expect(t, `a := {b: 1}; a.c = 2; out = a`, MAP{"b": 1, "c": 2})
expect(t, `a := {b: {c: 1}}; a.b.d = 2; out = a`, MAP{"b": MAP{"c": 1, "d": 2}})
expect(t, `func() { a = {b: 1, c: "foo"}; a.b = 2; out = a.b }()`, 2)
expect(t, `func() { a = {b: 1, c: "foo"}; a.c = 2; out = a.c }()`, 2) // type not checked on sub-field
expect(t, `func() { a = {b: {c: 1}}; a.b.c = 2; out = a.b.c }()`, 2)
expect(t, `func() { a = {b: 1}; a.c = 2; out = a }()`, MAP{"b": 1, "c": 2})
expect(t, `func() { a = {b: {c: 1}}; a.b.d = 2; out = a }()`, MAP{"b": MAP{"c": 1, "d": 2}})
expect(t, `func() { a := {b: 1, c: "foo"}; a.b = 2; out = a.b }()`, 2)
expect(t, `func() { a := {b: 1, c: "foo"}; a.c = 2; out = a.c }()`, 2) // type not checked on sub-field
expect(t, `func() { a := {b: {c: 1}}; a.b.c = 2; out = a.b.c }()`, 2)
expect(t, `func() { a := {b: 1}; a.c = 2; out = a }()`, MAP{"b": 1, "c": 2})
expect(t, `func() { a := {b: {c: 1}}; a.b.d = 2; out = a }()`, MAP{"b": MAP{"c": 1, "d": 2}})
expect(t, `func() { a = {b: 1, c: "foo"}; func() { a.b = 2 }(); out = a.b }()`, 2)
expect(t, `func() { a = {b: 1, c: "foo"}; func() { a.c = 2 }(); out = a.c }()`, 2) // type not checked on sub-field
expect(t, `func() { a = {b: {c: 1}}; func() { a.b.c = 2 }(); out = a.b.c }()`, 2)
expect(t, `func() { a = {b: 1}; func() { a.c = 2 }(); out = a }()`, MAP{"b": 1, "c": 2})
expect(t, `func() { a = {b: {c: 1}}; func() { a.b.d = 2 }(); out = a }()`, MAP{"b": MAP{"c": 1, "d": 2}})
expect(t, `func() { a := {b: 1, c: "foo"}; func() { a.b = 2 }(); out = a.b }()`, 2)
expect(t, `func() { a := {b: 1, c: "foo"}; func() { a.c = 2 }(); out = a.c }()`, 2) // type not checked on sub-field
expect(t, `func() { a := {b: {c: 1}}; func() { a.b.c = 2 }(); out = a.b.c }()`, 2)
expect(t, `func() { a := {b: 1}; func() { a.c = 2 }(); out = a }()`, MAP{"b": 1, "c": 2})
expect(t, `func() { a := {b: {c: 1}}; func() { a.b.d = 2 }(); out = a }()`, MAP{"b": MAP{"c": 1, "d": 2}})
expect(t, `
a = {
a := {
b: [1, 2, 3],
c: {
d: 8,
@ -69,10 +69,20 @@ a = {
out = [a.b[2], a.c.d, a.c.e, a.c.f[1]]
`, ARR{3, 8, "foo", 8})
expectError(t, `a = {b: {c: 1}}; a.d.c = 2`)
expectError(t, `a = [1, 2, 3]; a.b = 2`)
expectError(t, `a = "foo"; a.b = 2`)
expectError(t, `func() { a = {b: {c: 1}}; a.d.c = 2 }()`)
expectError(t, `func() { a = [1, 2, 3]; a.b = 2 }()`)
expectError(t, `func() { a = "foo"; a.b = 2 }()`)
expect(t, `
func() {
a := [1, 2, 3]
b := 9
a[1] = b
b = 7 // make sure a[1] has a COPY of value of 'b'
out = a[1]
}()
`, 9)
expectError(t, `a := {b: {c: 1}}; a.d.c = 2`)
expectError(t, `a := [1, 2, 3]; a.b = 2`)
expectError(t, `a := "foo"; a.b = 2`)
expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`)
expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`)
expectError(t, `func() { a := "foo"; a.b = 2 }()`)
}

77
vm/vm_tail_call_test.go Normal file
View file

@ -0,0 +1,77 @@
package vm_test
import "testing"
func TestTailCall(t *testing.T) {
expect(t, `
fac := func(n, a) {
if n == 1 {
return a
}
return fac(n-1, n*a)
}
out = fac(5, 1)`, 120)
expect(t, `
fac := func(n, a) {
if n == 1 {
return a
}
x := {foo: fac} // indirection for test
return x.foo(n-1, n*a)
}
out = fac(5, 1)`, 120)
expect(t, `
fib := func(x, s) {
if x == 0 {
return 0 + s
} else if x == 1 {
return 1 + s
}
return fib(x-1, fib(x-2, s))
}
out = fib(15, 0)`, 610)
// global variable and no return value
expect(t, `
out = 0
foo := func(a) {
if a == 0 {
return
}
out += a
foo(a-1)
}
foo(10)`, 55)
// TODO: see comment inside the code
expect(t, `
f1 := func() {
f2 := undefined // TODO: this is really inconvenient
f2 = func(n, s) {
if n == 0 { return s }
return f2(n-1, n + s)
}
return f2(5, 0)
}
out = f1()`, 15)
// tail call with free vars
// expect(t, `
//f1 := func() {
// a := 10
// f2 := undefined
// f2 = func(n, s) {
// a += s
// if n == 0 {
// return
// }
// f2(n-1, n + s)
// }
// return f2
//}
//out = f1()(5, 0)`, 25)
}