From d870ebf72e89ec41e6444febc977132bcba0afc4 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Thu, 10 Jan 2019 22:34:28 -0800 Subject: [PATCH] variable definition (operator ":=") is now required not optional --- cmd/bench/main.go | 49 ++++++++++++- compiler/compiler_assign.go | 10 +-- compiler/compiler_for.go | 8 +- compiler/compiler_test.go | 14 ++-- compiler/definitions.go | 1 + compiler/opcodes.go | 11 +-- script/compiled_test.go | 20 ++--- script/script_test.go | 4 +- vm/vm.go | 100 ++++++++++++++++++++++++- vm/vm_array_test.go | 15 ++-- vm/vm_assignment_test.go | 119 +++++++++++++++--------------- vm/vm_call_test.go | 6 +- vm/vm_for_test.go | 26 +++---- vm/vm_function_test.go | 141 +++++++++++++++++++----------------- vm/vm_if_test.go | 10 +-- vm/vm_logic_test.go | 16 ++-- vm/vm_map_test.go | 10 ++- vm/vm_return_test.go | 2 +- vm/vm_selector_test.go | 66 ++++++++++------- vm/vm_tail_call_test.go | 77 ++++++++++++++++++++ 20 files changed, 473 insertions(+), 232 deletions(-) create mode 100644 vm/vm_tail_call_test.go diff --git a/cmd/bench/main.go b/cmd/bench/main.go index 6787b1a..21eee96 100644 --- a/cmd/bench/main.go +++ b/cmd/bench/main.go @@ -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) diff --git a/compiler/compiler_assign.go b/compiler/compiler_assign.go index ffb4a8d..1c211f5 100644 --- a/compiler/compiler_assign.go +++ b/compiler/compiler_assign.go @@ -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 { diff --git a/compiler/compiler_for.go b/compiler/compiler_for.go index 890cbf6..da04799 100644 --- a/compiler/compiler_for.go +++ b/compiler/compiler_for.go @@ -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) } } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index ddf289f..be21604 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -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))))) diff --git a/compiler/definitions.go b/compiler/definitions.go index 75abc03..1b1cd80 100644 --- a/compiler/definitions.go +++ b/compiler/definitions.go @@ -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}}, diff --git a/compiler/opcodes.go b/compiler/opcodes.go index 15f5200..5d97042 100644 --- a/compiler/opcodes.go +++ b/compiler/opcodes.go @@ -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 diff --git a/script/compiled_test.go b/script/compiled_test.go index f62d002..e112dc0 100644 --- a/script/compiled_test.go +++ b/script/compiled_test.go @@ -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) diff --git a/script/script_test.go b/script/script_test.go index feba4da..54bf936 100644 --- a/script/script_test.go +++ b/script/script_test.go @@ -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 diff --git a/vm/vm.go b/vm/vm.go index a51ba0f..d92df6d 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -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 } diff --git a/vm/vm_array_test.go b/vm/vm_array_test.go index 2154e6d..3163560 100644 --- a/vm/vm_array_test.go +++ b/vm/vm_array_test.go @@ -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]`) diff --git a/vm/vm_assignment_test.go b/vm/vm_assignment_test.go index 86bee53..a2e0bbd 100644 --- a/vm/vm_assignment_test.go +++ b/vm/vm_assignment_test.go @@ -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 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 { diff --git a/vm/vm_logic_test.go b/vm/vm_logic_test.go index 01bc2d2..c80af98 100644 --- a/vm/vm_logic_test.go +++ b/vm/vm_logic_test.go @@ -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) } diff --git a/vm/vm_map_test.go b/vm/vm_map_test.go index e84d75b..e76e394 100644 --- a/vm/vm_map_test.go +++ b/vm/vm_map_test.go @@ -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) } diff --git a/vm/vm_return_test.go b/vm/vm_return_test.go index 6445c35..133e33e 100644 --- a/vm/vm_return_test.go +++ b/vm/vm_return_test.go @@ -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) } diff --git a/vm/vm_selector_test.go b/vm/vm_selector_test.go index da122f7..c87a206 100644 --- a/vm/vm_selector_test.go +++ b/vm/vm_selector_test.go @@ -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 }()`) } diff --git a/vm/vm_tail_call_test.go b/vm/vm_tail_call_test.go new file mode 100644 index 0000000..5abbc1d --- /dev/null +++ b/vm/vm_tail_call_test.go @@ -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) +}