spread last call argument v2 ()

This commit is contained in:
Ozan Hacıbekiroğlu 2020-06-08 19:54:24 +03:00 committed by GitHub
parent 366c69902f
commit 7834251c84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 213 additions and 35 deletions

View file

@ -506,7 +506,11 @@ func (c *Compiler) Compile(node parser.Node) error {
return err
}
}
c.emit(node, parser.OpCall, len(node.Args))
ellipsis := 0
if node.Ellipsis.IsValid() {
ellipsis = 1
}
c.emit(node, parser.OpCall, len(node.Args), ellipsis)
case *parser.ImportExpr:
if node.ModuleName == "" {
return c.errorf(node, "empty module name")
@ -526,7 +530,7 @@ func (c *Compiler) Compile(node parser.Node) error {
return err
}
c.emit(node, parser.OpConstant, c.addConstant(compiled))
c.emit(node, parser.OpCall, 0)
c.emit(node, parser.OpCall, 0, 0)
case Object: // builtin module
c.emit(node, parser.OpConstant, c.addConstant(v))
default:
@ -556,7 +560,7 @@ func (c *Compiler) Compile(node parser.Node) error {
return err
}
c.emit(node, parser.OpConstant, c.addConstant(compiled))
c.emit(node, parser.OpCall, 0)
c.emit(node, parser.OpCall, 0, 0)
} else {
return c.errorf(node, "module '%s' not found", node.ModuleName)
}

View file

