From b3ef412af1a74db58484be7df9258f5549d8972f Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Thu, 5 Feb 2026 15:04:34 +0100 Subject: [PATCH] fix(parser): add parsing error handling --- main.py | 5 ++++- src/parser/error.py | 2 ++ src/parser/parser.py | 34 +++++++++++++++++++++++++++------- src/pebble.py | 19 +++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 src/parser/error.py create mode 100644 src/pebble.py diff --git a/main.py b/main.py index 5542da7..52cacac 100644 --- a/main.py +++ b/main.py @@ -32,7 +32,10 @@ def main(): print(printer.print(ast)) parser: Parser = Parser() - ast = parser.process(tokens) + ast = parser.parse(tokens) + + if ast is None: + return print(printer.print(ast)) diff --git a/src/parser/error.py b/src/parser/error.py new file mode 100644 index 0000000..e8e65fb --- /dev/null +++ b/src/parser/error.py @@ -0,0 +1,2 @@ +class ParsingError(RuntimeError): + pass diff --git a/src/parser/parser.py b/src/parser/parser.py index fdcc9fb..59df0b4 100644 --- a/src/parser/parser.py +++ b/src/parser/parser.py @@ -1,4 +1,8 @@ +from typing import Optional + 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 @@ -7,21 +11,29 @@ class Parser: TokenType.WHITESPACE, TokenType.COMMENT } + STATEMENT_BOUNDARY: set[TokenType] = { + TokenType.FOR, TokenType.WHILE, TokenType.IF, TokenType.PRINT + } + def __init__(self): self.tokens: list[Token] = [] self.current: int = 0 self.length: int = 0 - def error(self, token: Token, msg: str): - lexeme: str = "end" if token.type == TokenType.EOF else f"'{token.lexeme}'" - raise SyntaxError(f"[ERROR] Invalid syntax at {lexeme} ({token.position}): {msg}") + @staticmethod + def error(token: Token, msg: str): + 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.current = 0 self.length = len(self.tokens) - return self.expression() + try: + return self.expression() + except ParsingError: + return None def is_at_end(self) -> bool: return self.current >= self.length @@ -51,9 +63,17 @@ class Parser: def consume(self, token_type: TokenType, error_msg: str): if not self.match(token_type): - self.error(self.peek(), error_msg) + raise self.error(self.peek(), error_msg) # 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: return self.equality() @@ -112,4 +132,4 @@ class Parser: self.consume(TokenType.RIGHT_PAREN, "Unclosed parenthesis") return GroupingExpr(expr) - self.error(self.peek(), "Malformed expression") + raise self.error(self.peek(), "Expected expression") diff --git a/src/pebble.py b/src/pebble.py new file mode 100644 index 0000000..6a3f031 --- /dev/null +++ b/src/pebble.py @@ -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)