Browse Source

§2.4 report parsing errors.

Frederic G. MARAND 5 years ago
parent
commit
afbd352a4a
2 changed files with 59 additions and 24 deletions
  1. 44 24
      parser/parser.go
  2. 15 0
      parser/parser_test.go

+ 44 - 24
parser/parser.go

@@ -4,17 +4,22 @@ import (
 	"code.osinet.fr/fgm/waiig15/ast"
 	"code.osinet.fr/fgm/waiig15/lexer"
 	"code.osinet.fr/fgm/waiig15/token"
+	"fmt"
 )
 
 type Parser struct {
-	l *lexer.Lexer
+	errors []string
+	l      *lexer.Lexer
 
 	curToken  token.Token
 	peekToken token.Token
 }
 
 func New(l *lexer.Lexer) *Parser {
-	p := &Parser{l: l}
+	p := &Parser{
+		l:      l,
+		errors: []string{},
+	}
 
 	// Read two tokens, so curToken and peeToken are both set.
 	p.nextToken()
@@ -23,9 +28,8 @@ func New(l *lexer.Lexer) *Parser {
 	return p
 }
 
-func (p *Parser) nextToken() {
-	p.curToken = p.peekToken
-	p.peekToken = p.l.NextToken()
+func (p *Parser) Errors() []string {
+	return p.errors
 }
 
 func (p *Parser) ParseProgram() *ast.Program {
@@ -43,15 +47,28 @@ func (p *Parser) ParseProgram() *ast.Program {
 	return program
 }
 
-func (p *Parser) parseStatement() ast.Statement {
-	switch p.curToken.Type {
-	case token.LET:
-		return p.parseLetStatement()
-	default:
-		return nil
+// Is the current token in the parser of the given type ?
+func (p *Parser) curTokenIs(t token.TokenType) bool {
+	return p.curToken.Type == t
+}
+
+// Is the next token in the parser of the given type ? If it is, consume it,
+// else don'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) nextToken() {
+	p.curToken = p.peekToken
+	p.peekToken = p.l.NextToken()
+}
+
 func (p *Parser) parseLetStatement() *ast.LetStatement {
 	stmt := &ast.LetStatement{
 		Token: p.curToken,
@@ -83,9 +100,22 @@ func (p *Parser) parseLetStatement() *ast.LetStatement {
 	return stmt
 }
 
-// Is the current token in the parser of the given type ?
-func (p *Parser) curTokenIs(t token.TokenType) bool {
-	return p.curToken.Type == t
+func (p *Parser) parseStatement() ast.Statement {
+	switch p.curToken.Type {
+	case token.LET:
+		return p.parseLetStatement()
+	default:
+		return nil
+	}
+}
+
+// Log a mismatch error on the peek token type in the parser instance.
+//
+//   - t is the type of token that was expected
+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)
 }
 
 // Is the next token in the parser of the given type ? Don't consume it.
@@ -93,13 +123,3 @@ func (p *Parser) peekTokenIs(t token.TokenType) bool {
 	return p.peekToken.Type == t
 }
 
-// Is the next token in the parser of the given type ? If it is, consume it,
-// else don't.
-func (p *Parser) expectPeek(t token.TokenType) bool {
-	if p.peekTokenIs(t) {
-		p.nextToken()
-		return true
-	} else {
-		return false
-	}
-}

+ 15 - 0
parser/parser_test.go

@@ -6,7 +6,21 @@ import (
 	"testing"
 )
 
+func checkParserErrors(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()
+}
+
 func TestLetStatements(t *testing.T) {
+	// Try removing the ident, the =, or both, to get human-readable errors.
 	input := `
 let x = 5;
 let y = 10;
@@ -16,6 +30,7 @@ let foobar = 838383;
 	p := New(l)
 
 	program := p.ParseProgram()
+	checkParserErrors(t, p)
 	if program == nil {
 		t.Fatalf("ParseProgram() returned nil.")
 	}