Browse Source

§2.6 Parsing infix expressions.

Frederic G. MARAND 5 years ago
parent
commit
55730af15e

+ 37 - 0
ast/infix_expression.go

@@ -0,0 +1,37 @@
+package ast
+
+import (
+	"bytes"
+
+	"code.osinet.fr/fgm/waiig15/token"
+)
+
+// InfixExpression fulfills the Node and Statement interfaces.
+// It represents a prefixed expression like:
+// "-5;"
+type InfixExpression struct {
+	Token    token.Token
+	Left     Expression
+	Operator string
+	Right    Expression
+}
+
+// String satisfies the Node and fmt.Stringer interfaces.
+func (ie *InfixExpression) String() string {
+	var out bytes.Buffer
+
+	out.WriteString("(")
+	out.WriteString(ie.Left.String())
+	out.WriteString(" " + ie.Operator + " ")
+	out.WriteString(ie.Right.String())
+	out.WriteString(")")
+
+	return out.String()
+}
+
+func (ie *InfixExpression) expressionNode() {}
+
+// TokenLiteral satisfies the Node interface.
+func (ie *InfixExpression) TokenLiteral() string {
+	return ie.Token.Literal
+}

+ 1 - 1
ast/prefix_expression.go

@@ -6,7 +6,7 @@ import (
 	"code.osinet.fr/fgm/waiig15/token"
 )
 
