Make parser send error upwards instead of panicing

This commit is contained in:
Neemek 2024-12-29 14:49:31 +01:00
parent 402644d219
commit 6b90cc488e
Signed by: neemek
GPG key ID: 28360A8951CD0E9B
5 changed files with 246 additions and 129 deletions

View file

@ -59,24 +59,11 @@ func (cmd *RunCmd) Run(ctx *Context) error {
log.Println("Initialized parser") log.Println("Initialized parser")
} }
defer func() { tree, err := p.Parse()
if r := recover(); r != nil {
if r != "no more tokens" { // if the panic was not caused by the parser, it should not be recovered.
panic(r)
}
for _, e := range p.Errors {
print(e.Format([]rune(src)))
}
}
}()
tree := p.Parse()
// if there were parsing errors, print them out // if there were parsing errors, print them out
if len(p.Errors) > 0 { if err != nil {
for _, e := range p.Errors { print(err.(*core.ParsingError).Format([]rune(src)))
print(e.Format([]rune(src)))
}
log.Fatal("Parsing had errors") log.Fatal("Parsing had errors")
} }
@ -165,7 +152,12 @@ func (cmd *CompileCmd) Compile(ctx *Context) error {
if ctx.Debug { if ctx.Debug {
log.Println("Parsing tree") log.Println("Parsing tree")
} }
tree := p.Parse()
tree, err := p.Parse()
if err != nil {
print(err.(*core.ParsingError).Format([]rune(src)))
}
if ctx.Debug { if ctx.Debug {
log.Println("Initialized compiler") log.Println("Initialized compiler")

View file

@ -43,13 +43,11 @@ func TestAll(t *testing.T) {
p := NewParser(tokens) p := NewParser(tokens)
t.Log("Parsing tokens") t.Log("Parsing tokens")
tree := p.Parse() tree, err := p.Parse()
if p.hadError { if err != nil {
for _, e := range p.Errors { print(err.(*ParsingError).Format([]rune(tc.src)))
print(e.Format(tc.src)) t.Fatalf("parser had an error")
}
t.Fatalf("parser had error(s)")
} }
t.Log("Initializing compiler") t.Log("Initializing compiler")
@ -82,7 +80,7 @@ func BenchmarkAll(b *testing.B) {
tokens, _ := l.Tokenize() tokens, _ := l.Tokenize()
p := NewParser(tokens) p := NewParser(tokens)
tree := p.Parse() tree, _ := p.Parse()
c := NewCompiler() c := NewCompiler()
c.Compile(tree) c.Compile(tree)

View file

@ -13,6 +13,10 @@ type ParsingError struct {
Causer *Token Causer *Token
} }
func (p *ParsingError) Error() string {
return p.Description
}
// Print a rich and informative error // Print a rich and informative error
func (p *ParsingError) Format(src []rune) string { func (p *ParsingError) Format(src []rune) string {
builder := strings.Builder{} builder := strings.Builder{}
@ -58,21 +62,16 @@ type Parser struct {
prev *Token prev *Token
curr *Token curr *Token
pos Pos pos Pos
hadError bool
Errors []ParsingError
} }
func NewParser(tokens []Token) *Parser { func NewParser(tokens []Token) *Parser {
return &Parser{ return &Parser{
tokens: tokens, tokens: tokens,
pos: 0, pos: 0,
hadError: false,
Errors: make([]ParsingError, 0),
} }
} }
func (p *Parser) Parse() Node { func (p *Parser) Parse() (Node, error) {
// top level statements // top level statements
statements := make([]Node, 0) statements := make([]Node, 0)
@ -80,12 +79,18 @@ func (p *Parser) Parse() Node {
p.advance() p.advance()
for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF { for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF {
statements = append(statements, p.block(true)) b, err := p.block(true)
if err != nil {
return nil, err
}
statements = append(statements, b)
} }
return &BlockNode{ return &BlockNode{
statements: statements, statements: statements,
} }, nil
} }
func (p *Parser) accept(tokenType TokenType) bool { func (p *Parser) accept(tokenType TokenType) bool {
@ -102,11 +107,11 @@ func (p *Parser) accept(tokenType TokenType) bool {
return false return false
} }
func (p *Parser) expect(tokenType TokenType) { func (p *Parser) expect(tokenType TokenType) error {
if !p.accept(tokenType) { if !p.accept(tokenType) {
p.error("Expected token "+tokenType.String()+", got "+p.curr.Type.String(), p.curr) return p.error("Expected token "+tokenType.String()+", got "+p.curr.Type.String(), p.curr)
p.advance()
} }
return nil
} }
func (p *Parser) peek() (Token, error) { func (p *Parser) peek() (Token, error) {
@ -128,103 +133,125 @@ func (p *Parser) advance() {
} }
} }
func (p *Parser) error(error string, causer *Token) { func (p *Parser) error(error string, causer *Token) error {
p.hadError = true return &ParsingError{
p.Errors = append(p.Errors, ParsingError{
Description: error, Description: error,
Causer: causer, Causer: causer,
}) }
} }
func (p *Parser) factor() Node { func (p *Parser) factor() (Node, error) {
switch (*p.curr).Type { switch (*p.curr).Type {
case TokenString: case TokenString:
p.advance() p.advance()
return &StringNode{ return &StringNode{
(*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1], (*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1],
(*p.prev).Lexeme, (*p.prev).Lexeme,
} }, nil
case TokenNumber: case TokenNumber:
p.advance() p.advance()
num, err := strconv.ParseFloat((*p.prev).Lexeme, NumberSize) num, err := strconv.ParseFloat((*p.prev).Lexeme, NumberSize)
if err != nil { if err != nil {
p.error(fmt.Sprintf("Error parsing number: %v", err), p.prev) return nil, p.error(fmt.Sprintf("Error parsing number: %v", err), p.prev)
} }
return &NumberNode{ return &NumberNode{
NumberValue(num), NumberValue(num),
} }, nil
case TokenTrue: case TokenTrue:
p.advance() p.advance()
return &BooleanNode{ return &BooleanNode{
true, true,
} }, nil
case TokenFalse: case TokenFalse:
p.advance() p.advance()
return &BooleanNode{ return &BooleanNode{
false, false,
} }, nil
case TokenNil: case TokenNil:
p.advance() p.advance()
return &NilNode{} return &NilNode{}, nil
// unary minus // unary minus
case TokenMinus: case TokenMinus:
p.advance() p.advance()
f, err := p.factor()
if err != nil {
return nil, err
}
return &BinaryNode{ return &BinaryNode{
BinarySubtraction, BinarySubtraction,
&NumberNode{NumberValue(0)}, &NumberNode{NumberValue(0)},
p.factor(), f,
} }, nil
case TokenName: case TokenName:
p.advance() p.advance()
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
if p.curr.Type == TokenOpenParenthesis { if p.curr.Type == TokenOpenParenthesis {
args := p.parseArgs() args, err := p.parseArgs()
if err != nil {
return nil, err
}
return &CallNode{ return &CallNode{
name, name,
args, args,
true, true,
} }, nil
} }
return &ReferenceNode{ return &ReferenceNode{
name, name,
} }, nil
case TokenFunc: case TokenFunc:
p.advance() p.advance()
params := p.parseParams() params, err := p.parseParams()
if err != nil {
return nil, err
}
b, err := p.block(false)
if err != nil {
return nil, err
}
return &FunctionNode{ return &FunctionNode{
"*", "*",
params, params,
p.block(false), b,
} }, nil
case TokenOpenParenthesis: case TokenOpenParenthesis:
p.advance() p.advance()
v := p.condition() v, err := p.condition()
p.expect(TokenCloseParenthesis) if err != nil {
return nil, err
}
if err := p.expect(TokenCloseParenthesis); err != nil {
return nil, err
}
return v return v, nil
default: default:
p.error("invalid factor", p.curr) err := p.error("invalid factor", p.curr)
p.advance() p.advance()
return nil return nil, err
} }
} }
func (p *Parser) product() Node { func (p *Parser) product() (Node, error) {
left := p.factor() left, err := p.factor()
if err != nil {
return nil, err
}
for p.accept(TokenStar) || p.accept(TokenSlash) { for p.accept(TokenStar) || p.accept(TokenSlash) {
op := BinaryMultiplication op := BinaryMultiplication
@ -233,18 +260,26 @@ func (p *Parser) product() Node {
op = BinaryDivision op = BinaryDivision
} }
f, err := p.factor()
if err != nil {
return nil, err
}
left = &BinaryNode{ left = &BinaryNode{
op, op,
left, left,
p.factor(), f,
} }
} }
return left return left, nil
} }
func (p *Parser) term() Node { func (p *Parser) term() (Node, error) {
left := p.product() left, err := p.product()
if err != nil {
return nil, err
}
for p.accept(TokenPlus) || p.accept(TokenMinus) { for p.accept(TokenPlus) || p.accept(TokenMinus) {
op := BinaryAddition op := BinaryAddition
@ -253,18 +288,28 @@ func (p *Parser) term() Node {
op = BinarySubtraction op = BinarySubtraction
} }
pr, err := p.product()
if err != nil {
return nil, err
}
left = &BinaryNode{ left = &BinaryNode{
op, op,
left, left,
p.product(), pr,
} }
} }
return left return left, nil
} }
func (p *Parser) comparison() Node { func (p *Parser) comparison() (Node, error) {
left := p.term() left, err := p.term()
if err != nil {
return nil, err
}
op := BinaryEquality op := BinaryEquality
switch (*p.curr).Type { switch (*p.curr).Type {
@ -281,20 +326,30 @@ func (p *Parser) comparison() Node {
case TokenGreaterThanOrEqual: case TokenGreaterThanOrEqual:
op = BinaryGreaterEqual op = BinaryGreaterEqual
default: default:
return left return left, nil
} }
p.advance() p.advance()
t, err := p.term()
if err != nil {
return nil, err
}
return &BinaryNode{ return &BinaryNode{
op, op,
left, left,
p.term(), t,
} }, nil
} }
func (p *Parser) condition() Node { func (p *Parser) condition() (Node, error) {
left := p.comparison() left, err := p.comparison()
if err != nil {
return nil, err
}
op := BinaryEquality op := BinaryEquality
switch (*p.curr).Type { switch (*p.curr).Type {
@ -303,57 +358,80 @@ func (p *Parser) condition() Node {
case TokenDoublePipe: case TokenDoublePipe:
op = BinaryOr op = BinaryOr
default: default:
return left return left, nil
} }
p.advance() p.advance()
c, err := p.comparison()
if err != nil {
return left, err
}
return &BinaryNode{ return &BinaryNode{
op, op,
left, left,
p.comparison(), c,
} }, nil
} }
func (p *Parser) statement() Node { func (p *Parser) statement() (Node, error) {
switch (*p.curr).Type { switch (*p.curr).Type {
case TokenIf: case TokenIf:
p.advance() p.advance()
condition := p.condition() condition, err := p.condition()
then := p.block(false) if err != nil {
return nil, err
}
then, err := p.block(false)
if err != nil {
return nil, err
}
var otherwise Node var otherwise Node
if p.accept(TokenElse) { if p.accept(TokenElse) {
otherwise = p.block(false) otherwise, err = p.block(false)
if err != nil {
return nil, err
}
} }
return &ConditionalNode{ return &ConditionalNode{
condition, condition,
then, then,
otherwise, otherwise,
} }, nil
case TokenName: case TokenName:
p.advance() p.advance()
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
if p.curr.Type == TokenOpenParenthesis { if p.curr.Type == TokenOpenParenthesis {
args := p.parseArgs() args, err := p.parseArgs()
if err != nil {
return nil, err
}
return &CallNode{ return &CallNode{
name, name,
args, args,
false, false,
} }, nil
} else if p.accept(TokenAssign) || p.accept(TokenDeclare) { } else if p.accept(TokenAssign) || p.accept(TokenDeclare) {
isDeclaration := p.prev.Type == TokenDeclare isDeclaration := p.prev.Type == TokenDeclare
c, err := p.condition()
if err != nil {
return nil, err
}
return &AssignNode{ return &AssignNode{
name, name,
p.condition(), c,
isDeclaration, isDeclaration,
} }, nil
} else { } else {
return p.condition() return p.condition()
} }
@ -361,101 +439,154 @@ func (p *Parser) statement() Node {
case TokenFunc: case TokenFunc:
p.advance() p.advance()
p.expect(TokenName) if err := p.expect(TokenName); err != nil {
return nil, err
}
name := p.prev.Lexeme name := p.prev.Lexeme
params := p.parseParams() params, err := p.parseParams()
if err != nil {
return nil, err
}
b, err := p.block(false)
if err != nil {
return nil, err
}
return &AssignNode{ return &AssignNode{
name, name,
&FunctionNode{ &FunctionNode{
name, name,
params, params,
p.block(false), b,
}, },
true, true,
} }, nil
case TokenWhile: case TokenWhile:
p.advance() p.advance()
return &LoopNode{ c, err := p.condition()
p.condition(), if err != nil {
p.block(false), return nil, err
} }
b, err := p.block(false)
if err != nil {
return nil, err
}
return &LoopNode{
c,
b,
}, nil
case TokenReturn: case TokenReturn:
p.advance() p.advance()
return &ReturnNode{ c, err := p.condition()
p.condition(), if err != nil {
return nil, err
} }
return &ReturnNode{
c,
}, nil
case TokenBreakpoint: case TokenBreakpoint:
p.advance() p.advance()
return &BreakpointNode{} return &BreakpointNode{}, nil
default: default:
p.error("invalid statement", p.curr) err := p.error("invalid statement", p.curr)
p.advance() p.advance()
return nil return nil, err
} }
} }
func (p *Parser) block(canBeStatement bool) Node { func (p *Parser) block(canBeStatement bool) (Node, error) {
if canBeStatement { if canBeStatement {
if !p.accept(TokenOpenBrace) { if !p.accept(TokenOpenBrace) {
return p.statement() return p.statement()
} }
} else { } else {
p.expect(TokenOpenBrace) if err := p.expect(TokenOpenBrace); err != nil {
return nil, err
}
} }
statements := make([]Node, 0) statements := make([]Node, 0)
for !p.accept(TokenCloseBrace) { for !p.accept(TokenCloseBrace) {
statements = append(statements, p.statement()) s, err := p.statement()
if err != nil {
return nil, err
}
statements = append(statements, s)
} }
return &BlockNode{ return &BlockNode{
statements, statements,
} }, nil
} }
func (p *Parser) parseArgs() []Node { func (p *Parser) parseArgs() ([]Node, error) {
args := make([]Node, 0) args := make([]Node, 0)
p.expect(TokenOpenParenthesis) if err := p.expect(TokenOpenParenthesis); err != nil {
return nil, err
}
if !p.accept(TokenCloseParenthesis) { if !p.accept(TokenCloseParenthesis) {
args = append(args, p.condition()) c, err := p.condition()
if err != nil {
return nil, err
}
args = append(args, c)
for !p.accept(TokenCloseParenthesis) { for !p.accept(TokenCloseParenthesis) {
p.expect(TokenComma) if err := p.expect(TokenComma); err != nil {
args = append(args, p.condition()) return nil, err
}
c, err = p.condition()
if err != nil {
return nil, err
}
args = append(args)
} }
} }
return args return args, nil
} }
// parseParams parse parameters and parentheses // parseParams parse parameters and parentheses
func (p *Parser) parseParams() []string { func (p *Parser) parseParams() ([]string, error) {
p.expect(TokenOpenParenthesis) if err := p.expect(TokenOpenParenthesis); err != nil {
return nil, err
}
params := make([]string, 0) params := make([]string, 0)
if p.accept(TokenName) { if p.accept(TokenName) {
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
params = append(params, name) params = append(params, name)
for !p.accept(TokenCloseParenthesis) { for !p.accept(TokenCloseParenthesis) {
p.expect(TokenComma) if err := p.expect(TokenComma); err != nil {
p.expect(TokenName) return nil, err
}
if err := p.expect(TokenName); err != nil {
return nil, err
}
name = (*p.prev).Lexeme name = (*p.prev).Lexeme
params = append(params, name) params = append(params, name)
} }
} else { } else {
p.expect(TokenCloseParenthesis) if err := p.expect(TokenCloseParenthesis); err != nil {
return nil, err
}
} }
return params return params, nil
} }

View file

@ -14,10 +14,6 @@ func TestNewParser(t *testing.T) {
t.Fatal("parser should not be nil") t.Fatal("parser should not be nil")
} }
if p.hadError {
t.Error("parser should not when initialized report an error")
}
if p.pos != 0 { if p.pos != 0 {
t.Error("parser should initialize position at 0") t.Error("parser should initialize position at 0")
} }
@ -602,10 +598,10 @@ func TestParser_Parse(t *testing.T) {
p := NewParser(data.tokens) p := NewParser(data.tokens)
t.Logf("Parsing main") t.Logf("Parsing main")
tree := p.Parse() tree, err := p.Parse()
if p.hadError { if err != nil {
t.Fatalf("Unexpected error(s): %s", p.Errors) t.Fatalf("Unexpected error(s): %s", err.(*ParsingError).Format([]rune{}))
} }
t.Logf("Checking parsed tree") t.Logf("Checking parsed tree")
@ -622,7 +618,7 @@ func BenchmarkParser_Parse(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
p := NewParser(data.tokens) p := NewParser(data.tokens)
_ = p.Parse() _, _ = p.Parse()
} }
}) })
} }

View file

@ -39,14 +39,14 @@ func run(this js.Value, args []js.Value) interface{} {
parser := core.NewParser(tokens) parser := core.NewParser(tokens)
tree := parser.Parse() tree, err := parser.Parse()
if err != nil {
return jsErrorOfString(err.Error())
}
log.Printf("Parsed tree: %s", tree.String()) log.Printf("Parsed tree: %s", tree.String())
if len(parser.Errors) > 0 {
return jsErrorOfString(parser.Errors[0].Description)
}
compiler := core.NewCompiler() compiler := core.NewCompiler()
compiler.Compile(tree) compiler.Compile(tree)