Source code for uppaal2jetracer.parser.declarationparser.plyparser

"""
This module contains the implementation of a PLY-based parser utility
designed to aid in constructing and using parsers. It includes classes for
representing coordinates of syntax elements (`Coord`), custom exception handling
(`ParseError`), and a `PLYParser` class that provides helper methods for rule 
creation, error handling, and token coordinate computation.

Additionally, the module defines utilities for dynamic rule generation, including 
parameterized rule decorators (`parameterized`) and a class decorator (`template`) 
to facilitate the creation of PLY rules from templates.

Based on the plyparser.py from the pycparser module by Eli Bendersky (https://eli.thegreenplace.net)
under the BSD license.

Key Components:
    * `Coord`: Represents the file, line, and optionally column for syntax elements.
    * `PLYParser`: Provides methods to handle optional rules, error reporting, and coordinate management for tokens during parsing.
    * `parameterized`: Decorator for creating parameterized parsing rules.
    * `template`: Class decorator for generating rules from parameterized templates.

This module can be used as part of a larger system to build a parser using 
the PLY (Python Lex-Yacc) library.
"""
import warnings


[docs] class Coord: """ Coordinates of a syntactic element. Consists of: :ivar file: Name of the file. :type file: str :ivar line: The line number of the coordinate in the input string :type line: int :ivar column: The column number of the coordinate in the input string :type column: int """ __slots__ = ("file", "line", "column", "__weakref__") def __init__(self, file, line, column = None): self.file = file self.line = line self.column = column def __str__(self): return f"{self.file}:{self.line}{f":{self.column}" if self.column else ""}"
[docs] class ParseError(Exception): """ Thrown if yacc has an internal problem or by :class:`PLYParser` if a token cannot be parsed. """
[docs] class PLYParser: """ Facilitates parsing tasks with helper methods and error handling specifically designed to work with the PLY parsing library. This class aids in managing parsing rules, tracking coordinates during parsing, and generating useful error messages for syntax issues. :ivar dlex: Lexer instance providing input and tokens for parsing. :type dlex: Lexer """ __slots__ = ("dlex",) def _create_opt_rule(self, rulename): """ Given a rule name, creates an optional ply.yacc rule for it. The name of the optional rule is <rulename>_opt """ optname = f"{rulename}_opt" # do NOT delete "self" attribute! def optrule(self, p): """ Empty framework for a new rule to be added to yacc. """ p[0] = p[1] optrule.__doc__ = f"{optname} : empty\n| {rulename}" optrule.__name__ = f"p_{optname}" setattr(self.__class__, optrule.__name__, optrule) def _coord(self, lineno, column = None): return Coord( file = self.dlex.filename, line = lineno, column = column ) def _token_coord(self, p, token_idx): """ Returns the coordinates for the YaccProduction object "p" indexed with "token_idx". The coordinate includes the "lineno" and "column". Both follow the lex semantic, starting from 1. """ last_cr = p.lexer.lexer.lexdata.rfind("\n", 0, p.lexpos(token_idx)) if last_cr < 0: last_cr = -1 column = p.lexpos(token_idx) - last_cr return self._coord(p.lineno(token_idx), column) def _parse_error(self, msg, coord): raise ParseError(f"Error: {coord}: {msg}.")
## ## Help methods ##
[docs] def parameterized(*params): """ Decorator to create parameterized rules. """ # Parameterized rule methods must be named starting with "p_" and contain # "xxx", and their docstrings may contain "xxx" and "yyy". These will be # replaced by the given parameter tuples. For example, "p_xxx_rule()" with # docstring "xxx_rule : yyy" when decorated with # "@parameterized(("id", "ID"))" produces "p_id_rule()" with the docstring # "id_rule : ID". Using multiple tuples produces multiple rules. def decorate(rule_func): """ Decorates a rule with parameters. """ rule_func._params = params return rule_func return decorate
[docs] def template(cls): """ Class decorator to generate rules from parameterized rule templates. See `parameterized` for more information on parameterized rules. """ issued_nodoc_warning = False for attr_name in dir(cls): if attr_name.startswith("p_"): method = getattr(cls, attr_name) if hasattr(method, "_params"): # Remove the template method delattr(cls, attr_name) # Create parameterized rules from this method; only run this if # the method has a docstring. if method.__doc__ is not None: _create_param_rules(cls, method) elif not issued_nodoc_warning: warnings.warn( "parsing methods must have __doc__ for pycparser to work properly", RuntimeWarning, stacklevel = 2) issued_nodoc_warning = True return cls
def _create_param_rules(cls, func): """ Create ply.yacc rules based on a parameterized rule function Generates new methods (one per each pair of parameters) based on the template rule function "func", and attaches them to "cls". The rule function's parameters must be accessible via its "_params" attribute. """ for xxx, yyy in func._params: # Use the template method's body for each new method def param_rule(self, p): """ Empty framework for a new rule to be added to yacc. """ func(self, p) # Substitute in the params for the grammar rule and function name param_rule.__doc__ = func.__doc__.replace("xxx", xxx).replace("yyy", yyy) param_rule.__name__ = func.__name__.replace("xxx", xxx) # Attach the new method to the class setattr(cls, param_rule.__name__, param_rule)