Source code for ebnf_compiler.parser

# SPDX-FileCopyrightText: 2026 Filipe Casimiro Ferreira <pro.maiscommentz@gmail.com>
#
# SPDX-License-Identifier: MIT

"""
EBNF Parser
"""

from dataclasses import dataclass

from loguru import logger

from . import ast
from .scanner import Scanner
from .tokens import Token


[docs] @dataclass class Parser: scanner: Scanner has_error: bool = False
[docs] def raise_error(self, msg: str) -> None: self.has_error = True self.scanner.print_error(msg) raise Exception(msg)
[docs] def raise_expected_error(self, expected: Token): self.raise_error(f"Expected '{expected}', but got '{self.scanner.sym}'")
[docs] def factor(self) -> ast.Factor: logger.debug("Factor") sym = self.scanner.sym if sym == Token.IDENT: val = self.scanner.value self.scanner.get_next_symbol() return ast.Identifier(value=val) elif sym == Token.LITERAL: val = self.scanner.value self.scanner.get_next_symbol() return ast.Literal(value=val) elif sym == Token.LPAREN: self.scanner.get_next_symbol() expr = self.expression() expr.paren = True if self.scanner.sym != Token.RPAREN: self.raise_expected_error(Token.RPAREN) self.scanner.get_next_symbol() return expr elif sym == Token.LBRAK: self.scanner.get_next_symbol() expr = self.expression() if self.scanner.sym != Token.RBRAK: self.raise_expected_error(Token.RBRAK) self.scanner.get_next_symbol() return ast.Option(expr=expr) elif sym == Token.LBRACE: self.scanner.get_next_symbol() expr = self.expression() if self.scanner.sym != Token.RBRACE: self.raise_expected_error(Token.RBRACE) self.scanner.get_next_symbol() return ast.Repetition(expr=expr) else: self.raise_error( f"Expected identifier, literal, (', '['. or '{{' got {sym}" ) raise Exception("Unexpected symbol")
[docs] def term(self) -> ast.Term: logger.debug("Term") factors = [] factors.append(self.factor()) while self.scanner.sym in ( Token.IDENT, Token.LITERAL, Token.LPAREN, Token.LBRAK, Token.LBRACE, ): factors.append(self.factor()) return ast.Term(factors=factors)
[docs] def expression(self) -> ast.Expression: logger.debug("Expression") terms = [] terms.append(self.term()) while self.scanner.sym == Token.BAR: self.scanner.get_next_symbol() terms.append(self.term()) return ast.Expression(terms=terms)
[docs] def production(self) -> ast.Production: logger.debug("Production") if self.scanner.sym != Token.IDENT: self.raise_expected_error(Token.IDENT) ident = ast.Identifier(value=self.scanner.value) self.scanner.get_next_symbol() if self.scanner.sym != Token.EQL: self.raise_expected_error(Token.EQL) self.scanner.get_next_symbol() expr = self.expression() if self.scanner.sym != Token.PERIOD: self.raise_expected_error(Token.PERIOD) self.scanner.get_next_symbol() return ast.Production(identifier=ident, expression=expr)
[docs] def syntax(self) -> ast.Syntax: logger.debug("Syntax") productions = [] while self.scanner.sym != Token.EOF: if self.scanner.sym != Token.IDENT: self.raise_expected_error(Token.IDENT) productions.append(self.production()) return ast.Syntax(production=productions)
[docs] def parse(self) -> ast.Syntax: logger.debug("Parsing") self.scanner.get_next_symbol() return self.syntax()