From d4d6561c6f34542e4849ced5c1bac8ad70dc523c Mon Sep 17 00:00:00 2001 From: Tyrel Souza Date: Wed, 1 Dec 2021 15:31:30 -0500 Subject: [PATCH] ast LET statements --- .gitignore | 1 + ast/ast.go | 46 ++++++++++++++++++ parser/parser.go | 106 ++++++++++++++++++++++++++++++++++++++++++ parser/parser_test.go | 76 ++++++++++++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 .gitignore create mode 100644 ast/ast.go create mode 100644 parser/parser.go create mode 100644 parser/parser_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d992b6f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Session.vim diff --git a/ast/ast.go b/ast/ast.go new file mode 100644 index 0000000..d3180a9 --- /dev/null +++ b/ast/ast.go @@ -0,0 +1,46 @@ +package ast + +import "gitlab.com/Tyrel/monkey/token" + +type Node interface { + TokenLiteral() string +} + +type Statement interface { + Node + statementNode() +} + +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +type LetStatement struct { + Token token.Token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } + +type Identifier struct { + Token token.Token + Value string +} + +func (ls *Identifier) expressionNode() {} +func (ls *Identifier) TokenLiteral() string { return ls.Token.Literal } diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..7ea24ca --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,106 @@ +package parser + +import ( + "fmt" + + "gitlab.com/Tyrel/monkey/ast" + "gitlab.com/Tyrel/monkey/lexer" + "gitlab.com/Tyrel/monkey/token" +) + +type Parser struct { + l *lexer.Lexer + + curToken token.Token + peekToken token.Token + + errors []string +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{ + l: l, + errors: []string{}, + } + + // get two tokens to curToken and peekToken are set + p.nextToken() + p.nextToken() + + return p +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) ParseProgram() *ast.Program { + 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.LET: + return p.parseLetStatement() + default: + return nil + } + +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{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 + } + + 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 + } +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s. got=%s", 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..890849b --- /dev/null +++ b/parser/parser_test.go @@ -0,0 +1,76 @@ +package parser + +import ( + "testing" + + "gitlab.com/Tyrel/monkey/ast" + "gitlab.com/Tyrel/monkey/lexer" +) + +func TestLetStatements(t *testing.T) { + input := ` +let x 5; +let y = 10; +let 838383; +` + l := lexer.New(input) + p := New(l) + + program := p.ParseProgram() + if program == nil { + t.Fatalf("ParseProgram() return nil") + } + if len(program.Statements) != 3 { + t.Fatalf("program.Statements does not contain 3 statements. got=%d", + len(program.Statements)) + } + + checkParserErrors(t, p) + + tests := []struct { + expectedIdentifier string + }{ + {"x"}, + {"y"}, + {"foobar"}, + } + + for i, tt := range tests { + stmt := program.Statements[i] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "let" { + t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) + return false + } + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.Letstatement. got=%T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name) + return false + } + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + if len(errors) == 0 { + return + } + + t.Errorf("parser had %d errors", len(errors)) + for _, msg := range errors { + t.Errorf("parser error: %q", msg) + } + t.FailNow() + +}