Skip to content

Commit

Permalink
cleaned up parser
Browse files Browse the repository at this point in the history
  • Loading branch information
tzcl committed Nov 14, 2023
1 parent e30fa0f commit a141796
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 18 deletions.
63 changes: 48 additions & 15 deletions glox/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ import (
"github.com/tzcl/lox/glox/internal/token"
)

type ParserError struct {
token token.Token
message string
}

func (e *ParserError) Error() string {
if e.token.Type == token.EOF {
return fmt.Sprintf("[line %d]: Error at end: %s", e.token.Line, e.message)
}
return fmt.Sprintf("[line %d]: Error at '%s': %s", e.token.Line, e.token.Lexeme, e.message)
}

type Parser struct {
tokens []token.Token
curr int
Expand All @@ -18,12 +30,15 @@ func New(tokens []token.Token) *Parser {
return &Parser{tokens: tokens}
}

func (p *Parser) Parse() (ast.Expr, error) {
func (p *Parser) Parse() (expr ast.Expr, err error) {
defer func() {
// if r := recover(); r != nil {
// TODO: What do we need to do here?

// }
if r := recover(); r != nil {
if e, ok := r.(*ParserError); ok {
err = e
} else {
panic(r)
}
}
}()

return p.expression(), nil
Expand Down Expand Up @@ -128,33 +143,51 @@ func (p *Parser) primary() ast.Expr {
number, err := strconv.ParseFloat(n.Lexeme, 64)
if err != nil {
// Should be unreachable
panic("couldn't parse number: " + err.Error())
panic(err)
}
return ast.NewLiteralExpr(number)
case token.String:
return ast.NewLiteralExpr(n.Lexeme)
case token.LeftParen:
expr := p.expression()
p.consume(token.RightParen, "expect ')' after expression")
p.consume(token.RightParen, "expected ')' after expression")
return ast.GroupingExpr{Expr: expr}
default:
panic(fmt.Sprint("unknown token ", n))
}
}

func (p *Parser) consume(ttype token.Type, message string) token.Token {
for !p.done() {
if p.check(ttype) {
return p.next()
}
if p.check(ttype) {
return p.next()
}

panic(message)
panic(&ParserError{p.peek(), message})
}

// func (p *Parser) synchronise() {
// TODO: implement
// }
func (p *Parser) synchronise() {
p.next()

for !p.done() {
if p.prev().Type == token.Semicolon {
return
}

switch p.peek().Type {
case token.Class,
token.Fun,
token.Var,
token.For,
token.If,
token.While,
token.Print,
token.Return:
return
}

p.next()
}
}

func (p *Parser) match(types ...token.Type) bool {
for _, ttype := range types {
Expand Down
82 changes: 82 additions & 0 deletions glox/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package parser_test

import (
"testing"

"github.com/hexops/autogold/v2"
"github.com/tzcl/lox/glox/internal/ast"
"github.com/tzcl/lox/glox/internal/parser"
"github.com/tzcl/lox/glox/internal/scanner"
)

func TestParser_Parse(t *testing.T) {
tests := map[string]struct {
source string
expect autogold.Value
}{
"Success": {
source: "(1+2*3/(4-5))",
expect: autogold.Expect("((+ 1 (/ (* 2 3) ((- 4 5)))))"),
},
}

t.Parallel()
for name, test := range tests {
test := test
t.Run(name, func(t *testing.T) {
t.Parallel()

scanner := scanner.New(test.source)
tokens, err := scanner.Scan()
if err != nil {
t.Fatal("unexpected err: ", err)
}

parser := parser.New(tokens)
expr, err := parser.Parse()
if err != nil {
t.Fatal("unexpected err: ", err)
}

test.expect.Equal(t, ast.Print(expr))
})
}
}

func TestParser_ParseError(t *testing.T) {
tests := map[string]struct {
source string
expect autogold.Value
}{
"AtEnd": {
source: "(1 + 1",
expect: autogold.Expect("[line 1]: Error at end: expected ')' after expression"),
},
"AtToken": {
source: "(1 + 1 a",
expect: autogold.Expect("[line 1]: Error at 'a': expected ')' after expression"),
},
}

t.Parallel()
for name, test := range tests {
test := test
t.Run(name, func(t *testing.T) {
t.Parallel()

scanner := scanner.New(test.source)
tokens, err := scanner.Scan()
if err != nil {
t.Fatal("unexpected err: ", err)
}

parser := parser.New(tokens)
_, err = parser.Parse()
if err == nil {
t.Fatal("expected an error")
}

test.expect.Equal(t, err.Error())
})
}
}
2 changes: 1 addition & 1 deletion glox/internal/scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ func (s *Scanner) peekNext() rune {
}

func (s *Scanner) addToken(ttype token.Type) {
s.addTokenWithLexeme(ttype, "")
s.addTokenWithLexeme(ttype, string(s.source[s.start:s.curr]))
}

func (s *Scanner) addTokenWithLexeme(ttype token.Type, lexeme string) {
Expand Down
4 changes: 2 additions & 2 deletions glox/internal/scanner/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestScan(t *testing.T) {
scanner := scanner.New(test.source)
tokens, err := scanner.Scan()
if err != nil {
t.Fatal("failed to scan text")
t.Fatal("failed to scan text: ", err)
}
test.expect.Equal(t, formatTokens(tokens))
})
Expand Down Expand Up @@ -111,7 +111,7 @@ bnm,`),
tokens, err := scanner.Scan()
t.Log(tokens)
if err == nil {
t.Fatal("expected error")
t.Fatal("expected an error")
}
test.expect.Equal(t, err.Error())
})
Expand Down

0 comments on commit a141796

Please sign in to comment.