@ -483,6 +483,25 @@ func TestCompiler_Compile(t *testing.T) {
intObject(3),
intObject(0))))
expectCompile(t, `f1 := func(a) { return a }; f1([1, 2]...);`,
bytecode(
concatInsts(
tengo.MakeInstruction(parser.OpConstant, 0),
tengo.MakeInstruction(parser.OpSetGlobal, 0),
tengo.MakeInstruction(parser.OpGetGlobal, 0),
tengo.MakeInstruction(parser.OpConstant, 1),
tengo.MakeInstruction(parser.OpConstant, 2),
tengo.MakeInstruction(parser.OpArray, 2),
tengo.MakeInstruction(parser.OpCall, 1, 1),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray(
compiledFunction(1, 1,
tengo.MakeInstruction(parser.OpGetLocal, 0),
tengo.MakeInstruction(parser.OpReturn, 1)),
intObject(1),
intObject(2))))
expectCompile(t, `func() { return 5 + 10 }`,
bytecode(
concatInsts(
@ -601,7 +620,7 @@ func TestCompiler_Compile(t *testing.T) {
bytecode(
concatInsts(
tengo.MakeInstruction(parser.OpConstant, 1),
tengo.MakeInstruction(parser.OpCall, 0),
tengo.MakeInstruction(parser.OpCall, 0, 0),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray(
@ -615,7 +634,7 @@ func TestCompiler_Compile(t *testing.T) {
bytecode(
concatInsts(
tengo.MakeInstruction(parser.OpConstant, 1),
tengo.MakeInstruction(parser.OpCall, 0),
tengo.MakeInstruction(parser.OpCall, 0, 0),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray(
@ -630,7 +649,7 @@ func TestCompiler_Compile(t *testing.T) {
tengo.MakeInstruction(parser.OpConstant, 1),
tengo.MakeInstruction(parser.OpSetGlobal, 0),
tengo.MakeInstruction(parser.OpGetGlobal, 0),
tengo.MakeInstruction(parser.OpCall, 0),
tengo.MakeInstruction(parser.OpCall, 0, 0),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray(
@ -646,7 +665,7 @@ func TestCompiler_Compile(t *testing.T) {
tengo.MakeInstruction(parser.OpConstant, 1),
tengo.MakeInstruction(parser.OpSetGlobal, 0),
tengo.MakeInstruction(parser.OpGetGlobal, 0),
tengo.MakeInstruction(parser.OpCall, 0),
tengo.MakeInstruction(parser.OpCall, 0, 0),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray(
@ -710,7 +729,7 @@ func TestCompiler_Compile(t *testing.T) {
tengo.MakeInstruction(parser.OpSetGlobal, 0),
tengo.MakeInstruction(parser.OpGetGlobal, 0),
tengo.MakeInstruction(parser.OpConstant, 1),
tengo.MakeInstruction(parser.OpCall, 1),
tengo.MakeInstruction(parser.OpCall, 1, 0),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray(
@ -728,7 +747,7 @@ func TestCompiler_Compile(t *testing.T) {
tengo.MakeInstruction(parser.OpConstant, 1),
tengo.MakeInstruction(parser.OpConstant, 2),
tengo.MakeInstruction(parser.OpConstant, 3),
tengo.MakeInstruction(parser.OpCall, 3),
tengo.MakeInstruction(parser.OpCall, 3, 0),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray(
@ -746,7 +765,7 @@ func TestCompiler_Compile(t *testing.T) {
tengo.MakeInstruction(parser.OpConstant, 1),
tengo.MakeInstruction(parser.OpConstant, 2),
tengo.MakeInstruction(parser.OpConstant, 3),
tengo.MakeInstruction(parser.OpCall, 3),
tengo.MakeInstruction(parser.OpCall, 3, 0),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray(
@ -782,7 +801,7 @@ func TestCompiler_Compile(t *testing.T) {
concatInsts(
tengo.MakeInstruction(parser.OpGetBuiltin, 0),
tengo.MakeInstruction(parser.OpArray, 0),
tengo.MakeInstruction(parser.OpCall, 1),
tengo.MakeInstruction(parser.OpCall, 1, 0),
tengo.MakeInstruction(parser.OpPop),
tengo.MakeInstruction(parser.OpSuspend)),
objectsArray()))
@ -797,7 +816,7 @@ func TestCompiler_Compile(t *testing.T) {
compiledFunction(0, 0,
tengo.MakeInstruction(parser.OpGetBuiltin, 0),
tengo.MakeInstruction(parser.OpArray, 0),
tengo.MakeInstruction(parser.OpCall, 1),
tengo.MakeInstruction(parser.OpCall, 1, 0),
tengo.MakeInstruction(parser.OpReturn, 1)))))
expectCompile(t, `func(a) { func(b) { return a + b } }`,

View file

@ -52,6 +52,12 @@ chmod +x myapp.tengo
**Note: Your source file must have `.tengo` extension.**
## Resolving Relative Import Paths
If there are tengo source module files which are imported with relative import
paths, CLI has `-resolve` flag. Flag enables to import a module relative to
importing file. This behavior will be default at version 3.
## Tengo REPL
You can run Tengo [REPL](https://en.wikipedia.org/wiki/Readevalprint_loop)

View file

@ -194,6 +194,30 @@ Only the last parameter can be variadic. The following code is also illegal:
illegal := func(a..., b) { /*... */ }
```
When calling a function, the number of passing arguments must match that of
function definition.
```golang
f := func(a, b) {}
f(1, 2, 3) // Runtime Error: wrong number of arguments: want=2, got=3
```
Like Go, you can use ellipsis `...` to pass array-type value as its last parameter:
```golang
f1 := func(a, b, c) { return a + b + c }
f1([1, 2, 3]...) // => 6
f1(1, [2, 3]...) // => 6
f1(1, 2, [3]...) // => 6
f1([1, 2]...) // Runtime Error: wrong number of arguments: want=3, got=2
f2 := func(a, ...b) {}
f2(1) // valid; a = 1, b = []
f2(1, 2) // valid; a = 1, b = [2]
f2(1, 2, 3) // valid; a = 1, b = [2, 3]
f2([1, 2, 3]...) // valid; a = 1, b = [2, 3]
```
## Variables and Scopes
A value can be assigned to a variable using assignment operator `:=` and `=`.
@ -382,6 +406,20 @@ d := "hello world"[2:10] // == "llo worl"
c := [1, 2, 3, 4, 5][-1:10] // == [1, 2, 3, 4, 5]
```
**Note: Keywords cannot be used as selectors.**
```golang
a := {in: true} // Parse Error: expected map key, found 'in'
a.func = "" // Parse Error: expected selector, found 'func'
```
Use double quotes and indexer to use keywords with maps.
```golang
a := {"in": true}
a["func"] = ""
```
## Statements
### If Statement

View file

@ -111,10 +111,11 @@ func (e *BoolLit) String() string {
// CallExpr represents a function call expression.
type CallExpr struct {
Func Expr
LParen Pos
Args []Expr
RParen Pos
Func Expr
LParen Pos
Args []Expr
Ellipsis Pos
RParen Pos
}
func (e *CallExpr) exprNode() {}
@ -134,6 +135,9 @@ func (e *CallExpr) String() string {
for _, e := range e.Args {
args = append(args, e.String())
}
if len(args) > 0 && e.Ellipsis.IsValid() {
args[len(args)-1] = args[len(args)-1] + "..."
}
return e.Func.String() + "(" + strings.Join(args, ", ") + ")"
}

View file

@ -120,7 +120,7 @@ var OpcodeOperands = [...][]int{
OpImmutable: {},
OpIndex: {},
OpSliceIndex: {},
OpCall: {1},
OpCall: {1, 1},
OpReturn: {1},
OpGetLocal: {1},
OpSetLocal: {1},

View file

@ -270,9 +270,13 @@ func (p *Parser) parseCall(x Expr) *CallExpr {
p.exprLevel++
var list []Expr
for p.token != token.RParen && p.token != token.EOF {
var ellipsis Pos
for p.token != token.RParen && p.token != token.EOF && !ellipsis.IsValid() {
list = append(list, p.parseExpr())
if p.token == token.Ellipsis {
ellipsis = p.pos
p.next()
}
if !p.expectComma(token.RParen, "call argument") {
break
}
@ -281,10 +285,11 @@ func (p *Parser) parseCall(x Expr) *CallExpr {
p.exprLevel--
rparen := p.expect(token.RParen)
return &CallExpr{
Func: x,
LParen: lparen,
RParen: rparen,
Args: list,
Func: x,
LParen: lparen,
RParen: rparen,
Ellipsis: ellipsis,
Args: list,
}
}

View file

@ -290,12 +290,23 @@ func TestParseCall(t *testing.T) {
exprStmt(
callExpr(
ident("add", p(1, 1)),
p(1, 4), p(1, 12),
p(1, 4), p(1, 12), NoPos,
intLit(1, p(1, 5)),
intLit(2, p(1, 8)),
intLit(3, p(1, 11)))))
})
expectParse(t, "add(1, 2, v...)", func(p pfn) []Stmt {
return stmts(
exprStmt(
callExpr(
ident("add", p(1, 1)),
p(1, 4), p(1, 15), p(1, 12),
intLit(1, p(1, 5)),
intLit(2, p(1, 8)),
ident("v", p(1, 11)))))
})
expectParse(t, "a = add(1, 2, 3)", func(p pfn) []Stmt {
return stmts(
assignStmt(
@ -304,7 +315,7 @@ func TestParseCall(t *testing.T) {
exprs(
callExpr(
ident("add", p(1, 5)),
p(1, 8), p(1, 16),
p(1, 8), p(1, 16), NoPos,
intLit(1, p(1, 9)),
intLit(2, p(1, 12)),
intLit(3, p(1, 15)))),
@ -321,7 +332,7 @@ func TestParseCall(t *testing.T) {
exprs(
callExpr(
ident("add", p(1, 8)),
p(1, 11), p(1, 19),
p(1, 11), p(1, 19), NoPos,
intLit(1, p(1, 12)),
intLit(2, p(1, 15)),
intLit(3, p(1, 18)))),
@ -334,7 +345,7 @@ func TestParseCall(t *testing.T) {
exprStmt(
callExpr(
ident("add", p(1, 1)),
p(1, 4), p(1, 26),
p(1, 4), p(1, 26), NoPos,
binaryExpr(
ident("a", p(1, 5)),
intLit(1, p(1, 9)),
@ -381,7 +392,7 @@ func TestParseCall(t *testing.T) {
ident("b", p(1, 18)),
token.Add,
p(1, 16))))),
p(1, 21), p(1, 26),
p(1, 21), p(1, 26), NoPos,
intLit(1, p(1, 22)),
intLit(2, p(1, 25)))))
})
@ -393,7 +404,7 @@ func TestParseCall(t *testing.T) {
selectorExpr(
ident("a", p(1, 1)),
stringLit("b", p(1, 3))),
p(1, 4), p(1, 5))))
p(1, 4), p(1, 5), NoPos)))
})
expectParse(t, `a.b.c()`, func(p pfn) []Stmt {
@ -405,7 +416,7 @@ func TestParseCall(t *testing.T) {
ident("a", p(1, 1)),
stringLit("b", p(1, 3))),
stringLit("c", p(1, 5))),
p(1, 6), p(1, 7))))
p(1, 6), p(1, 7), NoPos)))
})
expectParse(t, `a["b"].c()`, func(p pfn) []Stmt {
@ -418,8 +429,17 @@ func TestParseCall(t *testing.T) {
stringLit("b", p(1, 3)),
p(1, 2), p(1, 6)),
stringLit("c", p(1, 8))),
p(1, 9), p(1, 10))))
p(1, 9), p(1, 10), NoPos)))
})
expectParseError(t, `add(...a, 1)`)
expectParseError(t, `add(a..., 1)`)
expectParseError(t, `add(a..., b...)`)
expectParseError(t, `add(1, a..., b...)`)
expectParseError(t, `add(...)`)
expectParseError(t, `add(1, ...)`)
expectParseError(t, `add(1, ..., )`)
expectParseError(t, `add(...a)`)
}
func TestParseChar(t *testing.T) {
@ -1001,7 +1021,7 @@ func TestParseImport(t *testing.T) {
selectorExpr(
importExpr("mod1", p(1, 1)),
stringLit("func1", p(1, 16))),
p(1, 21), p(1, 22))))
p(1, 21), p(1, 22), NoPos)))
})
expectParse(t, `for x, y in import("mod1") {}`, func(p pfn) []Stmt {
@ -1753,10 +1773,11 @@ func parenExpr(x Expr, lparen, rparen Pos) *ParenExpr {
func callExpr(
f Expr,
lparen, rparen Pos,
lparen, rparen, ellipsis Pos,
args ...Expr,
) *CallExpr {
return &CallExpr{Func: f, LParen: lparen, RParen: rparen, Args: args}
return &CallExpr{Func: f, LParen: lparen, RParen: rparen,
Ellipsis: ellipsis, Args: args}
}
func indexExpr(

26
vm.go
View file

@ -537,12 +537,36 @@ func (v *VM) run() {
}
case parser.OpCall:
numArgs := int(v.curInsts[v.ip+1])
v.ip++
spread := int(v.curInsts[v.ip+2])
v.ip += 2
value := v.stack[v.sp-1-numArgs]
if !value.CanCall() {
v.err = fmt.Errorf("not callable: %s", value.TypeName())
return
}
if spread == 1 {
v.sp--
switch arr := v.stack[v.sp].(type) {
case *Array:
for _, item := range arr.Value {
v.stack[v.sp] = item
v.sp++
}
numArgs += len(arr.Value) - 1
case *ImmutableArray:
for _, item := range arr.Value {
v.stack[v.sp] = item
v.sp++
}
numArgs += len(arr.Value) - 1
default:
v.err = fmt.Errorf("not an array: %s", arr.TypeName())
return
}
}
if callee, ok := value.(*CompiledFunction); ok {
if callee.VarArgs {
// if the closure is variadic,

View file

@ -3472,6 +3472,63 @@ func() {
}()`, nil, 25)
}
func TestSpread(t *testing.T) {
expectRun(t, `
f := func(...a) {
return append(a, 3)
}
out = f([1, 2]...)
`, nil, ARR{1, 2, 3})
expectRun(t, `
f := func(a, ...b) {
return append([a], append(b, 3)...)
}
out = f([1, 2]...)
`, nil, ARR{1, 2, 3})
expectRun(t, `
f := func(a, ...b) {
return append(append([a], b), 3)
}
out = f(1, [2]...)
`, nil, ARR{1, ARR{2}, 3})
expectRun(t, `
f1 := func(...a){
return append([3], a...)
}
f2 := func(a, ...b) {
return f1(append([a], b...)...)
}
out = f2([1, 2]...)
`, nil, ARR{3, 1, 2})
expectRun(t, `
f := func(a, ...b) {
return func(...a) {
return append([3], append(a, 4)...)
}(a, b...)
}
out = f([1, 2]...)
`, nil, ARR{3, 1, 2, 4})
expectRun(t, `
f := func(a, ...b) {
c := append(b, 4)
return func(){
return append(append([a], b...), c...)
}()
}
out = f(1, immutable([2, 3])...)
`, nil, ARR{1, 2, 3, 2, 3, 4})
expectError(t, `func(a) {}([1, 2]...)`, nil,
"Runtime Error: wrong number of arguments: want=1, got=2")
expectError(t, `func(a, b, c) {}([1, 2]...)`, nil,
"Runtime Error: wrong number of arguments: want=3, got=2")
}
func expectRun(
t *testing.T,
input string,