From 152e63c1865d8bc1df36f54218cc9286b7fd1ff2 Mon Sep 17 00:00:00 2001 From: Bobby Date: Wed, 8 Nov 2023 17:56:53 +0000 Subject: parse infix operators --- parser/parser.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++-- parser/parser_test.go | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) (limited to 'parser') diff --git a/parser/parser.go b/parser/parser.go index b9fb91f..6645da1 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -20,6 +20,17 @@ const ( CALL // myFunction(X) ) +var precedences = map[tokens.TokenType]int{ + tokens.EQ: EQUALS, + tokens.NOT_EQ: EQUALS, + tokens.LT: LESSGREATER, + tokens.GT: LESSGREATER, + tokens.PLUS: SUM, + tokens.MINUS: SUM, + tokens.SLASH: PRODUCT, + tokens.ASTERISK: PRODUCT, +} + type ( prefixParseFn func() ast.Expression infixParseFn func(ast.Expression) ast.Expression @@ -55,6 +66,17 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(tokens.BANG, p.parsePrefixExpression) p.registerPrefix(tokens.MINUS, p.parsePrefixExpression) + // Initialize the infix parse functions. + p.infixParseFns = make(map[tokens.TokenType]infixParseFn) + p.registerInfix(tokens.PLUS, p.parseInfixExpression) + p.registerInfix(tokens.MINUS, p.parseInfixExpression) + p.registerInfix(tokens.SLASH, p.parseInfixExpression) + p.registerInfix(tokens.ASTERISK, p.parseInfixExpression) + p.registerInfix(tokens.EQ, p.parseInfixExpression) + p.registerInfix(tokens.NOT_EQ, p.parseInfixExpression) + p.registerInfix(tokens.LT, p.parseInfixExpression) + p.registerInfix(tokens.GT, p.parseInfixExpression) + return p } @@ -193,14 +215,27 @@ func (p *Parser) noPrefixParseFnError(t tokens.TokenType) { // parseExpression parses an expression. func (p *Parser) parseExpression(precedence int) ast.Expression { - var prefix = p.prefixParseFns[p.curToken.Type] + prefix := p.prefixParseFns[p.curToken.Type] if prefix == nil { p.noPrefixParseFnError(p.curToken.Type) return nil } - var leftExp ast.Expression = prefix() + leftExp := prefix() + + for !p.peekTokenIs(tokens.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + + if infix == nil { + return leftExp + } + + p.nextToken() + + leftExp = infix(leftExp) + + } return leftExp } @@ -219,6 +254,21 @@ func (p *Parser) parsePrefixExpression() ast.Expression { return expression } +// parseInfixExpression parses an infix 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 +} + // curTokenIs returns true if the current token is of the given type. func (p *Parser) curTokenIs(t tokens.TokenType) bool { return p.curToken.Type == t @@ -253,3 +303,21 @@ func (p *Parser) peekError(t tokens.TokenType) { p.errors = append(p.errors, msg) } + +// peek and cur precedences + +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 +} diff --git a/parser/parser_test.go b/parser/parser_test.go index 828db24..d3e2d1d 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -259,3 +259,60 @@ func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { return true } + +// Infix expression tests. + +func TestParsingInfixExpressions(t *testing.T) { + var 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 { + var l *lexer.Lexer = lexer.New(tt.input) + var p *Parser = New(l) + + var program *ast.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 + } + } +} -- cgit v1.2.3