-// ExpressionStatement fulfills the Node and Statement interfaces.
+// PrefixExpression fulfills the Node and Statement interfaces.
 // It represents a prefixed expression like:
 // "-5;"
 type PrefixExpression struct {

+ 44 - 0
parser/parser.go

@@ -37,6 +37,17 @@ type (
 	infixParseFn  func(ast.Expression) ast.Expression
 )
 
+var precedences = map[token.TokenType]int{
+	token.EQ:       EQUALS,
+	token.NOT_EQ:   EQUALS,
+	token.LT:       LESSGREATER,
+	token.GT:       LESSGREATER,
+	token.PLUS:     SUM,
+	token.MINUS:    SUM,
+	token.SLASH:    PRODUCT,
+	token.ASTERISK: PRODUCT,
+}
+
 // New returns a new Parser instance with the first two parser tokens already
 // loaded.
 func New(l *lexer.Lexer) *Parser {
@@ -45,6 +56,20 @@ func New(l *lexer.Lexer) *Parser {
 		errors: []string{},
 	}
 
+	p.infixParseFns = make(map[token.TokenType]infixParseFn)
+	for _, tok := range []token.TokenType{
+		token.ASTERISK,
+		token.EQ,
+		token.GT,
+		token.LT,
+		token.MINUS,
+		token.NOT_EQ,
+		token.PLUS,
+		token.SLASH,
+	} {
+		p.registerInfix(tok, p.parseInfixExpression)
+	}
+
 	p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
 	p.registerPrefix(token.BANG, p.parsePrefixExpression)
 	p.registerPrefix(token.IDENT, p.parseIdentifier)
@@ -80,6 +105,16 @@ func (p *Parser) ParseProgram() *ast.Program {
 	return program
 }
 
+// Return the precedence for the current token without advancing.
+func (p *Parser) curPrecedence() int {
+	if p, ok := precedences[p.curToken.Type]; ok {
+		return p
+	}
+
+	return LOWEST
+
+}
+
 // Is the current token in the parser of the given type ?
 func (p *Parser) curTokenIs(t token.TokenType) bool {
 	return p.curToken.Type == t
@@ -122,6 +157,15 @@ func (p *Parser) peekError(t token.TokenType) {
 	p.errors = append(p.errors, msg)
 }
 
+// Look forward for the precedence of the next token without advancing.
+func (p *Parser) peekPrecedence() int {
+	if p, ok := precedences[p.peekToken.Type]; ok {
+		return p
+	}
+
+	return LOWEST
+}
+
 // Is the next token in the parser of the given type ? Don't consume it.
 func (p *Parser) peekTokenIs(t token.TokenType) bool {
 	return p.peekToken.Type == t

+ 10 - 0
parser/parser_expression.go

@@ -21,6 +21,16 @@ func (p *Parser) parseExpression(precedence int) ast.Expression {
 
 	leftExp := prefix()
 
+	for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
+		infix := p.infixParseFns[p.peekToken.Type]
+		if infix == nil {
+			return leftExp
+		}
+
+		p.nextToken()
+		leftExp = infix(leftExp)
+	}
+
 	return leftExp
 }
 

+ 19 - 0
parser/parser_infix.go

@@ -0,0 +1,19 @@
+package parser
+
+import (
+	"code.osinet.fr/fgm/waiig15/ast"
+)
+
+func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
+	expression := &ast.InfixExpression{
+		Token:    p.curToken,
+		Operator: p.curToken.Literal,
+		Left:     left,
+	}
+
+	precedence := p.curPrecedence()
+	p.nextToken()
+	expression.Right = p.parseExpression(precedence)
+
+	return expression
+}

+ 64 - 0
parser/parser_infix_test.go

@@ -0,0 +1,64 @@
+package parser
+
+import (
+	"testing"
+
+	"code.osinet.fr/fgm/waiig15/ast"
+	"code.osinet.fr/fgm/waiig15/lexer"
+)
+
+func TestParsingInfixExpressions(t *testing.T) {
+	infixTests := []struct {
+		input      string
+		leftValue  int64
+		operator   string
+		rightValue int64
+	}{
+		{"5 + 5", 5, "+", 5},
+		{"5 - 5", 5, "-", 5},
+		{"5 * 5", 5, "*", 5},
+		{"5 / 5", 5, "/", 5},
+		{"5 > 5", 5, ">", 5},
+		{"5 < 5", 5, "<", 5},
+		{"5 == 5", 5, "==", 5},
+		{"5 != 5", 5, "!=", 5},
+	}
+
+	for _, tt := range infixTests {
+		l := lexer.New(tt.input)
+		p := New(l)
+		program := p.ParseProgram()
+		checkParserErrors(t, p)
+
+		if len(program.Statements) != 1 {
+			t.Fatalf("program.Statements does not contain %d statements. got=%d",
+				1, len(program.Statements))
+		}
+
+		stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+		if !ok {
+			t.Fatalf("program.Statements[0] is not expressionStatement. got=%T",
+				program.Statements[0])
+		}
+
+		exp, ok := stmt.Expression.(*ast.InfixExpression)
+		if !ok {
+			t.Fatalf("exp is not infixExpression. got=%T", stmt.Expression)
+		}
+
+		// Why no error ?
+		if !testIntegerLiteral(t, exp.Left, tt.leftValue) {
+			return
+		}
+
+		if exp.Operator != tt.operator {
+			t.Fatalf("exp.Operator is not '%s'. got=%s", tt.operator,
+				exp.Operator)
+		}
+
+		// Why no error ?
+		if !testIntegerLiteral(t, exp.Right, tt.rightValue) {
+			return
+		}
+	}
+}

+ 70 - 0
parser/parser_test.go

@@ -2,6 +2,8 @@ package parser
 
 import (
 	"testing"
+
+	"code.osinet.fr/fgm/waiig15/lexer"
 )
 
 func checkParserErrors(t *testing.T, p *Parser) {
@@ -16,3 +18,71 @@ func checkParserErrors(t *testing.T, p *Parser) {
 	}
 	t.FailNow()
 }
+
+func TestOperatorPrecedenceParsing(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected string
+	}{
+		{
+			"-a * b",
+			"((-a) * b)",
+		},
+		{
+			"!-a",
+			"(!(-a))",
+		},
+		{
+			"a + b + c",
+			"((a + b) + c)",
+		},
+		{
+			"a + b - c",
+			"((a + b) - c)",
+		},
+		{
+			"a * b * c",
+			"((a * b) * c)",
+		},
+		{
+			"a * b / c",
+			"((a * b) / c)",
+		},
+		{
+			"a + b / c",
+			"(a + (b / c))",
+		},
+		{
+			"a + b * c + d / e - f",
+			"(((a + (b * c)) + (d / e)) - f)",
+		},
+		{
+			"3 + 4; -5 * 5",
+			"(3 + 4)((-5) * 5)",
+		},
+		{
+			"5 > 4 == 3 < 4",
+			"((5 > 4) == (3 < 4))",
+		},
+		{
+			"5 < 4 != 3 > 4",
+			"((5 < 4) != (3 > 4))",
+		},
+		{
+			"3 + 4 * 5 == 3 * 1 + 4 * 5",
+			"((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))",
+		},
+	}
+
+	for _, tt := range tests {
+		l := lexer.New(tt.input)
+		p := New(l)
+		program := p.ParseProgram()
+		checkParserErrors(t, p)
+
+		actual := program.String()
+		if actual != tt.expected {
+			t.Errorf("expected=%q, got=%q", tt.expected, actual)
+		}
+	}
+}