Skip to content

Commit

Permalink
2.6: Add parser of infix operators with precedence
Browse files Browse the repository at this point in the history
  • Loading branch information
nibral committed Jan 12, 2019
1 parent f494a20 commit b0ae349
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 6 deletions.
21 changes: 21 additions & 0 deletions ast/ast.go
Expand Up @@ -129,3 +129,24 @@ func (pe *PrefixExpression) String() string {

return out.String()
}

type InfixExpression struct {
Token token.Token // the operator token, e.g. +
Left Expression
Operator string
Right Expression
}

func (ie *InfixExpression) expressionNode() {}
func (ie *InfixExpression) TokenLiteral() string { return ie.Token.Literal }
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()
}
74 changes: 68 additions & 6 deletions parser/parser.go
Expand Up @@ -11,14 +11,25 @@ import (
const (
_ int = iota
LOWEST
EQUALS // ==
LESSGRATER // < or >
SUM // +
PRODUCT // *
PREFIX // -X or !X
CALL // myFunction(X)
EQUALS // ==
LESSGREATER // < or >
SUM // +
PRODUCT // *
PREFIX // -X or !X
CALL // myFunction(X)
)

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,
}

type (
prefixParseFn func() ast.Expression
infixParseFn func(expression ast.Expression) ast.Expression
Expand Down Expand Up @@ -52,6 +63,16 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.BANG, p.parsePrefixExpression)
p.registerPrefix(token.MINUS, p.parsePrefixExpression)

p.infixParseFns = make(map[token.TokenType]infixParseFn)
p.registerInfix(token.PLUS, p.parseInfixExpression)
p.registerInfix(token.MINUS, p.parseInfixExpression)
p.registerInfix(token.SLASH, p.parseInfixExpression)
p.registerInfix(token.ASTERISK, p.parseInfixExpression)
p.registerInfix(token.EQ, p.parseInfixExpression)
p.registerInfix(token.NOT_EQ, p.parseInfixExpression)
p.registerInfix(token.LT, p.parseInfixExpression)
p.registerInfix(token.GT, p.parseInfixExpression)

return p
}

Expand All @@ -63,6 +84,22 @@ func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
p.infixParseFns[tokenType] = fn
}

func (p *Parser) peekPrecedence() int {
if p, ok := precedences[p.peekToken.Type]; ok {
return p
}

return LOWEST
}

func (p *Parser) curPrecedence() int {
if p, ok := precedences[p.curToken.Type]; ok {
return p
}

return LOWEST
}

func (p *Parser) Errors() []string {
return p.errors
}
Expand Down Expand Up @@ -181,6 +218,17 @@ 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
}

Expand Down Expand Up @@ -218,3 +266,17 @@ func (p *Parser) parsePrefixExpression() ast.Expression {

return expression
}

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
}
122 changes: 122 additions & 0 deletions parser/parser_test.go
Expand Up @@ -225,3 +225,125 @@ func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool {

return true
}

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\n",
1, len(program.Statements))
}

stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
if !ok {
t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T",
program.Statements[0])
}

exp, ok := stmt.Expression.(*ast.InfixExpression)
if !ok {
t.Fatalf("exp is not ast.InfixExpression. got=%T", stmt.Expression)
}

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

if !testIntegerLiteral(t, exp.Right, tt.rightValue) {
return
}
}
}

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

0 comments on commit b0ae349

Please sign in to comment.