diff --git a/ast/ast.go b/ast/ast.go index c98bb3b..4811aaa 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -5,6 +5,25 @@ import ( "monkey/token" ) +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} type LetStatement struct { Token token.Token // the token.LET token Name *Identifier @@ -122,3 +141,24 @@ func (es *ExpressionStatement) String() string { } func (i *Identifier) String() string { return i.Value } + +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} \ No newline at end of file diff --git a/parser/parser.go b/parser/parser.go index f2a5070..76493c4 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -48,6 +48,20 @@ func New(l *lexer.Lexer) *Parser { l: l, errors: []string{}, } + 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) + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.IDENT, p.parseIdentifier) + p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.BANG, p.parsePrefixExpression) + p.registerPrefix(token.MINUS, p.parsePrefixExpression) // Read two tokens, so curToken and peekToken are both set p.nextToken() @@ -56,6 +70,46 @@ func New(l *lexer.Lexer) *Parser { return p } +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return 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, +} + +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) nextToken() { p.curToken = p.peekToken p.peekToken = p.l.NextToken() @@ -64,9 +118,6 @@ func (p *Parser) nextToken() { func (p *Parser) ParseProgram() *ast.Program { program := &ast.Program{} program.Statements = []ast.Statement{} - p.prefixParseFns = make(map[token.TokenType]prefixParseFn) - p.registerPrefix(token.IDENT, p.parseIdentifier) - p.registerPrefix(token.INT, p.parseIntegerLiteral) for p.curToken.Type != token.EOF { stmt := p.parseStatement() if stmt != nil { @@ -137,13 +188,25 @@ func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { } func (p *Parser) parseExpression(precedence int) ast.Expression { - prefix := p.prefixParseFns[p.curToken.Type] - if prefix == nil { - return nil - } - leftExp := prefix() + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() - return leftExp + 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 } func (p *Parser) parseIntegerLiteral() ast.Expression { @@ -191,3 +254,22 @@ func (p *Parser) peekError(t token.TokenType) { func (p *Parser) parseIdentifier() ast.Expression { return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} } + + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} +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 +} \ No newline at end of file diff --git a/parser/parser_test.go b/parser/parser_test.go index ded6c8e..bb30671 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -4,8 +4,193 @@ import ( "monkey/ast" "monkey/lexer" "testing" + "fmt" ) +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) + } + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + integerValue int64 + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + } + + for _, tt := range prefixTests { + 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.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression. got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not '%s'. got=%s", + tt.operator, exp.Operator) + } + if !testIntegerLiteral(t, exp.Right, tt.integerValue) { + return + } + } +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral. got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("integ.Value not %d. got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d. got=%s", value, + integ.TokenLiteral()) + return false + } + + return true +} + func TestLetStatements(t *testing.T) { input := ` let x = 5;