From cb237b6685240443dc96d0f7a325345460b2e008 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Fri, 30 Oct 2020 13:21:53 -0400 Subject: [PATCH 01/15] added changes to support the v1 of 'this' --- compiler.go | 31 +++++++++++++++++++++++++++-- compiler_test.go | 19 ++++++++++++++++-- objects.go | 21 ++++++++++++++++++++ parser/expr.go | 6 +++++- parser/opcodes.go | 2 +- parser/parser.go | 10 ++++++++++ parser/parser_test.go | 30 ++++++++++++++++++++++++++++ symbol_table.go | 8 ++++++++ vm.go | 21 ++++++++++++++++++-- vm_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 186 insertions(+), 8 deletions(-) diff --git a/compiler.go b/compiler.go index 53cc7d38..609e25d1 100644 --- a/compiler.go +++ b/compiler.go @@ -56,6 +56,7 @@ type Compiler struct { loopIndex int trace io.Writer indent int + inMethodCall bool } // NewCompiler creates a Compiler. @@ -362,21 +363,31 @@ func (c *Compiler) Compile(node parser.Node) error { c.emit(node, parser.OpMap, len(node.Elements)*2) case *parser.SelectorExpr: // selector on RHS side + method := 0 + if c.inMethodCall { + method = 1 + } + c.inMethodCall = false if err := c.Compile(node.Expr); err != nil { return err } if err := c.Compile(node.Sel); err != nil { return err } - c.emit(node, parser.OpIndex) + c.emit(node, parser.OpIndex, method) case *parser.IndexExpr: + method := 0 + if c.inMethodCall { + method = 1 + } + c.inMethodCall = false if err := c.Compile(node.Expr); err != nil { return err } if err := c.Compile(node.Index); err != nil { return err } - c.emit(node, parser.OpIndex) + c.emit(node, parser.OpIndex, method) case *parser.SliceExpr: if err := c.Compile(node.Expr); err != nil { return err @@ -398,6 +409,7 @@ func (c *Compiler) Compile(node parser.Node) error { c.emit(node, parser.OpSliceIndex) case *parser.FuncLit: c.enterScope() + usesReceiver := node.Type.Receiver != nil && len(node.Type.Receiver.List) == 1 for _, p := range node.Type.Params.List { s := c.symbolTable.Define(p.Name) @@ -405,6 +417,11 @@ func (c *Compiler) Compile(node parser.Node) error { // function arguments is not assigned directly. s.LocalAssigned = true } + if usesReceiver { + s := c.symbolTable.Define(node.Type.Receiver.List[0].Name) + // receiver is not assigned directly. + s.LocalAssigned = true + } if err := c.Compile(node.Body); err != nil { return err @@ -476,6 +493,7 @@ func (c *Compiler) Compile(node parser.Node) error { NumParameters: len(node.Type.Params.List), VarArgs: node.Type.Params.VarArgs, SourceMap: sourceMap, + UsesReceiver: usesReceiver, } if len(freeSymbols) > 0 { c.emit(node, parser.OpClosure, @@ -498,9 +516,18 @@ func (c *Compiler) Compile(node parser.Node) error { c.emit(node, parser.OpReturn, 1) } case *parser.CallExpr: + method := false + switch node.Func.(type) { + case *parser.SelectorExpr: + method = true + case *parser.IndexExpr: + method = true + } + c.inMethodCall = method if err := c.Compile(node.Func); err != nil { return err } + c.inMethodCall = false for _, arg := range node.Args { if err := c.Compile(arg); err != nil { return err diff --git a/compiler_test.go b/compiler_test.go index 0fe93121..e69c7613 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -388,7 +388,7 @@ func TestCompiler_Compile(t *testing.T) { tengo.MakeInstruction(parser.OpConstant, 0), tengo.MakeInstruction(parser.OpConstant, 0), tengo.MakeInstruction(parser.OpBinaryOp, 11), - tengo.MakeInstruction(parser.OpIndex), + tengo.MakeInstruction(parser.OpIndex, 0), tengo.MakeInstruction(parser.OpPop), tengo.MakeInstruction(parser.OpSuspend)), objectsArray( @@ -405,7 +405,7 @@ func TestCompiler_Compile(t *testing.T) { tengo.MakeInstruction(parser.OpConstant, 1), tengo.MakeInstruction(parser.OpConstant, 2), tengo.MakeInstruction(parser.OpBinaryOp, 12), - tengo.MakeInstruction(parser.OpIndex), + tengo.MakeInstruction(parser.OpIndex, 0), tengo.MakeInstruction(parser.OpPop), tengo.MakeInstruction(parser.OpSuspend)), objectsArray( @@ -502,6 +502,21 @@ func TestCompiler_Compile(t *testing.T) { intObject(1), intObject(2)))) + expectCompile(t, `f1 := func(r)() { return r }; f1();`, + bytecode( + concatInsts( + tengo.MakeInstruction(parser.OpConstant, 0), + tengo.MakeInstruction(parser.OpSetGlobal, 0), + tengo.MakeInstruction(parser.OpGetGlobal, 0), + tengo.MakeInstruction(parser.OpCall, 0, 0), + tengo.MakeInstruction(parser.OpPop), + tengo.MakeInstruction(parser.OpSuspend)), + objectsArray( + compiledFunction(1, 1, + tengo.MakeInstruction(parser.OpGetLocal, 0), + tengo.MakeInstruction(parser.OpReturn, 1)), + ))) + expectCompile(t, `func() { return 5 + 10 }`, bytecode( concatInsts( diff --git a/objects.go b/objects.go index 30913db5..5eb9801b 100644 --- a/objects.go +++ b/objects.go @@ -576,6 +576,7 @@ type CompiledFunction struct { VarArgs bool SourceMap map[int]parser.Pos Free []*ObjectPtr + UsesReceiver bool } // TypeName returns the name of the type. @@ -595,6 +596,7 @@ func (o *CompiledFunction) Copy() Object { NumParameters: o.NumParameters, VarArgs: o.VarArgs, Free: append([]*ObjectPtr{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers + UsesReceiver: o.UsesReceiver, } } @@ -1308,6 +1310,25 @@ func (o *ObjectPtr) Equals(x Object) bool { return o == x } +type receiverWrapper struct { + ObjectImpl + Interior Object + Receiver Object +} + +func (o *receiverWrapper) String() string { + return "wrapper of " + o.Interior.TypeName() + " and " + o.Receiver.TypeName() +} + +// TypeName returns the name of the type. +func (o *receiverWrapper) TypeName() string { + return "" +} + +func (o *receiverWrapper) CanCall() bool { + return o.Interior.CanCall() +} + // String represents a string value. type String struct { ObjectImpl diff --git a/parser/expr.go b/parser/expr.go index b6b6c62b..176e0ae6 100644 --- a/parser/expr.go +++ b/parser/expr.go @@ -256,13 +256,14 @@ func (e *FuncLit) End() Pos { } func (e *FuncLit) String() string { - return "func" + e.Type.Params.String() + " " + e.Body.String() + return e.Type.String() + " " + e.Body.String() } // FuncType represents a function type definition. type FuncType struct { FuncPos Pos Params *IdentList + Receiver*IdentList } func (e *FuncType) exprNode() {} @@ -278,6 +279,9 @@ func (e *FuncType) End() Pos { } func (e *FuncType) String() string { + if e.Receiver != nil { + return "func" + e.Receiver.String() + e.Params.String() + } return "func" + e.Params.String() } diff --git a/parser/opcodes.go b/parser/opcodes.go index d97f4896..ce54edf5 100644 --- a/parser/opcodes.go +++ b/parser/opcodes.go @@ -118,7 +118,7 @@ var OpcodeOperands = [...][]int{ OpMap: {2}, OpError: {}, OpImmutable: {}, - OpIndex: {}, + OpIndex: {1}, OpSliceIndex: {}, OpCall: {1, 1}, OpReturn: {1}, diff --git a/parser/parser.go b/parser/parser.go index fd20423b..38c55da0 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -578,10 +578,20 @@ func (p *Parser) parseFuncType() *FuncType { } pos := p.expect(token.Func) + var receiver *IdentList params := p.parseIdentList() + if p.token == token.LParen { + if !params.VarArgs && len(params.List) <= 1 { + receiver = params + params = p.parseIdentList() + } else { + p.errorExpected(params.Pos(), "1 receiver") + } + } return &FuncType{ FuncPos: pos, Params: params, + Receiver:receiver, } } diff --git a/parser/parser_test.go b/parser/parser_test.go index 4ee6ad7a..7251e659 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -733,6 +733,32 @@ func TestParseFunction(t *testing.T) { }) } +func TestParseMethod(t *testing.T) { + expectParse(t, "a = func(b)(c, d) { return d }", func(p pfn) []Stmt { + return stmts( + assignStmt( + exprs( + ident("a", p(1, 1))), + exprs( + funcLit( + funcTypeMethod( + identList(p(1, 9), p(1, 11), false, + ident("b", p(1, 10))), + identList(p(1, 12), p(1, 17), false, + ident("c", p(1, 13)), + ident("d", p(1, 16))), + p(1, 5)), + blockStmt(p(1, 19), p(1, 30), + returnStmt(p(1, 21), ident("d", p(1, 28)))))), + token.Assign, + p(1, 3))) + }) + + expectParseError(t, "a = func(f, g)(c, d) { return d }") + expectParseError(t, "a = func(...g)(c, d) { return d }") + expectParseError(t, "a = func(...g, a)(c, d) { return d }") +} + func TestParseVariadicFunction(t *testing.T) { expectParse(t, "a = func(...args) { return args }", func(p pfn) []Stmt { return stmts( @@ -1669,6 +1695,10 @@ func funcType(params *IdentList, pos Pos) *FuncType { return &FuncType{Params: params, FuncPos: pos} } +func funcTypeMethod(receiver, params *IdentList, pos Pos) *FuncType { + return &FuncType{Receiver: receiver, Params: params, FuncPos: pos} +} + func blockStmt(lbrace, rbrace Pos, list ...Stmt) *BlockStmt { return &BlockStmt{Stmts: list, LBrace: lbrace, RBrace: rbrace} } diff --git a/symbol_table.go b/symbol_table.go index 73aaad3b..f88b09c1 100644 --- a/symbol_table.go +++ b/symbol_table.go @@ -174,6 +174,14 @@ func (t *SymbolTable) updateMaxDefs(numDefs int) { } } +func (t *SymbolTable) defineReceiver(name string) { + t.store[name] = &Symbol{ + Name: name, + Index: 0, + Scope: ScopeFree, + } +} + func (t *SymbolTable) defineFree(original *Symbol) *Symbol { // TODO: should we check duplicates? t.freeSymbols = append(t.freeSymbols, original) diff --git a/vm.go b/vm.go index 811ecef9..65a7d5ed 100644 --- a/vm.go +++ b/vm.go @@ -2,10 +2,9 @@ package tengo import ( "fmt" - "sync/atomic" - "github.com/d5/tengo/v2/parser" "github.com/d5/tengo/v2/token" + "sync/atomic" ) // frame represents a function call frame. @@ -14,6 +13,7 @@ type frame struct { freeVars []*ObjectPtr ip int basePointer int + context Object } // VM is a virtual machine that executes the bytecode compiled by Compiler. @@ -345,6 +345,9 @@ func (v *VM) run() { v.stack[v.sp-1] = immutableMap } case parser.OpIndex: + method := int(v.curInsts[v.ip+1]) + v.ip++ + index := v.stack[v.sp-1] left := v.stack[v.sp-2] v.sp -= 2 @@ -366,6 +369,11 @@ func (v *VM) run() { if val == nil { val = UndefinedValue } + if method == 1 { + v.curFrame.context = left + } else { + v.curFrame.context = UndefinedValue + } v.stack[v.sp] = val v.sp++ case parser.OpSliceIndex: @@ -617,6 +625,15 @@ func (v *VM) run() { return } + if callee.UsesReceiver { + if v.curFrame.context == nil { + v.stack[v.sp] = UndefinedValue + } else { + v.stack[v.sp] = v.curFrame.context + } + v.curFrame.context = nil + } + // update call frame v.curFrame.ip = v.ip // store current ip before call v.curFrame = &(v.frames[v.framesIndex]) diff --git a/vm_test.go b/vm_test.go index feb91f4f..177618ba 100644 --- a/vm_test.go +++ b/vm_test.go @@ -2771,6 +2771,52 @@ export { x: 1 } 1) } +func TestMethod(t *testing.T) { + expectRun(t, ` + s := func(r)(n, m, o) { + f := "F" + g := "G" + h := n + m * o + return r.h + }; + m := { f1: s, h: 22 } + out = m.f1(4, 5, 6)`, + nil, 22) + + expectRun(t, ` + m := { + f1: func(r)(n) { + if n > 0 { + return r.res + } else { + return r.f1(n-1) + } + }, + res: 22 + } + out = m.f1(12)`, + nil, 22) + + expectRun(t, ` + m := { f1: func(r)() { return r.c }} + n := copy(m) + n.c = 3 + out = n.f1()`, + nil, 3) + + expectRun(t, ` + f1 := func(r)() { return r } + out = f1()`, + nil, tengo.UndefinedValue) + + expectRun(t, ` + m := { f1: func(r)() { return r.c }} + n := copy(m) + n.c = 3 + out = n.f1()`, + nil, 3) +} + func TestModuleBlockScopes(t *testing.T) { m := Opts().Module("rand", &tengo.BuiltinModule{ From ceca2694ec806f138008cfee588b88f289d38108 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Sat, 31 Oct 2020 01:16:10 -0400 Subject: [PATCH 02/15] changed receiver to work via Free Vars. --- compiler.go | 12 +++++++----- compiler_test.go | 14 +++++++------- objects.go | 4 ++-- symbol_table.go | 8 -------- vm.go | 19 +++++-------------- vm_test.go | 14 +++++++++++++- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/compiler.go b/compiler.go index 609e25d1..90b37746 100644 --- a/compiler.go +++ b/compiler.go @@ -409,7 +409,8 @@ func (c *Compiler) Compile(node parser.Node) error { c.emit(node, parser.OpSliceIndex) case *parser.FuncLit: c.enterScope() - usesReceiver := node.Type.Receiver != nil && len(node.Type.Receiver.List) == 1 + reci := node.Type.Receiver + usesReceiver := reci != nil && len(reci.List) == 1 for _, p := range node.Type.Params.List { s := c.symbolTable.Define(p.Name) @@ -418,9 +419,9 @@ func (c *Compiler) Compile(node parser.Node) error { s.LocalAssigned = true } if usesReceiver { - s := c.symbolTable.Define(node.Type.Receiver.List[0].Name) - // receiver is not assigned directly. - s.LocalAssigned = true + c.symbolTable.defineFree(&Symbol{ Name: node.Type.Receiver.List[0].Name }) + } else { + c.symbolTable.defineFree(&Symbol{ Name: "" }) } if err := c.Compile(node.Body); err != nil { @@ -430,7 +431,7 @@ func (c *Compiler) Compile(node parser.Node) error { // code optimization c.optimizeFunc(node) - freeSymbols := c.symbolTable.FreeSymbols() + freeSymbols := c.symbolTable.FreeSymbols()[1:] numLocals := c.symbolTable.MaxSymbols() instructions, sourceMap := c.leaveScope() @@ -494,6 +495,7 @@ func (c *Compiler) Compile(node parser.Node) error { VarArgs: node.Type.Params.VarArgs, SourceMap: sourceMap, UsesReceiver: usesReceiver, + Free: []*ObjectPtr{{Value: &UndefinedValue}}, } if len(freeSymbols) > 0 { c.emit(node, parser.OpClosure, diff --git a/compiler_test.go b/compiler_test.go index e69c7613..39b24dfb 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -513,7 +513,7 @@ func TestCompiler_Compile(t *testing.T) { tengo.MakeInstruction(parser.OpSuspend)), objectsArray( compiledFunction(1, 1, - tengo.MakeInstruction(parser.OpGetLocal, 0), + tengo.MakeInstruction(parser.OpGetFree, 0), tengo.MakeInstruction(parser.OpReturn, 1)), ))) @@ -842,7 +842,7 @@ func TestCompiler_Compile(t *testing.T) { tengo.MakeInstruction(parser.OpSuspend)), objectsArray( compiledFunction(1, 1, - tengo.MakeInstruction(parser.OpGetFree, 0), + tengo.MakeInstruction(parser.OpGetFree, 1), tengo.MakeInstruction(parser.OpGetLocal, 0), tengo.MakeInstruction(parser.OpBinaryOp, 11), tengo.MakeInstruction(parser.OpReturn, 1)), @@ -867,14 +867,14 @@ func(a) { tengo.MakeInstruction(parser.OpSuspend)), objectsArray( compiledFunction(1, 1, - tengo.MakeInstruction(parser.OpGetFree, 0), tengo.MakeInstruction(parser.OpGetFree, 1), + tengo.MakeInstruction(parser.OpGetFree, 2), tengo.MakeInstruction(parser.OpBinaryOp, 11), tengo.MakeInstruction(parser.OpGetLocal, 0), tengo.MakeInstruction(parser.OpBinaryOp, 11), tengo.MakeInstruction(parser.OpReturn, 1)), compiledFunction(1, 1, - tengo.MakeInstruction(parser.OpGetFreePtr, 0), + tengo.MakeInstruction(parser.OpGetFreePtr, 1), tengo.MakeInstruction(parser.OpGetLocalPtr, 0), tengo.MakeInstruction(parser.OpClosure, 0, 2), tengo.MakeInstruction(parser.OpReturn, 1)), @@ -915,17 +915,17 @@ func() { tengo.MakeInstruction(parser.OpConstant, 3), tengo.MakeInstruction(parser.OpDefineLocal, 0), tengo.MakeInstruction(parser.OpGetGlobal, 0), - tengo.MakeInstruction(parser.OpGetFree, 0), - tengo.MakeInstruction(parser.OpBinaryOp, 11), tengo.MakeInstruction(parser.OpGetFree, 1), tengo.MakeInstruction(parser.OpBinaryOp, 11), + tengo.MakeInstruction(parser.OpGetFree, 2), + tengo.MakeInstruction(parser.OpBinaryOp, 11), tengo.MakeInstruction(parser.OpGetLocal, 0), tengo.MakeInstruction(parser.OpBinaryOp, 11), tengo.MakeInstruction(parser.OpReturn, 1)), compiledFunction(1, 0, tengo.MakeInstruction(parser.OpConstant, 2), tengo.MakeInstruction(parser.OpDefineLocal, 0), - tengo.MakeInstruction(parser.OpGetFreePtr, 0), + tengo.MakeInstruction(parser.OpGetFreePtr, 1), tengo.MakeInstruction(parser.OpGetLocalPtr, 0), tengo.MakeInstruction(parser.OpClosure, 4, 2), tengo.MakeInstruction(parser.OpReturn, 1)), diff --git a/objects.go b/objects.go index 5eb9801b..be193085 100644 --- a/objects.go +++ b/objects.go @@ -585,7 +585,7 @@ func (o *CompiledFunction) TypeName() string { } func (o *CompiledFunction) String() string { - return "" + return fmt.Sprintf(" %v", o.Free) } // Copy returns a copy of the type. @@ -1286,7 +1286,7 @@ type ObjectPtr struct { } func (o *ObjectPtr) String() string { - return "free-var" + return fmt.Sprintf("free-var -> %v", (*o.Value).TypeName()) } // TypeName returns the name of the type. diff --git a/symbol_table.go b/symbol_table.go index f88b09c1..73aaad3b 100644 --- a/symbol_table.go +++ b/symbol_table.go @@ -174,14 +174,6 @@ func (t *SymbolTable) updateMaxDefs(numDefs int) { } } -func (t *SymbolTable) defineReceiver(name string) { - t.store[name] = &Symbol{ - Name: name, - Index: 0, - Scope: ScopeFree, - } -} - func (t *SymbolTable) defineFree(original *Symbol) *Symbol { // TODO: should we check duplicates? t.freeSymbols = append(t.freeSymbols, original) diff --git a/vm.go b/vm.go index 65a7d5ed..f5448324 100644 --- a/vm.go +++ b/vm.go @@ -13,7 +13,6 @@ type frame struct { freeVars []*ObjectPtr ip int basePointer int - context Object } // VM is a virtual machine that executes the bytecode compiled by Compiler. @@ -370,9 +369,10 @@ func (v *VM) run() { val = UndefinedValue } if method == 1 { - v.curFrame.context = left - } else { - v.curFrame.context = UndefinedValue + if cfnc, ok := val.(*CompiledFunction); ok { + val = cfnc.Copy() + val.(*CompiledFunction).Free[0] = &ObjectPtr{Value: &left} + } } v.stack[v.sp] = val v.sp++ @@ -625,15 +625,6 @@ func (v *VM) run() { return } - if callee.UsesReceiver { - if v.curFrame.context == nil { - v.stack[v.sp] = UndefinedValue - } else { - v.stack[v.sp] = v.curFrame.context - } - v.curFrame.context = nil - } - // update call frame v.curFrame.ip = v.ip // store current ip before call v.curFrame = &(v.frames[v.framesIndex]) @@ -784,7 +775,7 @@ func (v *VM) run() { NumLocals: fn.NumLocals, NumParameters: fn.NumParameters, VarArgs: fn.VarArgs, - Free: free, + Free: append(fn.Free, free...), } v.allocs-- if v.allocs == 0 { diff --git a/vm_test.go b/vm_test.go index 177618ba..d0fdcd17 100644 --- a/vm_test.go +++ b/vm_test.go @@ -2814,7 +2814,19 @@ func TestMethod(t *testing.T) { n := copy(m) n.c = 3 out = n.f1()`, - nil, 3) + nil, 3) + + expectRun(t, ` + m := { f1: func(r)() { return r == undefined}} + n := copy(m) + f := n.f1 + out = f()`, + nil, true) + + expectRun(t, ` + m := { f1: func(r)() { return r == undefined}} + out = m.f1()`, + nil, false) } func TestModuleBlockScopes(t *testing.T) { From b38b3509f193fc3389b4c0e2fa0b46b7560affa5 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Sat, 31 Oct 2020 01:51:59 -0400 Subject: [PATCH 03/15] removed some erroneous changes --- objects.go | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/objects.go b/objects.go index be193085..a4345710 100644 --- a/objects.go +++ b/objects.go @@ -1286,7 +1286,7 @@ type ObjectPtr struct { } func (o *ObjectPtr) String() string { - return fmt.Sprintf("free-var -> %v", (*o.Value).TypeName()) + return "free-var" } // TypeName returns the name of the type. @@ -1310,25 +1310,6 @@ func (o *ObjectPtr) Equals(x Object) bool { return o == x } -type receiverWrapper struct { - ObjectImpl - Interior Object - Receiver Object -} - -func (o *receiverWrapper) String() string { - return "wrapper of " + o.Interior.TypeName() + " and " + o.Receiver.TypeName() -} - -// TypeName returns the name of the type. -func (o *receiverWrapper) TypeName() string { - return "" -} - -func (o *receiverWrapper) CanCall() bool { - return o.Interior.CanCall() -} - // String represents a string value. type String struct { ObjectImpl From b63a9890b294223804bd1c90954d046ceaf30c7c Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Sat, 31 Oct 2020 01:55:48 -0400 Subject: [PATCH 04/15] fixed more erroneous changes --- objects.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/objects.go b/objects.go index a4345710..4da6d4db 100644 --- a/objects.go +++ b/objects.go @@ -585,7 +585,7 @@ func (o *CompiledFunction) TypeName() string { } func (o *CompiledFunction) String() string { - return fmt.Sprintf(" %v", o.Free) + return "" } // Copy returns a copy of the type. From b7cc17cd08314f79a24f996f801f604420822d7e Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Sat, 31 Oct 2020 06:28:48 -0400 Subject: [PATCH 05/15] added builin is_method --- builtins.go | 17 +++++++++++++++++ compiler.go | 2 +- vm.go | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/builtins.go b/builtins.go index fcda81cd..d7c98d5e 100644 --- a/builtins.go +++ b/builtins.go @@ -109,6 +109,10 @@ var builtinFuncs = []*BuiltinFunction{ Name: "is_function", Value: builtinIsFunction, }, + { + Name: "is_method", + Value: builtinIsMethod, + }, { Name: "is_callable", Value: builtinIsCallable, @@ -276,6 +280,19 @@ func builtinIsFunction(args ...Object) (Object, error) { return FalseValue, nil } +func builtinIsMethod(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + switch args[0].(type) { + case *CompiledFunction: + if args[0].(*CompiledFunction).UsesReceiver { + return TrueValue, nil + } + } + return FalseValue, nil +} + func builtinIsCallable(args ...Object) (Object, error) { if len(args) != 1 { return nil, ErrWrongNumArguments diff --git a/compiler.go b/compiler.go index 90b37746..83add212 100644 --- a/compiler.go +++ b/compiler.go @@ -419,7 +419,7 @@ func (c *Compiler) Compile(node parser.Node) error { s.LocalAssigned = true } if usesReceiver { - c.symbolTable.defineFree(&Symbol{ Name: node.Type.Receiver.List[0].Name }) + c.symbolTable.defineFree(&Symbol{ Name: reci.List[0].Name }) } else { c.symbolTable.defineFree(&Symbol{ Name: "" }) } diff --git a/vm.go b/vm.go index f5448324..6c5d807f 100644 --- a/vm.go +++ b/vm.go @@ -2,9 +2,10 @@ package tengo import ( "fmt" + "sync/atomic" + "github.com/d5/tengo/v2/parser" "github.com/d5/tengo/v2/token" - "sync/atomic" ) // frame represents a function call frame. From 4154aeedd815447042a581af8f2c2732ae9e9e8e Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Sat, 31 Oct 2020 06:29:35 -0400 Subject: [PATCH 06/15] documentation added for methods and builtins --- docs/builtins.md | 9 +++++-- docs/tutorial.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/docs/builtins.md b/docs/builtins.md index 940a215a..bafff084 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -290,13 +290,18 @@ Returns `true` if the object's type is undefined. Or it returns `false`. ## is_function -Returns `true` if the object's type is function or closure. Or it returns +Returns `true` if the object's type is function, method, or closure. Or it returns `false`. Note that `is_function` returns `false` for builtin functions and user-provided callable objects. +## is_method + +Returns `true` if the object's type is method. Or it returns +`false`. + ## is_callable -Returns `true` if the object is callable (e.g. function, closure, builtin +Returns `true` if the object is callable (e.g. function, closure, method, builtin function, or user-provided callable objects). Or it returns `false`. ## is_array diff --git a/docs/tutorial.md b/docs/tutorial.md index 9f98358b..89560e62 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -38,6 +38,7 @@ Here's a list of all available value types in Tengo. | immutable map | [immutable](#immutable-values) map | - | | undefined | [undefined](#undefined-values) value | - | | function | [function](#function-values) value | - | +| method | [method](#method-values) value | - | | _user-defined_ | value of [user-defined types](https://github.com/d5/tengo/blob/master/docs/objects.md) | - | ### Error Values @@ -218,6 +219,72 @@ f2(1, 2, 3) // valid; a = 1, b = [2, 3] f2([1, 2, 3]...) // valid; a = 1, b = [2, 3] ``` +## Method Values + +Sometimes, when creating structures in Tengo, it becomes desireable to be able +to call back to the parent map or array. This is handled by a method, a +specialized form of a function. When a method is defined, it creates a receiver in +the local scope that takes on the identity of the parent slice or array during a +call. + +```golang +my_counter := { + counter: 21, + getCount: func(reci)() { + return reci.counter + } +} + +my_counter.counter += 4 + +my_counter.getCount() //returns 25 +``` + +Notably, and unlike Go, a method receiver is not dependent on being instanced inside +a map or array. + +```golang +concat := func(reci)(str) { + reci.buffer += str +} + +my_writer := { + buffer: "", + append: concat +} + +my_writer.append("hello ") // my_obj.buffer = "hello " +my_writer.append("world!") // my_obj.buffer = "hello world!" +``` + +Furthermore, when called from outside a method-call, the receiver on a method call will +be ```undefined``` + +```golang +f := func(r)() { + return r +} + +f() //returns undefined +``` + +Finally, a copy() of an array or slice containing a method will reference the new copy. + +```golang +m1 := { + c: 21, + getC: func(r)() { return r.c } +} + +m2 := copy(m1) + +m2.c = 7 +m1.getC() //21 +m1.c = 2 +m1.getC() //2 +m2.getC() //7 +``` + ## Variables and Scopes A value can be assigned to a variable using assignment operator `:=` and `=`. From a79e373ff9376100eb4c21261f18762e3567e967 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Sat, 31 Oct 2020 06:40:33 -0400 Subject: [PATCH 07/15] more documentation changes. --- docs/tutorial.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 89560e62..1e9c8474 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -221,9 +221,9 @@ f2([1, 2, 3]...) // valid; a = 1, b = [2, 3] ## Method Values -Sometimes, when creating structures in Tengo, it becomes desireable to be able +Sometimes, when creating structures in Tengo, it becomes desirable to be able to call back to the parent map or array. This is handled by a method, a -specialized form of a function. When a method is defined, it creates a receiver in +specialized subset of a function. When a method is defined, it creates a receiver in the local scope that takes on the identity of the parent slice or array during a call. From dec4d58a4cf5b644759525c7e9d0b901c1563212 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Mon, 2 Nov 2020 03:43:48 -0500 Subject: [PATCH 08/15] altered the way that indexing is handled for methods --- compiler.go | 24 ++++-------------------- parser/expr.go | 2 ++ parser/parser.go | 6 +++++- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/compiler.go b/compiler.go index 83add212..38799403 100644 --- a/compiler.go +++ b/compiler.go @@ -56,7 +56,6 @@ type Compiler struct { loopIndex int trace io.Writer indent int - inMethodCall bool } // NewCompiler creates a Compiler. @@ -363,30 +362,24 @@ func (c *Compiler) Compile(node parser.Node) error { c.emit(node, parser.OpMap, len(node.Elements)*2) case *parser.SelectorExpr: // selector on RHS side - method := 0 - if c.inMethodCall { - method = 1 - } - c.inMethodCall = false if err := c.Compile(node.Expr); err != nil { return err } if err := c.Compile(node.Sel); err != nil { return err } + method := 0 + if node.Reci { method = 1 } c.emit(node, parser.OpIndex, method) case *parser.IndexExpr: - method := 0 - if c.inMethodCall { - method = 1 - } - c.inMethodCall = false if err := c.Compile(node.Expr); err != nil { return err } if err := c.Compile(node.Index); err != nil { return err } + method := 0 + if node.Reci { method = 1 } c.emit(node, parser.OpIndex, method) case *parser.SliceExpr: if err := c.Compile(node.Expr); err != nil { @@ -518,18 +511,9 @@ func (c *Compiler) Compile(node parser.Node) error { c.emit(node, parser.OpReturn, 1) } case *parser.CallExpr: - method := false - switch node.Func.(type) { - case *parser.SelectorExpr: - method = true - case *parser.IndexExpr: - method = true - } - c.inMethodCall = method if err := c.Compile(node.Func); err != nil { return err } - c.inMethodCall = false for _, arg := range node.Args { if err := c.Compile(arg); err != nil { return err diff --git a/parser/expr.go b/parser/expr.go index 176e0ae6..0f54c5b3 100644 --- a/parser/expr.go +++ b/parser/expr.go @@ -364,6 +364,7 @@ type IndexExpr struct { LBrack Pos Index Expr RBrack Pos + Reci bool } func (e *IndexExpr) exprNode() {} @@ -487,6 +488,7 @@ func (e *ParenExpr) String() string { type SelectorExpr struct { Expr Expr Sel Expr + Reci bool } func (e *SelectorExpr) exprNode() {} diff --git a/parser/parser.go b/parser/parser.go index 38c55da0..faa86837 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -335,6 +335,8 @@ func (p *Parser) parseIndexOrSlice(x Expr) Expr { p.exprLevel-- rbrack := p.expect(token.RBrack) + receiver := p.token == token.LParen + if numColons > 0 { // slice expression return &SliceExpr{ @@ -350,6 +352,7 @@ func (p *Parser) parseIndexOrSlice(x Expr) Expr { LBrack: lbrack, RBrack: rbrack, Index: index[0], + Reci: receiver, } } @@ -359,7 +362,8 @@ func (p *Parser) parseSelector(x Expr) Expr { } sel := p.parseIdent() - return &SelectorExpr{Expr: x, Sel: &StringLit{ + receiver := p.token == token.LParen + return &SelectorExpr{Expr: x, Reci: receiver, Sel: &StringLit{ Value: sel.Name, ValuePos: sel.NamePos, Literal: sel.Name, From ac644eff1532bb7547c3474e3832bfae391b2e66 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Mon, 2 Nov 2020 03:58:52 -0500 Subject: [PATCH 09/15] added intellij .idea/ to ignore; added parser tests --- .gitignore | 3 ++- parser/parser.go | 3 +-- parser/parser_test.go | 49 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 77738287..36f4e0b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -dist/ \ No newline at end of file +dist/ +idea/ diff --git a/parser/parser.go b/parser/parser.go index faa86837..1f64a623 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -335,8 +335,6 @@ func (p *Parser) parseIndexOrSlice(x Expr) Expr { p.exprLevel-- rbrack := p.expect(token.RBrack) - receiver := p.token == token.LParen - if numColons > 0 { // slice expression return &SliceExpr{ @@ -347,6 +345,7 @@ func (p *Parser) parseIndexOrSlice(x Expr) Expr { High: index[1], } } + receiver := p.token == token.LParen return &IndexExpr{ Expr: x, LBrack: lbrack, diff --git a/parser/parser_test.go b/parser/parser_test.go index 7251e659..e6fbe968 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -401,9 +401,10 @@ func TestParseCall(t *testing.T) { return stmts( exprStmt( callExpr( - selectorExpr( + selectorMethodExpr( ident("a", p(1, 1)), - stringLit("b", p(1, 3))), + stringLit("b", p(1, 3)), + true), p(1, 4), p(1, 5), NoPos))) }) @@ -411,11 +412,12 @@ func TestParseCall(t *testing.T) { return stmts( exprStmt( callExpr( - selectorExpr( + selectorMethodExpr( selectorExpr( ident("a", p(1, 1)), stringLit("b", p(1, 3))), - stringLit("c", p(1, 5))), + stringLit("c", p(1, 5)), + true), p(1, 6), p(1, 7), NoPos))) }) @@ -423,15 +425,28 @@ func TestParseCall(t *testing.T) { return stmts( exprStmt( callExpr( - selectorExpr( + selectorMethodExpr( indexExpr( ident("a", p(1, 1)), stringLit("b", p(1, 3)), p(1, 2), p(1, 6)), - stringLit("c", p(1, 8))), + stringLit("c", p(1, 8)), + true), p(1, 9), p(1, 10), NoPos))) }) + expectParse(t, `a["b"]()`, func(p pfn) []Stmt { + return stmts( + exprStmt( + callExpr( + indexMethodExpr( + ident("a", p(1, 1)), + stringLit("b", p(1, 3)), + p(1, 2), p(1, 6), + true), + p(1, 7), p(1, 8), NoPos))) + }) + expectParseError(t, `add(...a, 1)`) expectParseError(t, `add(a..., 1)`) expectParseError(t, `add(a..., b...)`) @@ -1815,7 +1830,17 @@ func indexExpr( lbrack, rbrack Pos, ) *IndexExpr { return &IndexExpr{ - Expr: x, Index: index, LBrack: lbrack, RBrack: rbrack, + Expr: x, Index: index, LBrack: lbrack, RBrack: rbrack, Reci: false, + } +} + +func indexMethodExpr( + x, index Expr, + lbrack, rbrack Pos, + reci bool, +) *IndexExpr { + return &IndexExpr{ + Expr: x, Index: index, LBrack: lbrack, RBrack: rbrack, Reci: reci, } } @@ -1839,7 +1864,11 @@ func errorExpr( } func selectorExpr(x, sel Expr) *SelectorExpr { - return &SelectorExpr{Expr: x, Sel: sel} + return &SelectorExpr{Expr: x, Sel: sel, Reci: false} +} + +func selectorMethodExpr(x, sel Expr, reci bool) *SelectorExpr { + return &SelectorExpr{Expr: x, Sel: sel, Reci: reci} } func equalStmt(t *testing.T, expected, actual Stmt) { @@ -2020,6 +2049,8 @@ func equalExpr(t *testing.T, expected, actual Expr) { actual.(*IndexExpr).LBrack) require.Equal(t, expected.RBrack, actual.(*IndexExpr).RBrack) + require.Equal(t, expected.Reci, + actual.(*IndexExpr).Reci) case *SliceExpr: equalExpr(t, expected.Expr, actual.(*SliceExpr).Expr) @@ -2036,6 +2067,8 @@ func equalExpr(t *testing.T, expected, actual Expr) { actual.(*SelectorExpr).Expr) equalExpr(t, expected.Sel, actual.(*SelectorExpr).Sel) + require.Equal(t, expected.Reci, + actual.(*SelectorExpr).Reci) case *ImportExpr: require.Equal(t, expected.ModuleName, actual.(*ImportExpr).ModuleName) From af8da1099341a0aa609219e06ef5440537c2aff6 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Mon, 2 Nov 2020 04:08:49 -0500 Subject: [PATCH 10/15] fixed ignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 36f4e0b8..4fc275fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ dist/ -idea/ +.idea From 66a0227fb288b2b72be30911014332596c2330ce Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Mon, 2 Nov 2020 05:39:48 -0500 Subject: [PATCH 11/15] changed how free vars are assembled into closures --- vm.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vm.go b/vm.go index 6c5d807f..0033fe67 100644 --- a/vm.go +++ b/vm.go @@ -759,13 +759,15 @@ func (v *VM) run() { v.err = fmt.Errorf("not function: %s", fn.TypeName()) return } - free := make([]*ObjectPtr, numFree) + fnFreeDefault := len(fn.Free) + free := make([]*ObjectPtr, numFree + fnFreeDefault) + copy(free[:fnFreeDefault], fn.Free) for i := 0; i < numFree; i++ { switch freeVar := (v.stack[v.sp-numFree+i]).(type) { case *ObjectPtr: - free[i] = freeVar + free[i + fnFreeDefault] = freeVar default: - free[i] = &ObjectPtr{ + free[i + fnFreeDefault] = &ObjectPtr{ Value: &v.stack[v.sp-numFree+i], } } @@ -776,7 +778,7 @@ func (v *VM) run() { NumLocals: fn.NumLocals, NumParameters: fn.NumParameters, VarArgs: fn.VarArgs, - Free: append(fn.Free, free...), + Free: free, } v.allocs-- if v.allocs == 0 { From 01879718b7686b3c2682ff453e28284dc56008e5 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Tue, 3 Nov 2020 04:28:45 -0500 Subject: [PATCH 12/15] fix a minor difference in the parser test --- parser/parser_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/parser/parser_test.go b/parser/parser_test.go index e6fbe968..6d646474 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1059,9 +1059,10 @@ func TestParseImport(t *testing.T) { return stmts( exprStmt( callExpr( - selectorExpr( + selectorMethodExpr( importExpr("mod1", p(1, 1)), - stringLit("func1", p(1, 16))), + stringLit("func1", p(1, 16)), + true), p(1, 21), p(1, 22), NoPos))) }) From d8e8aedf8471ef9e2ee13fe15a7cf7c1202c91e8 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Tue, 3 Nov 2020 10:14:10 -0500 Subject: [PATCH 13/15] added VM tests for methods in arrays --- vm_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/vm_test.go b/vm_test.go index d0fdcd17..57d71441 100644 --- a/vm_test.go +++ b/vm_test.go @@ -2827,6 +2827,34 @@ func TestMethod(t *testing.T) { m := { f1: func(r)() { return r == undefined}} out = m.f1()`, nil, false) + + expectRun(t, ` + arr := [ func(r)() { return r[1] }, 17 ] + out = arr[0]()`, + nil, 17) + + expectRun(t, ` + arr := [ func(r)() { return r == undefined} ] + out = arr[0]()`, + nil, false) + + expectRun(t, ` + arr := [ func(r)() { return r == undefined}] + f := arr[0] + out = f()`, + nil, true) + + //jump between functions in an array + expectRun(t, ` + arr := [ 0, 1, 3, "s", func(r)() { return r[1] }, 14, func(r)() { return r[4]() }, 17 ] + out = arr[6]()`, + nil, 1) + + expectRun(t, ` + arr := [[ "b", func(r)(){ return r[0] }, 14], func(r)(){ return r[0][1]() }, 17 ] + out = arr[1]()`, + nil, "b") + } func TestModuleBlockScopes(t *testing.T) { From 37ff48c5ca2839ab5fcbea56f105c517d82f6543 Mon Sep 17 00:00:00 2001 From: Dawson Davis Date: Sat, 7 Nov 2020 14:58:48 -0500 Subject: [PATCH 14/15] added arrays to documentation, as per geseq request --- docs/tutorial.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/tutorial.md b/docs/tutorial.md index 1e9c8474..050b6392 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -238,6 +238,10 @@ my_counter := { my_counter.counter += 4 my_counter.getCount() //returns 25 + +my_arr := [ 0, 5, 17, func(r)() { return r[1] } ] + +my_arr[3]() //returns 5 ``` Notably, and unlike Go, a method receiver is not dependent on being instanced inside @@ -283,6 +287,15 @@ m1.getC() //21 m1.c = 2 m1.getC() //2 m2.getC() //7 + +a1 := [ func (r)() { return r[1] + " " + r[2] }, "some", "other data" ] +a2 := copy(a1) + +a2[2] = "more things" + +a2[0]() //returns "some more things" +a1[0]() //returns "some other data" + ``` ## Variables and Scopes From f0f5431388d9577f5327ba317aa2c21114482b8e Mon Sep 17 00:00:00 2001 From: DawDavis <69989322+DawDavis@users.noreply.github.com> Date: Sat, 6 Mar 2021 02:46:48 -0500 Subject: [PATCH 15/15] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 732ff142..2b2909b8 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/d5/tengo/v2 +module github.com/DawDavis/tengo/v2 go 1.13