|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import warnings |
|
|
|
class Coord(object): |
|
""" Coordinates of a syntactic element. Consists of: |
|
- File name |
|
- Line number |
|
- (optional) column number, for the Lexer |
|
""" |
|
__slots__ = ('file', 'line', 'column', '__weakref__') |
|
def __init__(self, file, line, column=None): |
|
self.file = file |
|
self.line = line |
|
self.column = column |
|
|
|
def __str__(self): |
|
str = "%s:%s" % (self.file, self.line) |
|
if self.column: str += ":%s" % self.column |
|
return str |
|
|
|
|
|
class ParseError(Exception): pass |
|
|
|
|
|
class PLYParser(object): |
|
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 = rulename + '_opt' |
|
|
|
def optrule(self, p): |
|
p[0] = p[1] |
|
|
|
optrule.__doc__ = '%s : empty\n| %s' % (optname, rulename) |
|
optrule.__name__ = 'p_%s' % optname |
|
setattr(self.__class__, optrule.__name__, optrule) |
|
|
|
def _coord(self, lineno, column=None): |
|
return Coord( |
|
file=self.clex.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("%s: %s" % (coord, msg)) |
|
|
|
|
|
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): |
|
rule_func._params = params |
|
return rule_func |
|
return decorate |
|
|
|
|
|
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'): |
|
|
|
delattr(cls, attr_name) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
def param_rule(self, p): |
|
func(self, p) |
|
|
|
|
|
param_rule.__doc__ = func.__doc__.replace('xxx', xxx).replace('yyy', yyy) |
|
param_rule.__name__ = func.__name__.replace('xxx', xxx) |
|
|
|
|
|
setattr(cls, param_rule.__name__, param_rule) |
|
|