codekingpro/portable-devtools
114k
1#-----------------------------------------------------------------2# plyparser.py3#4# PLYParser class and other utilities for simplifying programming5# parsers with PLY6#7# Eli Bendersky [https://eli.thegreenplace.net/]8# License: BSD9#-----------------------------------------------------------------10 11import warnings12 13class Coord(object):14 """ Coordinates of a syntactic element. Consists of:15 - File name16 - Line number17 - (optional) column number, for the Lexer18 """19 __slots__ = ('file', 'line', 'column', '__weakref__')20 def __init__(self, file, line, column=None):21 self.file = file22 self.line = line23 self.column = column24 25 def __str__(self):26 str = "%s:%s" % (self.file, self.line)27 if self.column: str += ":%s" % self.column28 return str29 30 31class ParseError(Exception): pass32 33 34class PLYParser(object):35 def _create_opt_rule(self, rulename):36 """ Given a rule name, creates an optional ply.yacc rule37 for it. The name of the optional rule is38 <rulename>_opt39 """40 optname = rulename + '_opt'41 42 def optrule(self, p):43 p[0] = p[1]44 45 optrule.__doc__ = '%s : empty\n| %s' % (optname, rulename)46 optrule.__name__ = 'p_%s' % optname47 setattr(self.__class__, optrule.__name__, optrule)48 49 def _coord(self, lineno, column=None):50 return Coord(51 file=self.clex.filename,52 line=lineno,53 column=column)54 55 def _token_coord(self, p, token_idx):56 """ Returns the coordinates for the YaccProduction object 'p' indexed57 with 'token_idx'. The coordinate includes the 'lineno' and58 'column'. Both follow the lex semantic, starting from 1.59 """60 last_cr = p.lexer.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))61 if last_cr < 0:62 last_cr = -163 column = (p.lexpos(token_idx) - (last_cr))64 return self._coord(p.lineno(token_idx), column)65 66 def _parse_error(self, msg, coord):67 raise ParseError("%s: %s" % (coord, msg))68 69 70def parameterized(*params):71 """ Decorator to create parameterized rules.72 73 Parameterized rule methods must be named starting with 'p_' and contain74 'xxx', and their docstrings may contain 'xxx' and 'yyy'. These will be75 replaced by the given parameter tuples. For example, ``p_xxx_rule()`` with76 docstring 'xxx_rule : yyy' when decorated with77 ``@parameterized(('id', 'ID'))`` produces ``p_id_rule()`` with the docstring78 'id_rule : ID'. Using multiple tuples produces multiple rules.79 """80 def decorate(rule_func):81 rule_func._params = params82 return rule_func83 return decorate84 85 86def template(cls):87 """ Class decorator to generate rules from parameterized rule templates.88 89 See `parameterized` for more information on parameterized rules.90 """91 issued_nodoc_warning = False92 for attr_name in dir(cls):93 if attr_name.startswith('p_'):94 method = getattr(cls, attr_name)95 if hasattr(method, '_params'):96 # Remove the template method97 delattr(cls, attr_name)98 # Create parameterized rules from this method; only run this if99 # the method has a docstring. This is to address an issue when100 # pycparser's users are installed in -OO mode which strips101 # docstrings away.102 # See: https://github.com/eliben/pycparser/pull/198/ and103 # https://github.com/eliben/pycparser/issues/197104 # for discussion.105 if method.__doc__ is not None:106 _create_param_rules(cls, method)107 elif not issued_nodoc_warning:108 warnings.warn(109 'parsing methods must have __doc__ for pycparser to work properly',110 RuntimeWarning,111 stacklevel=2)112 issued_nodoc_warning = True113 return cls114 115 116def _create_param_rules(cls, func):117 """ Create ply.yacc rules based on a parameterized rule function118 119 Generates new methods (one per each pair of parameters) based on the120 template rule function `func`, and attaches them to `cls`. The rule121 function's parameters must be accessible via its `_params` attribute.122 """123 for xxx, yyy in func._params:124 # Use the template method's body for each new method125 def param_rule(self, p):126 func(self, p)127 128 # Substitute in the params for the grammar rule and function name129 param_rule.__doc__ = func.__doc__.replace('xxx', xxx).replace('yyy', yyy)130 param_rule.__name__ = func.__name__.replace('xxx', xxx)131 132 # Attach the new method to the class133 setattr(cls, param_rule.__name__, param_rule)134 