parser implementation for conditional expression

This commit is contained in:
Daniel Kang 2019-01-27 16:25:12 -08:00
parent c0bb733c67
commit 69eb7da51e
6 changed files with 119 additions and 1 deletions

30
compiler/ast/cond_expr.go Normal file
View 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() + ")"
}

View file

@ -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"))

View 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`)
}

View file

@ -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))
}

View file

@ -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 = ";"

View file

@ -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",