Skip to content

Commit

Permalink
spread last call argument v2 (#302)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ozan Hacıbekiroğlu authored Jun 8, 2020
1 parent 366c699 commit 7834251
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 35 deletions.
10 changes: 7 additions & 3 deletions compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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:
Expand Down Expand Up @@ -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)
}
Expand Down
37 changes: 28 additions & 9 deletions compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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()))
Expand All @@ -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 } }`,
Expand Down
6 changes: 6 additions & 0 deletions docs/tengo-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/Read–eval–print_loop)
Expand Down
38 changes: 38 additions & 0 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `=`.
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions parser/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand All @@ -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, ", ") + ")"
}

Expand Down
2 changes: 1 addition & 1 deletion parser/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ var OpcodeOperands = [...][]int{
OpImmutable: {},
OpIndex: {},
OpSliceIndex: {},
OpCall: {1},
OpCall: {1, 1},
OpReturn: {1},
OpGetLocal: {1},
OpSetLocal: {1},
Expand Down
17 changes: 11 additions & 6 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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,
}
}

Expand Down
43 changes: 32 additions & 11 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)))),
Expand All @@ -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)))),
Expand All @@ -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)),
Expand Down Expand Up @@ -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)))))
})
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
Loading

0 comments on commit 7834251

Please sign in to comment.