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() { func main() {
runFib(35) 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) { func runFib(n int) {
@ -42,7 +78,8 @@ fib := func(x) {
panic(fmt.Errorf("wrong result: %d != %d", nativeResult, int(result.(*objects.Int).Value))) 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.Println("-------------------------------------")
fmt.Printf("Go: %s\n", nativeTime) fmt.Printf("Go: %s\n", nativeTime)
fmt.Printf("Parser: %s\n", parseTime) 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) { func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration, runTime time.Duration, result objects.Object, err error) {
var astFile *ast.File var astFile *ast.File
parseTime, astFile, err = parse(input) 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) symbol = c.symbolTable.Define(ident)
} else if op == token.Assign {
if !exists {
symbol = c.symbolTable.Define(ident)
}
} else { } else {
if !exists { if !exists {
return fmt.Errorf("unresolved reference '%s'", ident) 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 { if numSel > 0 {
c.emit(OpSetSelLocal, symbol.Index, numSel) c.emit(OpSetSelLocal, symbol.Index, numSel)
} else { } else {
c.emit(OpSetLocal, symbol.Index) if op == token.Define {
c.emit(OpDefineLocal, symbol.Index)
} else {
c.emit(OpSetLocal, symbol.Index)
}
} }
case ScopeFree: case ScopeFree:
if numSel > 0 { 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-in statement is compiled like following:
// //
// for :it = iterator(iterable); :it.next(); { // for :it := iterator(iterable); :it.next(); {
// k, v := :it.get() // DEFINE operator // k, v := :it.get() // DEFINE operator
// //
// ... body ... // ... body ...
@ -93,7 +93,7 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
if itSymbol.Scope == ScopeGlobal { if itSymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, itSymbol.Index) c.emit(OpSetGlobal, itSymbol.Index)
} else { } else {
c.emit(OpSetLocal, itSymbol.Index) c.emit(OpDefineLocal, itSymbol.Index)
} }
// pre-condition position // pre-condition position
@ -126,7 +126,7 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
if keySymbol.Scope == ScopeGlobal { if keySymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, keySymbol.Index) c.emit(OpSetGlobal, keySymbol.Index)
} else { } 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 { if valueSymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, valueSymbol.Index) c.emit(OpSetGlobal, valueSymbol.Index)
} else { } 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), intObject(55),
compiledFunction(1, 0, compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 0), compiler.MakeInstruction(compiler.OpConstant, 0),
compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0), compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpReturnValue, 1))))) compiler.MakeInstruction(compiler.OpReturnValue, 1)))))
@ -633,7 +633,7 @@ func TestCompiler_Compile(t *testing.T) {
intObject(23), intObject(23),
compiledFunction(1, 0, compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 0), compiler.MakeInstruction(compiler.OpConstant, 0),
compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpConstant, 1), compiler.MakeInstruction(compiler.OpConstant, 1),
compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0), compiler.MakeInstruction(compiler.OpGetLocal, 0),
@ -649,9 +649,9 @@ func TestCompiler_Compile(t *testing.T) {
intObject(77), intObject(77),
compiledFunction(2, 0, compiledFunction(2, 0,
compiler.MakeInstruction(compiler.OpConstant, 0), compiler.MakeInstruction(compiler.OpConstant, 0),
compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpConstant, 1), compiler.MakeInstruction(compiler.OpConstant, 1),
compiler.MakeInstruction(compiler.OpSetLocal, 1), compiler.MakeInstruction(compiler.OpDefineLocal, 1),
compiler.MakeInstruction(compiler.OpGetLocal, 0), compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 1), compiler.MakeInstruction(compiler.OpGetLocal, 1),
compiler.MakeInstruction(compiler.OpAdd), compiler.MakeInstruction(compiler.OpAdd),
@ -792,7 +792,7 @@ func() {
intObject(88), intObject(88),
compiledFunction(1, 0, compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 3), compiler.MakeInstruction(compiler.OpConstant, 3),
compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpGetGlobal, 0), compiler.MakeInstruction(compiler.OpGetGlobal, 0),
compiler.MakeInstruction(compiler.OpGetFree, 0), compiler.MakeInstruction(compiler.OpGetFree, 0),
compiler.MakeInstruction(compiler.OpAdd), compiler.MakeInstruction(compiler.OpAdd),
@ -803,14 +803,14 @@ func() {
compiler.MakeInstruction(compiler.OpReturnValue, 1)), compiler.MakeInstruction(compiler.OpReturnValue, 1)),
compiledFunction(1, 0, compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 2), compiler.MakeInstruction(compiler.OpConstant, 2),
compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpGetFree, 0), compiler.MakeInstruction(compiler.OpGetFree, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0), compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpClosure, 4, 2), compiler.MakeInstruction(compiler.OpClosure, 4, 2),
compiler.MakeInstruction(compiler.OpReturnValue, 1)), compiler.MakeInstruction(compiler.OpReturnValue, 1)),
compiledFunction(1, 0, compiledFunction(1, 0,
compiler.MakeInstruction(compiler.OpConstant, 1), compiler.MakeInstruction(compiler.OpConstant, 1),
compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpDefineLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0), compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpClosure, 5, 1), compiler.MakeInstruction(compiler.OpClosure, 5, 1),
compiler.MakeInstruction(compiler.OpReturnValue, 1))))) compiler.MakeInstruction(compiler.OpReturnValue, 1)))))

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ import (
) )
func TestScript_Add(t *testing.T) { 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", 5)) // b = 5
assert.NoError(t, s.Add("b", "foo")) // b = "foo" (re-define before compilation) assert.NoError(t, s.Add("b", "foo")) // b = "foo" (re-define before compilation)
c, err := s.Compile() c, err := s.Compile()
@ -19,7 +19,7 @@ func TestScript_Add(t *testing.T) {
} }
func TestScript_Remove(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) err := s.Add("b", 5)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, s.Remove("b")) // b is removed 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 { if err := v.push(&v.constants[cidx]); err != nil {
return err return err
} }
case compiler.OpNull: case compiler.OpNull:
if err := v.push(&undefinedObj); err != nil { if err := v.push(&undefinedObj); err != nil {
return err return err
@ -530,14 +531,29 @@ func (v *VM) Run() error {
if err := v.push(&undefinedObj); err != nil { if err := v.push(&undefinedObj); err != nil {
return err 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: case compiler.OpSetLocal:
localIndex := compiler.ReadUint8(ins[ip+1:]) localIndex := compiler.ReadUint8(ins[ip+1:])
curFrame.ip += 1 curFrame.ip += 1
sp := curFrame.basePointer + int(localIndex) 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: case compiler.OpSetSelLocal:
localIndex := compiler.ReadUint8(ins[ip+1:]) localIndex := compiler.ReadUint8(ins[ip+1:])
numSelectors := int(compiler.ReadUint8(ins[ip+2:])) numSelectors := int(compiler.ReadUint8(ins[ip+2:]))
@ -559,7 +575,7 @@ func (v *VM) Run() error {
} }
// RHS value // RHS value
val := v.pop() val := v.pop() // no need to copy value here; selectorAssign uses copy of value
sp := curFrame.basePointer + int(localIndex) sp := curFrame.basePointer + int(localIndex)
@ -632,6 +648,7 @@ func (v *VM) Run() error {
val := v.pop() val := v.pop()
*v.frames[v.framesIndex-1].freeVars[freeIndex] = *val *v.frames[v.framesIndex-1].freeVars[freeIndex] = *val
case compiler.OpIteratorInit: case compiler.OpIteratorInit:
var iterator objects.Object var iterator objects.Object
@ -650,6 +667,7 @@ func (v *VM) Run() error {
if err := v.push(&iterator); err != nil { if err := v.push(&iterator); err != nil {
return err return err
} }
case compiler.OpIteratorNext: case compiler.OpIteratorNext:
iterator := v.pop() iterator := v.pop()
b := (*iterator).(objects.Iterator).Next() b := (*iterator).(objects.Iterator).Next()
@ -716,6 +734,13 @@ func (v *VM) pop() *objects.Object {
return o 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 { func (v *VM) pushClosure(constIndex, numFree int) error {
c := v.constants[constIndex] c := v.constants[constIndex]
@ -853,6 +878,61 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
fn.NumParameters, numArgs) 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].fn = fn
v.frames[v.framesIndex].freeVars = freeVars v.frames[v.framesIndex].freeVars = freeVars
v.frames[v.framesIndex].ip = -1 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 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 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][0]`, 1)
expect(t, `out = [1, 2, 3][1]`, 2) expect(t, `out = [1, 2, 3][1]`, 2)
expect(t, `out = [1, 2, 3][2]`, 3) 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, `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[2]`, 3)
expect(t, `arr = [1, 2, 3]; out = arr[0] + arr[1] + arr[2]`, 6) 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]; i := arr[0]; out = arr[i]`, 2)
expect(t, `out = [1, 2, 3][1+1]`, 3) 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][:]`, ARR{1, 2, 3})
expect(t, `out = [1, 2, 3][0: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][1:1]`, ARR{})
expect(t, `out = [1, 2, 3][3-2:1+1]`, ARR{2}) 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 // 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][3]`)
expectError(t, `[1, 2, 3][-1]`) 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 = 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; 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 = 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 := 3; a = 2; return a }; out = f1()`, 2)
// define operator ":=" is optional expect(t, `a := 1; out = a`, 1)
expect(t, `a = 1; out = a`, 1) expect(t, `a := 1; a = 2; out = a`, 2)
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`, 2) expect(t, `a := 1; func() { a := 2 }(); out = a`, 1) // "a := 2" defines a new local variable 'a'
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; func() { b = 2; out = b }()`, 2)
expect(t, ` expect(t, `
out = func() { out = func() {
a = 2 a := 2
func() { func() {
a = 3 // captured from outer scope a = 3 // captured from outer scope
}() }()
@ -29,7 +28,7 @@ out = func() {
expect(t, ` expect(t, `
func() { func() {
a = 5 a := 5
out = func() { out = func() {
a := 4 a := 4
return a return a
@ -39,14 +38,14 @@ func() {
expectError(t, `a := 1; a := 2`) // redeclared in the same scope expectError(t, `a := 1; a := 2`) // redeclared in the same scope
expectError(t, `func() { 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 += 2; out = a`, 3)
expect(t, `a = 1; a += 4 - 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 -= 1;; out = a`, 2)
expect(t, `a = 3; a -= 5 - 4;; 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 *= 4;; out = a`, 8)
expect(t, `a = 2; a *= 1 + 3;; 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 /= 2;; out = a`, 5)
expect(t, `a = 10; a /= 5 - 3;; out = a`, 5) expect(t, `a := 10; a /= 5 - 3;; out = a`, 5)
// +=, -=, *=, /= operator does not define new variable // +=, -=, *=, /= operator does not define new variable
expectError(t, `a += 4`) expectError(t, `a += 4`)
@ -55,9 +54,9 @@ func() {
expectError(t, `a /= 4`) expectError(t, `a /= 4`)
expect(t, ` expect(t, `
f1 = func() { f1 := func() {
f2 = func() { f2 := func() {
a = 1 a := 1
a += 2 // it's a statement, not an expression a += 2 // it's a statement, not an expression
return a return a
}; };
@ -66,20 +65,20 @@ f1 = func() {
}; };
out = f1();`, 3) 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 := 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 -= 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 := 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 *= 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 := 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 /= 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 := 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, ` expect(t, `
f1 = func(a) { f1 := func(a) {
return func(b) { return func(b) {
c = a c := a
c += b * 2 c += b * 2
return c return c
} }
@ -90,7 +89,7 @@ out = f1();`, 3)
expect(t, ` expect(t, `
out = func() { out = func() {
a = 1 a := 1
func() { func() {
a = 2 a = 2
func() { func() {
@ -106,8 +105,8 @@ out = f1();`, 3)
// write on free variables // write on free variables
expect(t, ` expect(t, `
f1 = func() { f1 := func() {
a = 5 a := 5
return func() { return func() {
a += 3 a += 3
@ -118,14 +117,14 @@ out = f1();`, 3)
`, 8) `, 8)
expect(t, ` expect(t, `
it = func(seq, fn) { it := func(seq, fn) {
fn(seq[0]) fn(seq[0])
fn(seq[1]) fn(seq[1])
fn(seq[2]) fn(seq[2])
} }
foo = func(a) { foo := func(a) {
b = 0 b := 0
it([1, 2, 3], func(x) { it([1, 2, 3], func(x) {
b = x + a b = x + a
}) })
@ -136,14 +135,14 @@ out = f1();`, 3)
`, 5) `, 5)
expect(t, ` expect(t, `
it = func(seq, fn) { it := func(seq, fn) {
fn(seq[0]) fn(seq[0])
fn(seq[1]) fn(seq[1])
fn(seq[2]) fn(seq[2])
} }
foo = func(a) { foo := func(a) {
b = 0 b := 0
it([1, 2, 3], func(x) { it([1, 2, 3], func(x) {
b += x + a b += x + a
}) })
@ -155,7 +154,7 @@ out = f1();`, 3)
expect(t, ` expect(t, `
out = func() { out = func() {
a = 1 a := 1
func() { func() {
a = 2 a = 2
}() }()
@ -164,25 +163,25 @@ out = func() {
`, 2) `, 2)
expect(t, ` expect(t, `
f = func() { f := func() {
a = 1 a := 1
return { return {
b: func() { a += 3 }, b: func() { a += 3 },
c: func() { a += 2 }, c: func() { a += 2 },
d: func() { return a } d: func() { return a }
} }
} }
m = f() m := f()
m.b() m.b()
m.c() m.c()
out = m.d() out = m.d()
`, 6) `, 6)
expect(t, ` 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() { out = func() {
a = 100 a := 100
each([1, 2, 3], func(x) { each([1, 2, 3], func(x) {
a += x a += x
}) })
@ -194,11 +193,11 @@ out = func() {
`, 136) `, 136)
// assigning different type value // assigning different type value
expect(t, `a = 1; a = "foo"; out = a`, "foo") // global expect(t, `a := 1; a = "foo"; out = a`, "foo") // global
expect(t, `func() { a = 1; a = "foo"; out = a }()`, "foo") // local expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local
expect(t, ` expect(t, `
out = func() { out = func() {
a = 5 a := 5
return func() { return func() {
a = "foo" a = "foo"
return a return a
@ -206,18 +205,18 @@ out = func() {
}()`, "foo") // free }()`, "foo") // free
// variables declared in if/for blocks // variables declared in if/for blocks
expect(t, `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") expect(t, `func() { for a:=0; a<5; a++ {}; a := "foo"; out = a }()`, "foo")
// selectors // 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]`, 5)
expect(t, `a=[1,2,3]; a[1] += 5; out = a[1]`, 7) 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`, 5)
expect(t, `a={b:1,c:2}; a.b += 5; out = a.b`, 6) 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.b`, 3)
expect(t, `a={b:1,c:2}; a.b += a.c; out = a.c`, 2) expect(t, `a:={b:1,c:2}; a.b += a.c; out = a.c`, 2)
expect(t, ` expect(t, `
a = { a := {
b: [1, 2, 3], b: [1, 2, 3],
c: { c: {
d: 8, d: 8,
@ -230,7 +229,7 @@ out = a["c"]["f"][1]
`, 10) `, 10)
expect(t, ` expect(t, `
a = { a := {
b: [1, 2, 3], b: [1, 2, 3],
c: { c: {
d: 8, d: 8,
@ -243,7 +242,7 @@ out = a.c.h
`, "bar") `, "bar")
expectError(t, ` expectError(t, `
a = { a := {
b: [1, 2, 3], b: [1, 2, 3],
c: { c: {
d: 8, d: 8,

View file

@ -3,9 +3,9 @@ package vm_test
import "testing" import "testing"
func TestCall(t *testing.T) { func TestCall(t *testing.T) {
expect(t, `a = { b: func(x) { return x + 2 } }; out = a.b(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)
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 // "this" binding
// expect(t, ` // expect(t, `

View file

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

View file

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

View file

@ -18,11 +18,11 @@ out = {
expect(t, `out = {foo: 5}["foo"]`, 5) expect(t, `out = {foo: 5}["foo"]`, 5)
expect(t, `out = {foo: 5}["bar"]`, undefined()) 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, `out = {}["foo"]`, undefined())
expect(t, ` expect(t, `
m = { m := {
foo: func(x) { foo: func(x) {
return x * 2 return x * 2
} }
@ -31,6 +31,8 @@ out = m["foo"](2) + m["foo"](3)
`, 10) `, 10)
// map assignment is copy-by-reference // 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; 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; 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) }()`, 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) { 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.k1`, 5)
expect(t, `a = {k1: 5, k2: "foo"}; out = a.k2`, "foo") 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.k3`, undefined())
expect(t, ` expect(t, `
a = { a := {
b: { b: {
c: 4, c: 4,
a: false a: false
@ -20,7 +20,7 @@ a = {
out = a.b.c`, 4) out = a.b.c`, 4)
expectError(t, ` expectError(t, `
a = { a := {
b: { b: {
c: 4, c: 4,
a: false a: false
@ -30,7 +30,7 @@ a = {
out = a.x.c`) out = a.x.c`)
expectError(t, ` expectError(t, `
a = { a := {
b: { b: {
c: 4, c: 4,
a: false a: false
@ -39,26 +39,26 @@ a = {
} }
out = a.x.y`) 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.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: 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: {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: 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: {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.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: 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: {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: 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: {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.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: 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: {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: 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: {c: 1}}; func() { a.b.d = 2 }(); out = a }()`, MAP{"b": MAP{"c": 1, "d": 2}})
expect(t, ` expect(t, `
a = { a := {
b: [1, 2, 3], b: [1, 2, 3],
c: { c: {
d: 8, d: 8,
@ -69,10 +69,20 @@ a = {
out = [a.b[2], a.c.d, a.c.e, a.c.f[1]] out = [a.b[2], a.c.d, a.c.e, a.c.f[1]]
`, ARR{3, 8, "foo", 8}) `, ARR{3, 8, "foo", 8})
expectError(t, `a = {b: {c: 1}}; a.d.c = 2`) expect(t, `
expectError(t, `a = [1, 2, 3]; a.b = 2`) func() {
expectError(t, `a = "foo"; a.b = 2`) a := [1, 2, 3]
expectError(t, `func() { a = {b: {c: 1}}; a.d.c = 2 }()`) b := 9
expectError(t, `func() { a = [1, 2, 3]; a.b = 2 }()`) a[1] = b
expectError(t, `func() { a = "foo"; a.b = 2 }()`) 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)
}