parser implementation for conditional expression
This commit is contained in:
parent
c0bb733c67
commit
69eb7da51e
6 changed files with 119 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() + ")"
|
||||||
|
}
|
|
@ -82,7 +82,14 @@ func (p *Parser) parseExpr() ast.Expr {
|
||||||
defer un(trace(p, "Expression"))
|
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 {
|
func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr {
|
||||||
|
@ -101,6 +108,7 @@ func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr {
|
||||||
pos := p.expect(op)
|
pos := p.expect(op)
|
||||||
|
|
||||||
y := p.parseBinaryExpr(prec + 1)
|
y := p.parseBinaryExpr(prec + 1)
|
||||||
|
|
||||||
x = &ast.BinaryExpr{
|
x = &ast.BinaryExpr{
|
||||||
LHS: x,
|
LHS: x,
|
||||||
RHS: y,
|
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 {
|
func (p *Parser) parseUnaryExpr() ast.Expr {
|
||||||
if p.trace {
|
if p.trace {
|
||||||
defer un(trace(p, "UnaryExpression"))
|
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}
|
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 {
|
func unaryExpr(x ast.Expr, op token.Token, pos source.Pos) *ast.UnaryExpr {
|
||||||
return &ast.UnaryExpr{Expr: x, Token: op, TokenPos: pos}
|
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.ErrorPos), int(actual.(*ast.ErrorExpr).ErrorPos)) &&
|
||||||
assert.Equal(t, int(expected.LParen), int(actual.(*ast.ErrorExpr).LParen)) &&
|
assert.Equal(t, int(expected.LParen), int(actual.(*ast.ErrorExpr).LParen)) &&
|
||||||
assert.Equal(t, int(expected.RParen), int(actual.(*ast.ErrorExpr).RParen))
|
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:
|
default:
|
||||||
panic(fmt.Errorf("unknown type: %T", expected))
|
panic(fmt.Errorf("unknown type: %T", expected))
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,8 @@ func (s *Scanner) Scan() (tok token.Token, literal string, pos source.Pos) {
|
||||||
}
|
}
|
||||||
case ',':
|
case ',':
|
||||||
tok = token.Comma
|
tok = token.Comma
|
||||||
|
case '?':
|
||||||
|
tok = token.Question
|
||||||
case ';':
|
case ';':
|
||||||
tok = token.Semicolon
|
tok = token.Semicolon
|
||||||
literal = ";"
|
literal = ";"
|
||||||
|
|
|
@ -64,6 +64,7 @@ const (
|
||||||
RBrace // }
|
RBrace // }
|
||||||
Semicolon // ;
|
Semicolon // ;
|
||||||
Colon // :
|
Colon // :
|
||||||
|
Question // ?
|
||||||
_operatorEnd
|
_operatorEnd
|
||||||
_keywordBeg
|
_keywordBeg
|
||||||
Break
|
Break
|
||||||
|
@ -142,6 +143,7 @@ var tokens = [...]string{
|
||||||
RBrace: "}",
|
RBrace: "}",
|
||||||
Semicolon: ";",
|
Semicolon: ";",
|
||||||
Colon: ":",
|
Colon: ":",
|
||||||
|
Question: "?",
|
||||||
Break: "break",
|
Break: "break",
|
||||||
Case: "case",
|
Case: "case",
|
||||||
Continue: "continue",
|
Continue: "continue",
|
||||||
|
|
Loading…
Reference in a new issue