fix(parser): add parsing error handling

This commit is contained in:
2026-02-05 15:04:34 +01:00
parent 2028c7cf58
commit b3ef412af1
4 changed files with 52 additions and 8 deletions

View File

@@ -32,7 +32,10 @@ def main():
print(printer.print(ast)) print(printer.print(ast))
parser: Parser = Parser() parser: Parser = Parser()
ast = parser.process(tokens) ast = parser.parse(tokens)
if ast is None:
return
print(printer.print(ast)) print(printer.print(ast))

2
src/parser/error.py Normal file
View File

@@ -0,0 +1,2 @@
class ParsingError(RuntimeError):
pass

View File

@@ -1,4 +1,8 @@
from typing import Optional
from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr
from src.parser.error import ParsingError
from src.pebble import Pebble
from src.token import Token, TokenType from src.token import Token, TokenType
@@ -7,21 +11,29 @@ class Parser:
TokenType.WHITESPACE, TokenType.COMMENT TokenType.WHITESPACE, TokenType.COMMENT
} }
STATEMENT_BOUNDARY: set[TokenType] = {
TokenType.FOR, TokenType.WHILE, TokenType.IF, TokenType.PRINT
}
def __init__(self): def __init__(self):
self.tokens: list[Token] = [] self.tokens: list[Token] = []
self.current: int = 0 self.current: int = 0
self.length: int = 0 self.length: int = 0
def error(self, token: Token, msg: str): @staticmethod
lexeme: str = "end" if token.type == TokenType.EOF else f"'{token.lexeme}'" def error(token: Token, msg: str):
raise SyntaxError(f"[ERROR] Invalid syntax at {lexeme} ({token.position}): {msg}") Pebble.token_error(token, msg)
return ParsingError()
def process(self, tokens: list[Token]): def parse(self, tokens: list[Token]) -> Optional[Expr]:
self.tokens = list(filter(lambda t: t.type not in self.IGNORE, tokens)) self.tokens = list(filter(lambda t: t.type not in self.IGNORE, tokens))
self.current = 0 self.current = 0
self.length = len(self.tokens) self.length = len(self.tokens)
try:
return self.expression() return self.expression()
except ParsingError:
return None
def is_at_end(self) -> bool: def is_at_end(self) -> bool:
return self.current >= self.length return self.current >= self.length
@@ -51,9 +63,17 @@ class Parser:
def consume(self, token_type: TokenType, error_msg: str): def consume(self, token_type: TokenType, error_msg: str):
if not self.match(token_type): if not self.match(token_type):
self.error(self.peek(), error_msg) raise self.error(self.peek(), error_msg)
# Parsing # Parsing
def synchronize(self):
self.advance()
while not self.is_at_end():
# TODO: if self.previous().type == TokenType.NEWLINE: return
if self.peek().type in self.STATEMENT_BOUNDARY:
return
self.advance()
def expression(self) -> Expr: def expression(self) -> Expr:
return self.equality() return self.equality()
@@ -112,4 +132,4 @@ class Parser:
self.consume(TokenType.RIGHT_PAREN, "Unclosed parenthesis") self.consume(TokenType.RIGHT_PAREN, "Unclosed parenthesis")
return GroupingExpr(expr) return GroupingExpr(expr)
self.error(self.peek(), "Malformed expression") raise self.error(self.peek(), "Expected expression")

19
src/pebble.py Normal file
View File

@@ -0,0 +1,19 @@
from src.position import Position
from src.token import Token, TokenType
class Pebble:
@staticmethod
def report(position: Position, where: str, msg: str):
print(f"({position}) Error{where}: {msg}")
@classmethod
def error(cls, position: Position, msg: str):
cls.report(position, "", msg)
@classmethod
def token_error(cls, token: Token, msg: str):
if token.type == TokenType.EOF:
cls.report(token.position, " at end", msg)
else:
cls.report(token.position, f" at '{token.lexeme}'", msg)