From 69eb7da51e3e40668019eb9e6ae268c110c70041 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Sun, 27 Jan 2019 16:25:12 -0800 Subject: [PATCH] parser implementation for conditional expression --- compiler/ast/cond_expr.go | 30 ++++++++++++++++++ compiler/parser/parser.go | 28 ++++++++++++++++- compiler/parser/parser_cond_test.go | 48 +++++++++++++++++++++++++++++ compiler/parser/parser_test.go | 10 ++++++ compiler/scanner/scanner.go | 2 ++ compiler/token/tokens.go | 2 ++ 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 compiler/ast/cond_expr.go create mode 100644 compiler/parser/parser_cond_test.go diff --git a/compiler/ast/cond_expr.go b/compiler/ast/cond_expr.go new file mode 100644 index 0000000..bb1db84 --- /dev/null +++ b/compiler/ast/cond_expr.go @@ -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() + ")" +} diff --git a/compiler/parser/parser.go b/compiler/parser/parser.go index c24fc26..df84d96 100644 --- a/compiler/parser/parser.go +++ b/compiler/parser/parser.go @@ -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")) diff --git a/compiler/parser/parser_cond_test.go b/compiler/parser/parser_cond_test.go new file mode 100644 index 0000000..ae8684b --- /dev/null +++ b/compiler/parser/parser_cond_test.go @@ -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`) +} diff --git a/compiler/parser/parser_test.go b/compiler/parser/parser_test.go index e39cbcc..8ad75b8 100644 --- a/compiler/parser/parser_test.go +++ b/compiler/parser/parser_test.go @@ -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)) } diff --git a/compiler/scanner/scanner.go b/compiler/scanner/scanner.go index 5bc413d..ae9156b 100644 --- a/compiler/scanner/scanner.go +++ b/compiler/scanner/scanner.go @@ -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 = ";" diff --git a/compiler/token/tokens.go b/compiler/token/tokens.go index 5e37c75..813301f 100644 --- a/compiler/token/tokens.go +++ b/compiler/token/tokens.go @@ -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",