From 0d25e2d9a2a849935834e405259b4ebd8b8747d6 Mon Sep 17 00:00:00 2001 From: unintendedfraud Date: Thu, 25 Apr 2024 16:37:38 +0200 Subject: [PATCH] parse statements --- ast/ast.go | 8 +++ parser/parser.go | 95 +++++++++++++++++++++++++++++++-- parser/parser_test.go | 120 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 parser/parser_test.go diff --git a/ast/ast.go b/ast/ast.go index 234a3e8..c721365 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -44,3 +44,11 @@ type Identifier struct { func (i *Identifier) expressionNode() {} func (i *Identifier) TokenLiteral() string { return i.Token.Literal } + +type ReturnStatement struct { + Token token.Token + Value Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } diff --git a/parser/parser.go b/parser/parser.go index 2ba8ff5..9a24bef 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1,20 +1,26 @@ package parser import ( + "fmt" + "gointerpreter/ast" "gointerpreter/lexer" "gointerpreter/token" ) type Parser struct { - l *lexer.Lexer + l *lexer.Lexer + errors []string currToken token.Token peekToken token.Token } func New(l *lexer.Lexer) *Parser { - p := &Parser{l: l} + p := &Parser{ + l: l, + errors: []string{}, + } p.nextToken() p.nextToken() @@ -28,5 +34,88 @@ func (p *Parser) nextToken() { } func (p *Parser) ParseProgram() *ast.Program { - return nil + program := &ast.Program{ + Statements: []ast.Statement{}, + } + + for p.currToken.Type != token.EOF { + statement := p.parseStatement() + if statement != nil { + program.Statements = append(program.Statements, statement) + } + + p.nextToken() + } + + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.currToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + default: + return nil + } +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.currToken} + + p.nextToken() + + for !p.currTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.currToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.currToken, Value: p.currToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + for !p.currTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) currTokenIs(t token.TokenType) bool { + return p.currToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } + + p.peekError(t) + return false +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be [%s], got [%s] instead", t, p.peekToken.Type) + p.errors = append(p.errors, msg) } diff --git a/parser/parser_test.go b/parser/parser_test.go new file mode 100644 index 0000000..ecb2420 --- /dev/null +++ b/parser/parser_test.go @@ -0,0 +1,120 @@ +package parser + +import ( + "testing" + + "gointerpreter/ast" + "gointerpreter/lexer" +) + +func Test_LetStatement(t *testing.T) { + input := ` + let x = 5; + let y = 10; + let foobar = 324453; + ` + + l := lexer.New(input) + p := New(l) + + program := p.ParseProgram() + checkParserErrors(t, p) + + if program == nil { + t.Fatalf("ParseProgram() returned nil") + } + + if len(program.Statements) != 3 { + t.Fatalf("program.Statement does not contained 3 statements, got [%d]", len(program.Statements)) + } + + tests := []struct { + expectedIdentifier string + }{ + {"x"}, + {"y"}, + {"foobar"}, + } + + for i, tt := range tests { + statement := program.Statements[i] + + if !testLetStatement(t, statement, tt.expectedIdentifier) { + return + } + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral() is not [let], got [%s]", s.TokenLiteral()) + return false + } + + letStatement, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s is not a *ast.LetStatement, got [%T]", s) + return false + } + + if letStatement.Name.Value != name { + t.Errorf("letStatement.Name.Value is not [%s], got [%s]", name, letStatement.Name.Value) + return false + } + + if letStatement.Name.TokenLiteral() != name { + t.Errorf("letStatement.TokenLiteral() is not [%s], got [%s]", name, letStatement.TokenLiteral()) + return false + } + + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + + if len(errors) == 0 { + return + } + + t.Errorf("parser has [%d] errors", len(errors)) + for _, e := range errors { + t.Errorf("parser error: %q", e) + } + + t.FailNow() +} + +func Test_ReturnStatement(t *testing.T) { + input := ` + return 5; + return 10; + return 234342; + ` + + l := lexer.New(input) + p := New(l) + + program := p.ParseProgram() + checkParserErrors(t, p) + + if program == nil { + t.Fatalf("ParseProgram() returned nil") + } + + if len(program.Statements) != 3 { + t.Fatalf("program.Statement does not contained 3 statements, got [%d]", len(program.Statements)) + } + + for _, stmt := range program.Statements { + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Errorf("stmt is not *ast.ReturnStatement, got [%T]", stmt) + continue + } + + if returnStmt.TokenLiteral() != "return" { + t.Errorf("returnStmt.TokenLiteral() is not [return], got [%q]", returnStmt.TokenLiteral()) + } + } +}