aboutsummaryrefslogtreecommitdiff
path: root/parser
diff options
context:
space:
mode:
Diffstat (limited to 'parser')
-rw-r--r--parser/parser.go132
-rw-r--r--parser/parser_test.go91
2 files changed, 223 insertions, 0 deletions
diff --git a/parser/parser.go b/parser/parser.go
new file mode 100644
index 0000000..6e8bba6
--- /dev/null
+++ b/parser/parser.go
@@ -0,0 +1,132 @@
+package parser
+
+import (
+ "fmt"
+ "mana/ast"
+ "mana/lexer"
+ "mana/tokens"
+)
+
+// Parser represents a parser.
+type Parser struct {
+ l *lexer.Lexer
+
+ curToken tokens.Token
+ peekToken tokens.Token
+
+ errors []string
+}
+
+// New returns a new Parser.
+func New(l *lexer.Lexer) *Parser {
+ var p *Parser = &Parser{
+ l: l,
+ errors: []string{},
+ }
+
+ // Read two tokens, so curToken and peekToken are both set.
+ p.nextToken()
+ p.nextToken()
+
+ return p
+}
+
+// nextToken advances the tokens.
+func (p *Parser) nextToken() {
+ p.curToken = p.peekToken
+ p.peekToken = p.l.NextToken()
+}
+
+// ParseProgram parses a program.
+func (p *Parser) ParseProgram() *ast.Program {
+
+ // Initialize the program with an empty slice of statements.
+ var program *ast.Program = &ast.Program{}
+ program.Statements = []ast.Statement{}
+
+ // Iterate over the tokens until we reach the end of file.
+ for p.curToken.Type != tokens.EOF {
+ // Parse the statement and append it to the program.
+ var stmt ast.Statement = p.parseStatement()
+
+ if stmt != nil {
+ program.Statements = append(program.Statements, stmt)
+ }
+
+ // Advance to the next token.
+ p.nextToken()
+ }
+
+ // Return the program.
+ return program
+}
+
+// parseStatement parses a statement.
+func (p *Parser) parseStatement() ast.Statement {
+ switch p.curToken.Type {
+ case tokens.LET:
+ return p.parseLetStatement()
+ default:
+ return nil
+ }
+}
+
+// parseLetStatement parses a let statement.
+func (p *Parser) parseLetStatement() *ast.LetStatement {
+ var stmt *ast.LetStatement = &ast.LetStatement{Token: p.curToken}
+
+ if !p.expectPeek(tokens.IDENT) {
+ return nil
+ }
+
+ stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
+
+ if !p.expectPeek(tokens.ASSIGN) {
+ return nil
+ }
+
+ // TODO: We're skipping the expressions until we
+ // encounter a semicolon.
+
+ for !p.curTokenIs(tokens.SEMICOLON) {
+ p.nextToken()
+ }
+
+ return stmt
+}
+
+// curTokenIs returns true if the current token is of the given type.
+func (p *Parser) curTokenIs(t tokens.TokenType) bool {
+ return p.curToken.Type == t
+}
+
+// peekTokenIs returns true if the next token is of the given type.
+func (p *Parser) peekTokenIs(t tokens.TokenType) bool {
+ return p.peekToken.Type == t
+}
+
+// expectPeek expects the next token to be of the given type.
+func (p *Parser) expectPeek(t tokens.TokenType) bool {
+ if p.peekTokenIs(t) {
+ p.nextToken()
+
+ return true
+ } else {
+ p.peekError(t)
+
+ return false
+ }
+}
+
+// Errors returns the parser errors.
+func (p *Parser) Errors() []string {
+ return p.errors
+}
+
+// peekError returns an error message.
+func (p *Parser) peekError(t tokens.TokenType) {
+ var msg string = 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..8f78c8b
--- /dev/null
+++ b/parser/parser_test.go
@@ -0,0 +1,91 @@
+package parser
+
+import (
+ "testing"
+ "mana/ast"
+ "mana/lexer"
+)
+
+func TestLetStatements(t *testing.T) {
+ const input string = `
+ let x = 5;
+ let y = 10;
+ let foobar = 838383;
+ `
+
+ var l *lexer.Lexer = lexer.New(input)
+ var p *Parser = New(l)
+
+ var program *ast.Program = p.ParseProgram()
+ checkParserErrors(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))
+ }
+
+ var tests = []struct {
+ expectedIdentifier string
+ }{
+ {"x"},
+ {"y"},
+ {"foobar"},
+ }
+
+ for i, tt := range tests {
+ var stmt ast.Statement = program.Statements[i]
+
+ if !testLetStatement(t, stmt, tt.expectedIdentifier) {
+ return
+ }
+ }
+}
+
+func checkParserErrors(t *testing.T, p *Parser) {
+ var errors []string = 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()
+}
+
+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
+ }
+
+ var 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.Value)
+
+ return false
+ }
+
+ if letStmt.Name.TokenLiteral() != name {
+ t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", name, letStmt.Name.TokenLiteral())
+
+ return false
+ }
+
+ return true
+}