commit
93917a4645
9 changed files with 205 additions and 1 deletions
30
compiler/ast/cond_expr.go
Normal file
30
compiler/ast/cond_expr.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"github.com/d5/tengo/compiler/source"
|
||||
)
|
||||
|
||||
// CondExpr represents a ternary conditional expression.
|
||||
type CondExpr struct {
|
||||
Cond Expr
|
||||
True Expr
|
||||
False Expr
|
||||
QuestionPos source.Pos
|
||||
ColonPos source.Pos
|
||||
}
|
||||
|
||||
func (e *CondExpr) exprNode() {}
|
||||
|
||||
// Pos returns the position of first character belonging to the node.
|
||||
func (e *CondExpr) Pos() source.Pos {
|
||||
return e.Cond.Pos()
|
||||
}
|
||||
|
||||
// End returns the position of first character immediately after the node.
|
||||
func (e *CondExpr) End() source.Pos {
|
||||
return e.False.End()
|
||||
}
|
||||
|
||||
func (e *CondExpr) String() string {
|
||||
return "(" + e.Cond.String() + " ? " + e.True.String() + " : " + e.False.String() + ")"
|
||||
}
|
|
@ -480,6 +480,33 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||
}
|
||||
|
||||
c.emit(OpImmutable)
|
||||
|
||||
case *ast.CondExpr:
|
||||
if err := c.Compile(node.Cond); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// first jump placeholder
|
||||
jumpPos1 := c.emit(OpJumpFalsy, 0)
|
||||
|
||||
if err := c.Compile(node.True); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// second jump placeholder
|
||||
jumpPos2 := c.emit(OpJump, 0)
|
||||
|
||||
// update first jump offset
|
||||
curPos := len(c.currentInstructions())
|
||||
c.changeOperand(jumpPos1, curPos)
|
||||
|
||||
if err := c.Compile(node.False); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update second jump offset
|
||||
curPos = len(c.currentInstructions())
|
||||
c.changeOperand(jumpPos2, curPos)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -82,7 +82,14 @@ func (p *Parser) parseExpr() ast.Expr {
|
|||
defer un(trace(p, "Expression"))
|
||||
}
|
||||
|
||||
return p.parseBinaryExpr(token.LowestPrec + 1)
|
||||
expr := p.parseBinaryExpr(token.LowestPrec + 1)
|
||||
|
||||
// ternary conditional expression
|
||||
if p.token == token.Question {
|
||||
return p.parseCondExpr(expr)
|
||||
}
|
||||
|
||||
return expr
|
||||
}
|
||||
|
||||
func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr {
|
||||
|
@ -101,6 +108,7 @@ func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr {
|
|||
pos := p.expect(op)
|
||||
|
||||
y := p.parseBinaryExpr(prec + 1)
|
||||
|
||||
x = &ast.BinaryExpr{
|
||||
LHS: x,
|
||||
RHS: y,
|
||||
|
@ -110,6 +118,24 @@ func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseCondExpr(cond ast.Expr) ast.Expr {
|
||||
questionPos := p.expect(token.Question)
|
||||
|
||||
trueExpr := p.parseExpr()
|
||||
|
||||
colonPos := p.expect(token.Colon)
|
||||
|
||||
falseExpr := p.parseExpr()
|
||||
|
||||
return &ast.CondExpr{
|
||||
Cond: cond,
|
||||
True: trueExpr,
|
||||
False: falseExpr,
|
||||
QuestionPos: questionPos,
|
||||
ColonPos: colonPos,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseUnaryExpr() ast.Expr {
|
||||
if p.trace {
|
||||
defer un(trace(p, "UnaryExpression"))
|
||||
|
|
48
compiler/parser/parser_cond_test.go
Normal file
48
compiler/parser/parser_cond_test.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package parser_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/d5/tengo/compiler/ast"
|
||||
)
|
||||
|
||||
func TestCondExpr(t *testing.T) {
|
||||
expect(t, "a ? b : c", func(p pfn) []ast.Stmt {
|
||||
return stmts(
|
||||
exprStmt(
|
||||
condExpr(
|
||||
ident("a", p(1, 1)),
|
||||
ident("b", p(1, 5)),
|
||||
ident("c", p(1, 9)),
|
||||
p(1, 3),
|
||||
p(1, 7))))
|
||||
})
|
||||
expect(t, `a ?
|
||||
b :
|
||||
c`, func(p pfn) []ast.Stmt {
|
||||
return stmts(
|
||||
exprStmt(
|
||||
condExpr(
|
||||
ident("a", p(1, 1)),
|
||||
ident("b", p(1, 5)),
|
||||
ident("c", p(1, 9)),
|
||||
p(1, 3),
|
||||
p(1, 7))))
|
||||
})
|
||||
|
||||
expectString(t, `a ? b : c`, "(a ? b : c)")
|
||||
expectString(t, `a + b ? c - d : e * f`, "((a + b) ? (c - d) : (e * f))")
|
||||
expectString(t, `a == b ? c + (d / e) : f ? g : h + i`, "((a == b) ? (c + ((d / e))) : (f ? g : (h + i)))")
|
||||
expectString(t, `(a + b) ? (c - d) : (e * f)`, "(((a + b)) ? ((c - d)) : ((e * f)))")
|
||||
expectString(t, `a + (b ? c : d) - e`, "((a + ((b ? c : d))) - e)")
|
||||
expectString(t, `a ? b ? c : d : e`, "(a ? (b ? c : d) : e)")
|
||||
expectString(t, `a := b ? c : d`, "a := (b ? c : d)")
|
||||
expectString(t, `x := a ? b ? c : d : e`, "x := (a ? (b ? c : d) : e)")
|
||||
|
||||
// ? : should be at the end of each line if it's multi-line
|
||||
expectError(t, `a
|
||||
? b
|
||||
: c`)
|
||||
expectError(t, `a ? (b : e)`)
|
||||
expectError(t, `(a ? b) : e`)
|
||||
}
|
|
@ -184,6 +184,10 @@ func binaryExpr(x, y ast.Expr, op token.Token, pos source.Pos) *ast.BinaryExpr {
|
|||
return &ast.BinaryExpr{LHS: x, RHS: y, Token: op, TokenPos: pos}
|
||||
}
|
||||
|
||||
func condExpr(cond, trueExpr, falseExpr ast.Expr, questionPos, colonPos source.Pos) *ast.CondExpr {
|
||||
return &ast.CondExpr{Cond: cond, True: trueExpr, False: falseExpr, QuestionPos: questionPos, ColonPos: colonPos}
|
||||
}
|
||||
|
||||
func unaryExpr(x ast.Expr, op token.Token, pos source.Pos) *ast.UnaryExpr {
|
||||
return &ast.UnaryExpr{Expr: x, Token: op, TokenPos: pos}
|
||||
}
|
||||
|
@ -398,6 +402,12 @@ func equalExpr(t *testing.T, expected, actual ast.Expr) bool {
|
|||
assert.Equal(t, int(expected.ErrorPos), int(actual.(*ast.ErrorExpr).ErrorPos)) &&
|
||||
assert.Equal(t, int(expected.LParen), int(actual.(*ast.ErrorExpr).LParen)) &&
|
||||
assert.Equal(t, int(expected.RParen), int(actual.(*ast.ErrorExpr).RParen))
|
||||
case *ast.CondExpr:
|
||||
return equalExpr(t, expected.Cond, actual.(*ast.CondExpr).Cond) &&
|
||||
equalExpr(t, expected.True, actual.(*ast.CondExpr).True) &&
|
||||
equalExpr(t, expected.False, actual.(*ast.CondExpr).False) &&
|
||||
assert.Equal(t, expected.QuestionPos, actual.(*ast.CondExpr).QuestionPos) &&
|
||||
assert.Equal(t, expected.ColonPos, actual.(*ast.CondExpr).ColonPos)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %T", expected))
|
||||
}
|
||||
|
|
|
@ -125,6 +125,8 @@ func (s *Scanner) Scan() (tok token.Token, literal string, pos source.Pos) {
|
|||
}
|
||||
case ',':
|
||||
tok = token.Comma
|
||||
case '?':
|
||||
tok = token.Question
|
||||
case ';':
|
||||
tok = token.Semicolon
|
||||
literal = ";"
|
||||
|
|
|
@ -64,6 +64,7 @@ const (
|
|||
RBrace // }
|
||||
Semicolon // ;
|
||||
Colon // :
|
||||
Question // ?
|
||||
_operatorEnd
|
||||
_keywordBeg
|
||||
Break
|
||||
|
@ -142,6 +143,7 @@ var tokens = [...]string{
|
|||
RBrace: "}",
|
||||
Semicolon: ";",
|
||||
Colon: ":",
|
||||
Question: "?",
|
||||
Break: "break",
|
||||
Case: "case",
|
||||
Continue: "continue",
|
||||
|
|
|
@ -94,6 +94,21 @@ d := "hello world"[2:10] // == "llo worl"
|
|||
```
|
||||
> [Run in Playground](https://tengolang.com/?s=214ab490bb24549578770984985f6b161aed915d)
|
||||
|
||||
## Conditional Expression
|
||||
|
||||
Tengo supports the ternary conditional expression (`cond ? expr1 : expr2`):
|
||||
|
||||
```golang
|
||||
a := true ? 1 : -1 // a == 1
|
||||
|
||||
min := func(a, b) {
|
||||
return a < b ? a : b
|
||||
}
|
||||
b := min(5, 10) // b == 5
|
||||
```
|
||||
|
||||
> [Run in Playground](https://tengolang.com/?s=24724cc03e8ef2e56b7851017f8c2a577897961e)
|
||||
|
||||
## Functions
|
||||
|
||||
In Tengo, functions are first-class citizen, and, it also supports closures, functions that captures variables in outer scopes. In the following example, the function returned from `adder` is capturing `base` variable.
|
||||
|
|
44
runtime/vm_cond_test.go
Normal file
44
runtime/vm_cond_test.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package runtime_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCondExpr(t *testing.T) {
|
||||
expect(t, `out = true ? 5 : 10`, 5)
|
||||
expect(t, `out = false ? 5 : 10`, 10)
|
||||
expect(t, `out = (1 == 1) ? 2 + 3 : 12 - 2`, 5)
|
||||
expect(t, `out = (1 != 1) ? 2 + 3 : 12 - 2`, 10)
|
||||
expect(t, `out = (1 == 1) ? true ? 10 - 8 : 1 + 3 : 12 - 2`, 2)
|
||||
expect(t, `out = (1 == 1) ? false ? 10 - 8 : 1 + 3 : 12 - 2`, 4)
|
||||
|
||||
expect(t, `
|
||||
f1 := func() { out += 10 }
|
||||
f2 := func() { out = -out }
|
||||
true ? f1() : f2()
|
||||
`, 10)
|
||||
expect(t, `
|
||||
out = 5
|
||||
f1 := func() { out += 10 }
|
||||
f2 := func() { out = -out }
|
||||
false ? f1() : f2()
|
||||
`, -5)
|
||||
expect(t, `
|
||||
f1 := func(a) { return a + 2 }
|
||||
f2 := func(a) { return a - 2 }
|
||||
f3 := func(a) { return a + 10 }
|
||||
f4 := func(a) { return -a }
|
||||
|
||||
f := func(c) {
|
||||
return c == 0 ? f1(c) : f2(c) ? f3(c) : f4(c)
|
||||
}
|
||||
|
||||
out = [f(0), f(1), f(2)]
|
||||
`, ARR{2, 11, -2})
|
||||
|
||||
expect(t, `f := func(a) { return -a }; out = f(true ? 5 : 3)`, -5)
|
||||
expect(t, `out = [false?5:10, true?1:2]`, ARR{10, 1})
|
||||
|
||||
expect(t, `
|
||||
out = 1 > 2 ?
|
||||
1 + 2 + 3 :
|
||||
10 - 5`, 5)
|
||||
}
|
Loading…
Reference in a new issue