add variadic function parameters (#189)
* parsing variable function types * finished variadic functions * fix case where number of passed args to variadic function is 0 * remove extraneous OpVarArgs * allow multiple variables in variadic function declaration * fix IdentList string method to print multi-arg variadic functions correctly * round 2 of fix IdentList string method to print multi-arg variadic functions correctly round 2 of fix IdentList string method to print multi-arg variadic functions correctly * clean up tasks in OpCall handling, add tests for variadic closures * cleanup for pr - add syntax documentation - cleanup parseIdentList - cleanup OpCall handling for functions and closures - cleanup tests
This commit is contained in:
parent
2e0ea3a4c1
commit
adcf05d26f
12 changed files with 237 additions and 17 deletions
|
@ -8,9 +8,10 @@ import (
|
|||
|
||||
// IdentList represents a list of identifiers.
|
||||
type IdentList struct {
|
||||
LParen source.Pos
|
||||
List []*Ident
|
||||
RParen source.Pos
|
||||
LParen source.Pos
|
||||
VarArgs bool
|
||||
List []*Ident
|
||||
RParen source.Pos
|
||||
}
|
||||
|
||||
// Pos returns the position of first character belonging to the node.
|
||||
|
@ -50,8 +51,12 @@ func (n *IdentList) NumFields() int {
|
|||
|
||||
func (n *IdentList) String() string {
|
||||
var list []string
|
||||
for _, e := range n.List {
|
||||
list = append(list, e.String())
|
||||
for i, e := range n.List {
|
||||
if n.VarArgs && i == len(n.List)-1 {
|
||||
list = append(list, "..."+e.String())
|
||||
} else {
|
||||
list = append(list, e.String())
|
||||
}
|
||||
}
|
||||
|
||||
return "(" + strings.Join(list, ", ") + ")"
|
||||
|
|
37
compiler/ast/ident_list_test.go
Normal file
37
compiler/ast/ident_list_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package ast_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/d5/tengo/compiler/ast"
|
||||
)
|
||||
|
||||
func TestIdentListString(t *testing.T) {
|
||||
identListVar := &ast.IdentList{
|
||||
List: []*ast.Ident{
|
||||
{Name: "a"},
|
||||
{Name: "b"},
|
||||
{Name: "c"},
|
||||
},
|
||||
VarArgs: true,
|
||||
}
|
||||
|
||||
expectedVar := "(a, b, ...c)"
|
||||
if str := identListVar.String(); str != expectedVar {
|
||||
t.Fatalf("expected string of %#v to be %s, got %s", identListVar, expectedVar, str)
|
||||
}
|
||||
|
||||
identList := &ast.IdentList{
|
||||
List: []*ast.Ident{
|
||||
{Name: "a"},
|
||||
{Name: "b"},
|
||||
{Name: "c"},
|
||||
},
|
||||
VarArgs: false,
|
||||
}
|
||||
|
||||
expected := "(a, b, c)"
|
||||
if str := identList.String(); str != expected {
|
||||
t.Fatalf("expected string of %#v to be %s, got %s", identList, expected, str)
|
||||
}
|
||||
}
|
|
@ -477,6 +477,7 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||
Instructions: instructions,
|
||||
NumLocals: numLocals,
|
||||
NumParameters: len(node.Type.Params.List),
|
||||
VarArgs: node.Type.Params.VarArgs,
|
||||
SourceMap: sourceMap,
|
||||
}
|
||||
|
||||
|
|
|
@ -670,6 +670,23 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
compiler.MakeInstruction(compiler.OpReturn, 1)),
|
||||
intObject(24))))
|
||||
|
||||
expect(t, `varTest := func(...a) { return a }; varTest(1,2,3);`,
|
||||
bytecode(
|
||||
concat(
|
||||
compiler.MakeInstruction(compiler.OpConstant, 0),
|
||||
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
|
||||
compiler.MakeInstruction(compiler.OpGetGlobal, 0),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 1),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 2),
|
||||
compiler.MakeInstruction(compiler.OpConstant, 3),
|
||||
compiler.MakeInstruction(compiler.OpCall, 3),
|
||||
compiler.MakeInstruction(compiler.OpPop)),
|
||||
objectsArray(
|
||||
compiledFunction(1, 1,
|
||||
compiler.MakeInstruction(compiler.OpGetLocal, 0),
|
||||
compiler.MakeInstruction(compiler.OpReturn, 1)),
|
||||
intObject(1), intObject(2), intObject(3))))
|
||||
|
||||
expect(t, `f1 := func(a, b, c) { a; b; return c; }; f1(24, 25, 26);`,
|
||||
bytecode(
|
||||
concat(
|
||||
|
|
|
@ -610,19 +610,31 @@ func (p *Parser) parseIdentList() *ast.IdentList {
|
|||
|
||||
var params []*ast.Ident
|
||||
lparen := p.expect(token.LParen)
|
||||
isVarArgs := false
|
||||
if p.token != token.RParen {
|
||||
params = append(params, p.parseIdent())
|
||||
for p.token == token.Comma {
|
||||
if p.token == token.Ellipsis {
|
||||
isVarArgs = true
|
||||
p.next()
|
||||
}
|
||||
|
||||
params = append(params, p.parseIdent())
|
||||
for !isVarArgs && p.token == token.Comma {
|
||||
p.next()
|
||||
if p.token == token.Ellipsis {
|
||||
isVarArgs = true
|
||||
p.next()
|
||||
}
|
||||
params = append(params, p.parseIdent())
|
||||
}
|
||||
}
|
||||
|
||||
rparen := p.expect(token.RParen)
|
||||
|
||||
return &ast.IdentList{
|
||||
LParen: lparen,
|
||||
RParen: rparen,
|
||||
List: params,
|
||||
LParen: lparen,
|
||||
RParen: rparen,
|
||||
VarArgs: isVarArgs,
|
||||
List: params,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ func TestCall(t *testing.T) {
|
|||
funcType(
|
||||
identList(
|
||||
p(1, 5), p(1, 10),
|
||||
false,
|
||||
ident("a", p(1, 6)),
|
||||
ident("b", p(1, 9))),
|
||||
p(1, 1)),
|
||||
|
|
|
@ -16,7 +16,7 @@ func TestFunction(t *testing.T) {
|
|||
exprs(
|
||||
funcLit(
|
||||
funcType(
|
||||
identList(p(1, 9), p(1, 17),
|
||||
identList(p(1, 9), p(1, 17), false,
|
||||
ident("b", p(1, 10)),
|
||||
ident("c", p(1, 13)),
|
||||
ident("d", p(1, 16))),
|
||||
|
@ -27,3 +27,60 @@ func TestFunction(t *testing.T) {
|
|||
p(1, 3)))
|
||||
})
|
||||
}
|
||||
|
||||
func TestVariableFunction(t *testing.T) {
|
||||
expect(t, "a = func(...args) { return args }", func(p pfn) []ast.Stmt {
|
||||
return stmts(
|
||||
assignStmt(
|
||||
exprs(
|
||||
ident("a", p(1, 1))),
|
||||
exprs(
|
||||
funcLit(
|
||||
funcType(
|
||||
identList(
|
||||
p(1, 9), p(1, 17),
|
||||
true,
|
||||
ident("args", p(1, 13)),
|
||||
), p(1, 5)),
|
||||
blockStmt(p(1, 19), p(1, 33),
|
||||
returnStmt(p(1, 21),
|
||||
ident("args", p(1, 28)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
token.Assign,
|
||||
p(1, 3)))
|
||||
})
|
||||
}
|
||||
|
||||
func TestVariableFunctionWithArgs(t *testing.T) {
|
||||
expect(t, "a = func(x, y, ...z) { return z }", func(p pfn) []ast.Stmt {
|
||||
return stmts(
|
||||
assignStmt(
|
||||
exprs(
|
||||
ident("a", p(1, 1))),
|
||||
exprs(
|
||||
funcLit(
|
||||
funcType(
|
||||
identList(
|
||||
p(1, 9), p(1, 20),
|
||||
true,
|
||||
ident("x", p(1, 10)),
|
||||
ident("y", p(1, 13)),
|
||||
ident("z", p(1, 19)),
|
||||
), p(1, 5)),
|
||||
blockStmt(p(1, 22), p(1, 33),
|
||||
returnStmt(p(1, 24),
|
||||
ident("z", p(1, 31)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
token.Assign,
|
||||
p(1, 3)))
|
||||
})
|
||||
|
||||
expectError(t, "a = func(x, y, ...z, invalid) { return z }")
|
||||
expectError(t, "a = func(...args, invalid) { return args }")
|
||||
}
|
||||
|
|
|
@ -180,8 +180,8 @@ func ident(name string, pos source.Pos) *ast.Ident {
|
|||
return &ast.Ident{Name: name, NamePos: pos}
|
||||
}
|
||||
|
||||
func identList(opening, closing source.Pos, list ...*ast.Ident) *ast.IdentList {
|
||||
return &ast.IdentList{List: list, LParen: opening, RParen: closing}
|
||||
func identList(opening, closing source.Pos, varArgs bool, list ...*ast.Ident) *ast.IdentList {
|
||||
return &ast.IdentList{VarArgs: varArgs, List: list, LParen: opening, RParen: closing}
|
||||
}
|
||||
|
||||
func binaryExpr(x, y ast.Expr, op token.Token, pos source.Pos) *ast.BinaryExpr {
|
||||
|
|
|
@ -154,6 +154,29 @@ func my_func(arg1, arg2) { // illegal
|
|||
}
|
||||
```
|
||||
|
||||
Tengo also supports variadic functions/closures:
|
||||
|
||||
```golang
|
||||
variadic := func (a, b, ...c) {
|
||||
return [a, b, c]
|
||||
}
|
||||
variadic(1, 2, 3, 4) // [1, 2, [3, 4]]
|
||||
|
||||
variadicClosure := func(a) {
|
||||
return func(b, ...c) {
|
||||
return [a, b, c]
|
||||
}
|
||||
}
|
||||
variadicClosure(1)(2, 3, 4) // [1, 2, [3, 4]]
|
||||
```
|
||||
|
||||
Only the last parameter can be variadic. The following code is also illegal:
|
||||
|
||||
```golang
|
||||
// illegal, because a is variadic and is not the last parameter
|
||||
illegal := func(a..., b) { /*... */ }
|
||||
```
|
||||
|
||||
## Variables and Scopes
|
||||
|
||||
A value can be assigned to a variable using assignment operator `:=` and `=`.
|
||||
|
|
|
@ -10,6 +10,7 @@ type CompiledFunction struct {
|
|||
Instructions []byte
|
||||
NumLocals int // number of local variables (including function parameters)
|
||||
NumParameters int
|
||||
VarArgs bool
|
||||
SourceMap map[int]source.Pos
|
||||
}
|
||||
|
||||
|
@ -34,6 +35,7 @@ func (o *CompiledFunction) Copy() Object {
|
|||
Instructions: append([]byte{}, o.Instructions...),
|
||||
NumLocals: o.NumLocals,
|
||||
NumParameters: o.NumParameters,
|
||||
VarArgs: o.VarArgs,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -642,9 +642,31 @@ func (v *VM) run() {
|
|||
|
||||
switch callee := value.(type) {
|
||||
case *objects.Closure:
|
||||
if callee.Fn.VarArgs {
|
||||
// if the closure is variadic,
|
||||
// roll up all variadic parameters into an array
|
||||
realArgs := callee.Fn.NumParameters - 1
|
||||
varArgs := numArgs - realArgs
|
||||
if varArgs >= 0 {
|
||||
numArgs = realArgs + 1
|
||||
args := make([]objects.Object, varArgs)
|
||||
spStart := v.sp - varArgs
|
||||
for i := spStart; i < v.sp; i++ {
|
||||
args[i-spStart] = v.stack[i]
|
||||
}
|
||||
v.stack[spStart] = &objects.Array{Value: args}
|
||||
v.sp = spStart + 1
|
||||
}
|
||||
}
|
||||
|
||||
if numArgs != callee.Fn.NumParameters {
|
||||
v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d",
|
||||
callee.Fn.NumParameters, numArgs)
|
||||
if callee.Fn.VarArgs {
|
||||
v.err = fmt.Errorf("wrong number of arguments: want>=%d, got=%d",
|
||||
callee.Fn.NumParameters-1, numArgs)
|
||||
} else {
|
||||
v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d",
|
||||
callee.Fn.NumParameters, numArgs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -674,9 +696,31 @@ func (v *VM) run() {
|
|||
v.sp = v.sp - numArgs + callee.Fn.NumLocals
|
||||
|
||||
case *objects.CompiledFunction:
|
||||
if callee.VarArgs {
|
||||
// if the closure is variadic,
|
||||
// roll up all variadic parameters into an array
|
||||
realArgs := callee.NumParameters - 1
|
||||
varArgs := numArgs - realArgs
|
||||
if varArgs >= 0 {
|
||||
numArgs = realArgs + 1
|
||||
args := make([]objects.Object, varArgs)
|
||||
spStart := v.sp - varArgs
|
||||
for i := spStart; i < v.sp; i++ {
|
||||
args[i-spStart] = v.stack[i]
|
||||
}
|
||||
v.stack[spStart] = &objects.Array{Value: args}
|
||||
v.sp = spStart + 1
|
||||
}
|
||||
}
|
||||
|
||||
if numArgs != callee.NumParameters {
|
||||
v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d",
|
||||
callee.NumParameters, numArgs)
|
||||
if callee.VarArgs {
|
||||
v.err = fmt.Errorf("wrong number of arguments: want>=%d, got=%d",
|
||||
callee.NumParameters-1, numArgs)
|
||||
} else {
|
||||
v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d",
|
||||
callee.NumParameters, numArgs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,27 @@ func TestFunction(t *testing.T) {
|
|||
expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, nil, objects.UndefinedValue)
|
||||
expect(t, `f := func(x) { x; }; out = f(5);`, nil, objects.UndefinedValue)
|
||||
|
||||
expect(t, `f := func(...x) { return x; }; out = f(1,2,3);`, nil, ARR{1, 2, 3})
|
||||
|
||||
expect(t, `f := func(a, b, ...x) { return [a, b, x]; }; out = f(8,9,1,2,3);`, nil, ARR{8, 9, ARR{1, 2, 3}})
|
||||
|
||||
expect(t, `f := func(v) { x := 2; return func(a, ...b){ return [a, b, v+x]}; }; out = f(5)("a", "b");`, nil,
|
||||
ARR{"a", ARR{"b"}, 7})
|
||||
|
||||
expect(t, `f := func(...x) { return x; }; out = f();`, nil, &objects.Array{Value: []objects.Object{}})
|
||||
|
||||
expect(t, `f := func(a, b, ...x) { return [a, b, x]; }; out = f(8, 9);`, nil,
|
||||
ARR{8, 9, ARR{}})
|
||||
|
||||
expect(t, `f := func(v) { x := 2; return func(a, ...b){ return [a, b, v+x]}; }; out = f(5)("a");`, nil,
|
||||
ARR{"a", ARR{}, 7})
|
||||
|
||||
expectError(t, `f := func(a, b, ...x) { return [a, b, x]; }; f();`, nil,
|
||||
"Runtime Error: wrong number of arguments: want>=2, got=0\n\tat test:1:46")
|
||||
|
||||
expectError(t, `f := func(a, b, ...x) { return [a, b, x]; }; f(1);`, nil,
|
||||
"Runtime Error: wrong number of arguments: want>=2, got=1\n\tat test:1:46")
|
||||
|
||||
expect(t, `f := func(x) { return x; }; out = f(5);`, nil, 5)
|
||||
expect(t, `f := func(x) { return x * 2; }; out = f(5);`, nil, 10)
|
||||
expect(t, `f := func(x, y) { return x + y; }; out = f(5, 5);`, nil, 10)
|
||||
|
|
Loading…
Reference in a new issue