fix(parser): add parsing error handling
This commit is contained in:
5
main.py
5
main.py
@@ -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
2
src/parser/error.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
class ParsingError(RuntimeError):
|
||||||
|
pass
|
||||||
@@ -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)
|
||||||
|
|
||||||
return self.expression()
|
try:
|
||||||
|
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
19
src/pebble.py
Normal 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)
|
||||||
Reference in New Issue
Block a user