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")
}
defer func() {
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()
tree, err := p.Parse()
// if there were parsing errors, print them out
if len(p.Errors) > 0 {
for _, e := range p.Errors {
print(e.Format([]rune(src)))
}
if err != nil {
print(err.(*core.ParsingError).Format([]rune(src)))
log.Fatal("Parsing had errors")
}
@ -165,7 +152,12 @@ func (cmd *CompileCmd) Compile(ctx *Context) error {
if ctx.Debug {
log.Println("Parsing tree")
}
tree := p.Parse()
tree, err := p.Parse()
if err != nil {
print(err.(*core.ParsingError).Format([]rune(src)))
}
if ctx.Debug {
log.Println("Initialized compiler")

View file

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

View file

@ -13,6 +13,10 @@ type ParsingError struct {
Causer *Token
}
func (p *ParsingError) Error() string {
return p.Description
}
// Print a rich and informative error
func (p *ParsingError) Format(src []rune) string {
builder := strings.Builder{}
@ -58,21 +62,16 @@ type Parser struct {
prev *Token
curr *Token
pos Pos
hadError bool
Errors []ParsingError
}
func NewParser(tokens []Token) *Parser {
return &Parser{
tokens: tokens,
pos: 0,
hadError: false,
Errors: make([]ParsingError, 0),
tokens: tokens,
pos: 0,
}
}
func (p *Parser) Parse() Node {
func (p *Parser) Parse() (Node, error) {
// top level statements
statements := make([]Node, 0)
@ -80,12 +79,18 @@ func (p *Parser) Parse() Node {
p.advance()
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{
statements: statements,
}
}, nil
}
func (p *Parser) accept(tokenType TokenType) bool {
@ -102,11 +107,11 @@ func (p *Parser) accept(tokenType TokenType) bool {
return false
}
func (p *Parser) expect(tokenType TokenType) {
func (p *Parser) expect(tokenType TokenType) error {
if !p.accept(tokenType) {
p.error("Expected token "+tokenType.String()+", got "+p.curr.Type.String(), p.curr)
p.advance()
return p.error("Expected token "+tokenType.String()+", got "+p.curr.Type.String(), p.curr)
}
return nil
}
func (p *Parser) peek() (Token, error) {
@ -128,103 +133,125 @@ func (p *Parser) advance() {
}
}
func (p *Parser) error(error string, causer *Token) {
p.hadError = true
p.Errors = append(p.Errors, ParsingError{
func (p *Parser) error(error string, causer *Token) error {
return &ParsingError{
Description: error,
Causer: causer,
})
}
}
func (p *Parser) factor() Node {
func (p *Parser) factor() (Node, error) {
switch (*p.curr).Type {
case TokenString:
p.advance()
return &StringNode{
(*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1],
(*p.prev).Lexeme,
}
}, nil
case TokenNumber:
p.advance()
num, err := strconv.ParseFloat((*p.prev).Lexeme, NumberSize)
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{
NumberValue(num),
}
}, nil
case TokenTrue:
p.advance()
return &BooleanNode{
true,
}
}, nil
case TokenFalse:
p.advance()
return &BooleanNode{
false,
}
}, nil
case TokenNil:
p.advance()
return &NilNode{}
return &NilNode{}, nil
// unary minus
case TokenMinus:
p.advance()
f, err := p.factor()
if err != nil {
return nil, err
}
return &BinaryNode{
BinarySubtraction,
&NumberNode{NumberValue(0)},
p.factor(),
}
f,
}, nil
case TokenName:
p.advance()
name := (*p.prev).Lexeme
if p.curr.Type == TokenOpenParenthesis {
args := p.parseArgs()
args, err := p.parseArgs()
if err != nil {
return nil, err
}
return &CallNode{
name,
args,
true,
}
}, nil
}
return &ReferenceNode{
name,
}
}, nil
case TokenFunc:
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{
"*",
params,
p.block(false),
}
b,
}, nil
case TokenOpenParenthesis:
p.advance()
v := p.condition()
p.expect(TokenCloseParenthesis)
v, err := p.condition()
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseParenthesis); err != nil {
return nil, err
}
return v
return v, nil
default:
p.error("invalid factor", p.curr)
err := p.error("invalid factor", p.curr)
p.advance()
return nil
return nil, err
}
}
func (p *Parser) product() Node {
left := p.factor()
func (p *Parser) product() (Node, error) {
left, err := p.factor()
if err != nil {
return nil, err
}
for p.accept(TokenStar) || p.accept(TokenSlash) {
op := BinaryMultiplication
@ -233,18 +260,26 @@ func (p *Parser) product() Node {
op = BinaryDivision
}
f, err := p.factor()
if err != nil {
return nil, err
}
left = &BinaryNode{
op,
left,
p.factor(),
f,
}
}
return left
return left, nil
}
func (p *Parser) term() Node {
left := p.product()
func (p *Parser) term() (Node, error) {
left, err := p.product()
if err != nil {
return nil, err
}
for p.accept(TokenPlus) || p.accept(TokenMinus) {
op := BinaryAddition
@ -253,18 +288,28 @@ func (p *Parser) term() Node {
op = BinarySubtraction
}
pr, err := p.product()
if err != nil {
return nil, err
}
left = &BinaryNode{
op,
left,
p.product(),
pr,
}
}
return left
return left, nil
}
func (p *Parser) comparison() Node {
left := p.term()
func (p *Parser) comparison() (Node, error) {
left, err := p.term()
if err != nil {
return nil, err
}
op := BinaryEquality
switch (*p.curr).Type {
@ -281,20 +326,30 @@ func (p *Parser) comparison() Node {
case TokenGreaterThanOrEqual:
op = BinaryGreaterEqual
default:
return left
return left, nil
}
p.advance()
t, err := p.term()
if err != nil {
return nil, err
}
return &BinaryNode{
op,
left,
p.term(),
}
t,
}, nil
}
func (p *Parser) condition() Node {
left := p.comparison()
func (p *Parser) condition() (Node, error) {
left, err := p.comparison()
if err != nil {
return nil, err
}
op := BinaryEquality
switch (*p.curr).Type {
@ -303,57 +358,80 @@ func (p *Parser) condition() Node {
case TokenDoublePipe:
op = BinaryOr
default:
return left
return left, nil
}
p.advance()
c, err := p.comparison()
if err != nil {
return left, err
}
return &BinaryNode{
op,
left,
p.comparison(),
}
c,
}, nil
}
func (p *Parser) statement() Node {
func (p *Parser) statement() (Node, error) {
switch (*p.curr).Type {
case TokenIf:
p.advance()
condition := p.condition()
then := p.block(false)
condition, err := p.condition()
if err != nil {
return nil, err
}
then, err := p.block(false)
if err != nil {
return nil, err
}
var otherwise Node
if p.accept(TokenElse) {
otherwise = p.block(false)
otherwise, err = p.block(false)
if err != nil {
return nil, err
}
}
return &ConditionalNode{
condition,
then,
otherwise,
}
}, nil
case TokenName:
p.advance()
name := (*p.prev).Lexeme
if p.curr.Type == TokenOpenParenthesis {
args := p.parseArgs()
args, err := p.parseArgs()
if err != nil {
return nil, err
}
return &CallNode{
name,
args,
false,
}
}, nil
} else if p.accept(TokenAssign) || p.accept(TokenDeclare) {
isDeclaration := p.prev.Type == TokenDeclare
c, err := p.condition()
if err != nil {
return nil, err
}
return &AssignNode{
name,
p.condition(),
c,
isDeclaration,
}
}, nil
} else {
return p.condition()
}
@ -361,101 +439,154 @@ func (p *Parser) statement() Node {
case TokenFunc:
p.advance()
p.expect(TokenName)
if err := p.expect(TokenName); err != nil {
return nil, err
}
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{
name,
&FunctionNode{
name,
params,
p.block(false),
b,
},
true,
}
}, nil
case TokenWhile:
p.advance()
return &LoopNode{
p.condition(),
p.block(false),
c, err := p.condition()
if err != nil {
return nil, err
}
b, err := p.block(false)
if err != nil {
return nil, err
}
return &LoopNode{
c,
b,
}, nil
case TokenReturn:
p.advance()
return &ReturnNode{
p.condition(),
c, err := p.condition()
if err != nil {
return nil, err
}
return &ReturnNode{
c,
}, nil
case TokenBreakpoint:
p.advance()
return &BreakpointNode{}
return &BreakpointNode{}, nil
default:
p.error("invalid statement", p.curr)
err := p.error("invalid statement", p.curr)
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 !p.accept(TokenOpenBrace) {
return p.statement()
}
} else {
p.expect(TokenOpenBrace)
if err := p.expect(TokenOpenBrace); err != nil {
return nil, err
}
}
statements := make([]Node, 0)
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{
statements,
}
}, nil
}
func (p *Parser) parseArgs() []Node {
func (p *Parser) parseArgs() ([]Node, error) {
args := make([]Node, 0)
p.expect(TokenOpenParenthesis)
if err := p.expect(TokenOpenParenthesis); err != nil {
return nil, err
}
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) {
p.expect(TokenComma)
args = append(args, p.condition())
if err := p.expect(TokenComma); err != nil {
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
func (p *Parser) parseParams() []string {
p.expect(TokenOpenParenthesis)
func (p *Parser) parseParams() ([]string, error) {
if err := p.expect(TokenOpenParenthesis); err != nil {
return nil, err
}
params := make([]string, 0)
if p.accept(TokenName) {
name := (*p.prev).Lexeme
params = append(params, name)
for !p.accept(TokenCloseParenthesis) {
p.expect(TokenComma)
p.expect(TokenName)
if err := p.expect(TokenComma); err != nil {
return nil, err
}
if err := p.expect(TokenName); err != nil {
return nil, err
}
name = (*p.prev).Lexeme
params = append(params, name)
}
} 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")
}
if p.hadError {
t.Error("parser should not when initialized report an error")
}
if p.pos != 0 {
t.Error("parser should initialize position at 0")
}
@ -602,10 +598,10 @@ func TestParser_Parse(t *testing.T) {
p := NewParser(data.tokens)
t.Logf("Parsing main")
tree := p.Parse()
tree, err := p.Parse()
if p.hadError {
t.Fatalf("Unexpected error(s): %s", p.Errors)
if err != nil {
t.Fatalf("Unexpected error(s): %s", err.(*ParsingError).Format([]rune{}))
}
t.Logf("Checking parsed tree")
@ -622,7 +618,7 @@ func BenchmarkParser_Parse(b *testing.B) {
for i := 0; i < b.N; i++ {
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)
tree := parser.Parse()
tree, err := parser.Parse()
if err != nil {
return jsErrorOfString(err.Error())
}
log.Printf("Parsed tree: %s", tree.String())
if len(parser.Errors) > 0 {
return jsErrorOfString(parser.Errors[0].Description)
}
compiler := core.NewCompiler()
compiler.Compile(tree)