From 7abb78c88e75a9edebb11ee4014bf5ae0755ca58 Mon Sep 17 00:00:00 2001 From: 54L1M Date: Mon, 29 Jan 2024 02:12:19 +0330 Subject: [PATCH] parse var statements --- ast/ast.go | 6 ++-- parser/parser.go | 81 +++++++++++++++++++++++++++++++++++++++++-- parser/parser_test.go | 78 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 parser/parser_test.go diff --git a/ast/ast.go b/ast/ast.go index 00159e9..ec1d1b5 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -40,13 +40,13 @@ func (i *Identifier) TokenLiteral() string { return i.Token.Literal } -type LetStatement struct { +type VarStatement struct { Token token.Token Name *Identifier Value Expression } -func (ls *LetStatement) statementNode() {} -func (ls *LetStatement) TokenLiteral() string { +func (ls *VarStatement) statementNode() {} +func (ls *VarStatement) TokenLiteral() string { return ls.Token.Literal } diff --git a/parser/parser.go b/parser/parser.go index aec6b00..9f4a211 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1,6 +1,8 @@ package parser import ( + "fmt" + "github.com/54L1m/mil-lang/ast" "github.com/54L1m/mil-lang/lexer" "github.com/54L1m/mil-lang/token" @@ -9,23 +11,98 @@ import ( type Parser struct { l *lexer.Lexer + errors []string + curToken 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() return p } +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) +} + func (p *Parser) nextToken() { p.curToken = p.peekToken p.peekToken = p.l.NextToken() } func (p *Parser) ParseProgram() *ast.Program { - return nil + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for p.curToken.Type != token.EOF { + stmt := p.parseStatement() + if stmt != nil { + program.Statements = append(program.Statements, stmt) + } + p.nextToken() + } + return program +} + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.VAR: + return p.parseVarStatement() + default: + return nil + + } +} + +func (p *Parser) parseVarStatement() *ast.VarStatement { + stmt := &ast.VarStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + //TODO: We're skipping the expressions until we encounter a semicolon + + for !p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.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 + } else { + p.peekError(t) + return false + } } diff --git a/parser/parser_test.go b/parser/parser_test.go new file mode 100644 index 0000000..4a6a693 --- /dev/null +++ b/parser/parser_test.go @@ -0,0 +1,78 @@ +package parser + +import ( + "testing" + + "github.com/54L1m/mil-lang/ast" + "github.com/54L1m/mil-lang/lexer" +) + +func TestLetStatement(t *testing.T) { + input := ` + var x = 5; + var y = 10; + var foobar = 838383; + ` + l := lexer.New(input) + p := New(l) + + program := p.ParseProgram() + checkParseErrors(t, p) + if program == nil { + t.Fatalf("ParseProgram() returned nil") + } + if len(program.Statements) != 3 { + t.Fatalf("program.Statements does not contain 3 statements. got=%d", + len(program.Statements)) + } + + tests := []struct { + expectedIdentifier string + }{ + {"x"}, + {"y"}, + {"foobar"}, + } + + for i, tt := range tests { + stmt := program.Statements[i] + if !testVarStatement(t, stmt, tt.expectedIdentifier) { + return + } + } +} + +func testVarStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "var" { + t.Errorf("s.TokenLiteral not 'var'. got=%q", s.TokenLiteral()) + return false + } + varStmt, ok := s.(*ast.VarStatement) + if !ok { + t.Errorf("s not *ast.VarStatement. got=%T", s) + return false + } + if varStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, varStmt.Name.Value) + return false + } + if varStmt.Name.TokenLiteral() != name { + t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", + name, varStmt.Name.TokenLiteral()) + return false + } + return true +} + +func checkParseErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser has %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() +}