diff options
| author | Michael Tews <michael@tews.dev> | 2024-12-13 07:16:00 +0100 |
|---|---|---|
| committer | Michael Tews <michael@tews.dev> | 2026-04-12 11:11:02 +0200 |
| commit | 68ff1414130b7bdd9cbfe0e5c1fc60278f9a48fd (patch) | |
| tree | 5baeea833552b3f60f2c9f5dbf7d82492e066358 /parser | |
| parent | 0f51131eedb73d371bcf4d4868cb784f3592d08c (diff) | |
Signed-off-by: Michael Tews <michael@tews.dev>
Diffstat (limited to 'parser')
| -rw-r--r-- | parser/parser.go | 111 | ||||
| -rw-r--r-- | parser/parser_test.go | 171 |
2 files changed, 280 insertions, 2 deletions
diff --git a/parser/parser.go b/parser/parser.go index 936d58a..a9c381a 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -2,18 +2,46 @@ package parser import ( "fmt" + "strconv" "github.com/mewsen/interpreter/ast" "github.com/mewsen/interpreter/lexer" "github.com/mewsen/interpreter/token" ) +const ( + _ int = iota + LOWEST + EQUALS + LESSGREATER + SUM + PRODUCT + PREFIX + CALL +) + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + type Parser struct { l *lexer.Lexer errors []string curToken token.Token peekToken token.Token + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn } func (p *Parser) peekError(t token.TokenType) { @@ -34,12 +62,35 @@ func (p *Parser) Errors() []string { func New(l *lexer.Lexer) *Parser { p := &Parser{l: l, errors: []string{}} + 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) + p.NextToken() p.NextToken() 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 +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + func (p *Parser) ParseProgram() *ast.Program { program := &ast.Program{} program.Statements = []ast.Statement{} @@ -59,9 +110,25 @@ func (p *Parser) parseStatement() ast.Statement { switch p.curToken.Type { case token.LET: return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() default: - return nil + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + + p.NextToken() + + // TODO: We're skipping the expressions until + // we encounter a semicolon + for !p.curTokenIs(token.SEMICOLON) { + p.NextToken() } + + return stmt } func (p *Parser) parseLetStatement() *ast.LetStatement { @@ -85,6 +152,43 @@ func (p *Parser) parseLetStatement() *ast.LetStatement { return stmt } +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.NextToken() + } + + return stmt +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + return leftExp +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + func (p *Parser) curTokenIs(t token.TokenType) bool { return p.curToken.Type == t } @@ -101,3 +205,8 @@ func (p *Parser) expectPeek(t token.TokenType) bool { return false } } + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} diff --git a/parser/parser_test.go b/parser/parser_test.go index f7158bf..be49bf5 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1,9 +1,11 @@ package parser import ( + "fmt" + "testing" + "github.com/mewsen/interpreter/ast" "github.com/mewsen/interpreter/lexer" - "testing" ) func TestLetStatements(t *testing.T) { @@ -64,3 +66,170 @@ func testLetStatement(t *testing.T, s ast.Statement, name string) bool { return true } + +func TestReturnStatement(t *testing.T) { + input := ` +return 5; +return 10; +return 993322; + ` + + l := lexer.New(input) + p := New(l) + + program := p.ParseProgram() + + checkParserErrors(t, p) + + if len(program.Statements) != 3 { + t.Fatalf("program.Statements does not contain 3 statement. got=%d", len(program.Statements)) + } + + for _, stmt := range program.Statements { + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Errorf("stmt not *ast.returnStatement, got=%T", stmt) + continue + } + if returnStmt.TokenLiteral() != "return" { + t.Errorf("returnStmt.TokenLiteral not 'return', got=%q", returnStmt.TokenLiteral()) + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + 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]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier. got=%T", stmt.Expression) + } + + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s. got=%s", "foobar", ident.Value) + } + + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s. got=%s", "foobar", + ident.TokenLiteral()) + } +} + +func checkParserErrors(t *testing.T, p *Parser) { + for _, err := range p.errors { + t.Fatal(err) + } +} + +func TestIntegerLiteralExpression(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("exp not *ast.IntergerLiteral. got=%T", program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("literal.Value not %d, got=%d", 5, stmt.Expression) + } + + if literal.Value != 5 { + t.Errorf("literal.Value not %d. got=%d", 5, literal.Value) + } + + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "5", + literal.TokenLiteral()) + } +} + +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 +} |
