Spaces:
Running
Running
text-generation-webui
/
installer_files
/conda
/lib
/python3.10
/site-packages
/pycparser
/ply
/yacc.py
# ----------------------------------------------------------------------------- | |
# ply: yacc.py | |
# | |
# Copyright (C) 2001-2017 | |
# David M. Beazley (Dabeaz LLC) | |
# All rights reserved. | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions are | |
# met: | |
# | |
# * Redistributions of source code must retain the above copyright notice, | |
# this list of conditions and the following disclaimer. | |
# * Redistributions in binary form must reproduce the above copyright notice, | |
# this list of conditions and the following disclaimer in the documentation | |
# and/or other materials provided with the distribution. | |
# * Neither the name of the David Beazley or Dabeaz LLC may be used to | |
# endorse or promote products derived from this software without | |
# specific prior written permission. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
# ----------------------------------------------------------------------------- | |
# | |
# This implements an LR parser that is constructed from grammar rules defined | |
# as Python functions. The grammer is specified by supplying the BNF inside | |
# Python documentation strings. The inspiration for this technique was borrowed | |
# from John Aycock's Spark parsing system. PLY might be viewed as cross between | |
# Spark and the GNU bison utility. | |
# | |
# The current implementation is only somewhat object-oriented. The | |
# LR parser itself is defined in terms of an object (which allows multiple | |
# parsers to co-exist). However, most of the variables used during table | |
# construction are defined in terms of global variables. Users shouldn't | |
# notice unless they are trying to define multiple parsers at the same | |
# time using threads (in which case they should have their head examined). | |
# | |
# This implementation supports both SLR and LALR(1) parsing. LALR(1) | |
# support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu), | |
# using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles, | |
# Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced | |
# by the more efficient DeRemer and Pennello algorithm. | |
# | |
# :::::::: WARNING ::::::: | |
# | |
# Construction of LR parsing tables is fairly complicated and expensive. | |
# To make this module run fast, a *LOT* of work has been put into | |
# optimization---often at the expensive of readability and what might | |
# consider to be good Python "coding style." Modify the code at your | |
# own risk! | |
# ---------------------------------------------------------------------------- | |
import re | |
import types | |
import sys | |
import os.path | |
import inspect | |
import base64 | |
import warnings | |
__version__ = '3.10' | |
__tabversion__ = '3.10' | |
#----------------------------------------------------------------------------- | |
# === User configurable parameters === | |
# | |
# Change these to modify the default behavior of yacc (if you wish) | |
#----------------------------------------------------------------------------- | |
yaccdebug = True # Debugging mode. If set, yacc generates a | |
# a 'parser.out' file in the current directory | |
debug_file = 'parser.out' # Default name of the debugging file | |
tab_module = 'parsetab' # Default name of the table module | |
default_lr = 'LALR' # Default LR table generation method | |
error_count = 3 # Number of symbols that must be shifted to leave recovery mode | |
yaccdevel = False # Set to True if developing yacc. This turns off optimized | |
# implementations of certain functions. | |
resultlimit = 40 # Size limit of results when running in debug mode. | |
pickle_protocol = 0 # Protocol to use when writing pickle files | |
# String type-checking compatibility | |
if sys.version_info[0] < 3: | |
string_types = basestring | |
else: | |
string_types = str | |
MAXINT = sys.maxsize | |
# This object is a stand-in for a logging object created by the | |
# logging module. PLY will use this by default to create things | |
# such as the parser.out file. If a user wants more detailed | |
# information, they can create their own logging object and pass | |
# it into PLY. | |
class PlyLogger(object): | |
def __init__(self, f): | |
self.f = f | |
def debug(self, msg, *args, **kwargs): | |
self.f.write((msg % args) + '\n') | |
info = debug | |
def warning(self, msg, *args, **kwargs): | |
self.f.write('WARNING: ' + (msg % args) + '\n') | |
def error(self, msg, *args, **kwargs): | |
self.f.write('ERROR: ' + (msg % args) + '\n') | |
critical = debug | |
# Null logger is used when no output is generated. Does nothing. | |
class NullLogger(object): | |
def __getattribute__(self, name): | |
return self | |
def __call__(self, *args, **kwargs): | |
return self | |
# Exception raised for yacc-related errors | |
class YaccError(Exception): | |
pass | |
# Format the result message that the parser produces when running in debug mode. | |
def format_result(r): | |
repr_str = repr(r) | |
if '\n' in repr_str: | |
repr_str = repr(repr_str) | |
if len(repr_str) > resultlimit: | |
repr_str = repr_str[:resultlimit] + ' ...' | |
result = '<%s @ 0x%x> (%s)' % (type(r).__name__, id(r), repr_str) | |
return result | |
# Format stack entries when the parser is running in debug mode | |
def format_stack_entry(r): | |
repr_str = repr(r) | |
if '\n' in repr_str: | |
repr_str = repr(repr_str) | |
if len(repr_str) < 16: | |
return repr_str | |
else: | |
return '<%s @ 0x%x>' % (type(r).__name__, id(r)) | |
# Panic mode error recovery support. This feature is being reworked--much of the | |
# code here is to offer a deprecation/backwards compatible transition | |
_errok = None | |
_token = None | |
_restart = None | |
_warnmsg = '''PLY: Don't use global functions errok(), token(), and restart() in p_error(). | |
Instead, invoke the methods on the associated parser instance: | |
def p_error(p): | |
... | |
# Use parser.errok(), parser.token(), parser.restart() | |
... | |
parser = yacc.yacc() | |
''' | |
def errok(): | |
warnings.warn(_warnmsg) | |
return _errok() | |
def restart(): | |
warnings.warn(_warnmsg) | |
return _restart() | |
def token(): | |
warnings.warn(_warnmsg) | |
return _token() | |
# Utility function to call the p_error() function with some deprecation hacks | |
def call_errorfunc(errorfunc, token, parser): | |
global _errok, _token, _restart | |
_errok = parser.errok | |
_token = parser.token | |
_restart = parser.restart | |
r = errorfunc(token) | |
try: | |
del _errok, _token, _restart | |
except NameError: | |
pass | |
return r | |
#----------------------------------------------------------------------------- | |
# === LR Parsing Engine === | |
# | |
# The following classes are used for the LR parser itself. These are not | |
# used during table construction and are independent of the actual LR | |
# table generation algorithm | |
#----------------------------------------------------------------------------- | |
# This class is used to hold non-terminal grammar symbols during parsing. | |
# It normally has the following attributes set: | |
# .type = Grammar symbol type | |
# .value = Symbol value | |
# .lineno = Starting line number | |
# .endlineno = Ending line number (optional, set automatically) | |
# .lexpos = Starting lex position | |
# .endlexpos = Ending lex position (optional, set automatically) | |
class YaccSymbol: | |
def __str__(self): | |
return self.type | |
def __repr__(self): | |
return str(self) | |
# This class is a wrapper around the objects actually passed to each | |
# grammar rule. Index lookup and assignment actually assign the | |
# .value attribute of the underlying YaccSymbol object. | |
# The lineno() method returns the line number of a given | |
# item (or 0 if not defined). The linespan() method returns | |
# a tuple of (startline,endline) representing the range of lines | |
# for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos) | |
# representing the range of positional information for a symbol. | |
class YaccProduction: | |
def __init__(self, s, stack=None): | |
self.slice = s | |
self.stack = stack | |
self.lexer = None | |
self.parser = None | |
def __getitem__(self, n): | |
if isinstance(n, slice): | |
return [s.value for s in self.slice[n]] | |
elif n >= 0: | |
return self.slice[n].value | |
else: | |
return self.stack[n].value | |
def __setitem__(self, n, v): | |
self.slice[n].value = v | |
def __getslice__(self, i, j): | |
return [s.value for s in self.slice[i:j]] | |
def __len__(self): | |
return len(self.slice) | |
def lineno(self, n): | |
return getattr(self.slice[n], 'lineno', 0) | |
def set_lineno(self, n, lineno): | |
self.slice[n].lineno = lineno | |
def linespan(self, n): | |
startline = getattr(self.slice[n], 'lineno', 0) | |
endline = getattr(self.slice[n], 'endlineno', startline) | |
return startline, endline | |
def lexpos(self, n): | |
return getattr(self.slice[n], 'lexpos', 0) | |
def lexspan(self, n): | |
startpos = getattr(self.slice[n], 'lexpos', 0) | |
endpos = getattr(self.slice[n], 'endlexpos', startpos) | |
return startpos, endpos | |
def error(self): | |
raise SyntaxError | |
# ----------------------------------------------------------------------------- | |
# == LRParser == | |
# | |
# The LR Parsing engine. | |
# ----------------------------------------------------------------------------- | |
class LRParser: | |
def __init__(self, lrtab, errorf): | |
self.productions = lrtab.lr_productions | |
self.action = lrtab.lr_action | |
self.goto = lrtab.lr_goto | |
self.errorfunc = errorf | |
self.set_defaulted_states() | |
self.errorok = True | |
def errok(self): | |
self.errorok = True | |
def restart(self): | |
del self.statestack[:] | |
del self.symstack[:] | |
sym = YaccSymbol() | |
sym.type = '$end' | |
self.symstack.append(sym) | |
self.statestack.append(0) | |
# Defaulted state support. | |
# This method identifies parser states where there is only one possible reduction action. | |
# For such states, the parser can make a choose to make a rule reduction without consuming | |
# the next look-ahead token. This delayed invocation of the tokenizer can be useful in | |
# certain kinds of advanced parsing situations where the lexer and parser interact with | |
# each other or change states (i.e., manipulation of scope, lexer states, etc.). | |
# | |
# See: https://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html#Default-Reductions | |
def set_defaulted_states(self): | |
self.defaulted_states = {} | |
for state, actions in self.action.items(): | |
rules = list(actions.values()) | |
if len(rules) == 1 and rules[0] < 0: | |
self.defaulted_states[state] = rules[0] | |
def disable_defaulted_states(self): | |
self.defaulted_states = {} | |
def parse(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): | |
if debug or yaccdevel: | |
if isinstance(debug, int): | |
debug = PlyLogger(sys.stderr) | |
return self.parsedebug(input, lexer, debug, tracking, tokenfunc) | |
elif tracking: | |
return self.parseopt(input, lexer, debug, tracking, tokenfunc) | |
else: | |
return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc) | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# parsedebug(). | |
# | |
# This is the debugging enabled version of parse(). All changes made to the | |
# parsing engine should be made here. Optimized versions of this function | |
# are automatically created by the ply/ygen.py script. This script cuts out | |
# sections enclosed in markers such as this: | |
# | |
# #--! DEBUG | |
# statements | |
# #--! DEBUG | |
# | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
def parsedebug(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): | |
#--! parsedebug-start | |
lookahead = None # Current lookahead symbol | |
lookaheadstack = [] # Stack of lookahead symbols | |
actions = self.action # Local reference to action table (to avoid lookup on self.) | |
goto = self.goto # Local reference to goto table (to avoid lookup on self.) | |
prod = self.productions # Local reference to production list (to avoid lookup on self.) | |
defaulted_states = self.defaulted_states # Local reference to defaulted states | |
pslice = YaccProduction(None) # Production object passed to grammar rules | |
errorcount = 0 # Used during error recovery | |
#--! DEBUG | |
debug.info('PLY: PARSE DEBUG START') | |
#--! DEBUG | |
# If no lexer was given, we will try to use the lex module | |
if not lexer: | |
from . import lex | |
lexer = lex.lexer | |
# Set up the lexer and parser objects on pslice | |
pslice.lexer = lexer | |
pslice.parser = self | |
# If input was supplied, pass to lexer | |
if input is not None: | |
lexer.input(input) | |
if tokenfunc is None: | |
# Tokenize function | |
get_token = lexer.token | |
else: | |
get_token = tokenfunc | |
# Set the parser() token method (sometimes used in error recovery) | |
self.token = get_token | |
# Set up the state and symbol stacks | |
statestack = [] # Stack of parsing states | |
self.statestack = statestack | |
symstack = [] # Stack of grammar symbols | |
self.symstack = symstack | |
pslice.stack = symstack # Put in the production | |
errtoken = None # Err token | |
# The start state is assumed to be (0,$end) | |
statestack.append(0) | |
sym = YaccSymbol() | |
sym.type = '$end' | |
symstack.append(sym) | |
state = 0 | |
while True: | |
# Get the next symbol on the input. If a lookahead symbol | |
# is already set, we just use that. Otherwise, we'll pull | |
# the next token off of the lookaheadstack or from the lexer | |
#--! DEBUG | |
debug.debug('') | |
debug.debug('State : %s', state) | |
#--! DEBUG | |
if state not in defaulted_states: | |
if not lookahead: | |
if not lookaheadstack: | |
lookahead = get_token() # Get the next token | |
else: | |
lookahead = lookaheadstack.pop() | |
if not lookahead: | |
lookahead = YaccSymbol() | |
lookahead.type = '$end' | |
# Check the action table | |
ltype = lookahead.type | |
t = actions[state].get(ltype) | |
else: | |
t = defaulted_states[state] | |
#--! DEBUG | |
debug.debug('Defaulted state %s: Reduce using %d', state, -t) | |
#--! DEBUG | |
#--! DEBUG | |
debug.debug('Stack : %s', | |
('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) | |
#--! DEBUG | |
if t is not None: | |
if t > 0: | |
# shift a symbol on the stack | |
statestack.append(t) | |
state = t | |
#--! DEBUG | |
debug.debug('Action : Shift and goto state %s', t) | |
#--! DEBUG | |
symstack.append(lookahead) | |
lookahead = None | |
# Decrease error count on successful shift | |
if errorcount: | |
errorcount -= 1 | |
continue | |
if t < 0: | |
# reduce a symbol on the stack, emit a production | |
p = prod[-t] | |
pname = p.name | |
plen = p.len | |
# Get production function | |
sym = YaccSymbol() | |
sym.type = pname # Production name | |
sym.value = None | |
#--! DEBUG | |
if plen: | |
debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, | |
'['+','.join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+']', | |
goto[statestack[-1-plen]][pname]) | |
else: | |
debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, [], | |
goto[statestack[-1]][pname]) | |
#--! DEBUG | |
if plen: | |
targ = symstack[-plen-1:] | |
targ[0] = sym | |
#--! TRACKING | |
if tracking: | |
t1 = targ[1] | |
sym.lineno = t1.lineno | |
sym.lexpos = t1.lexpos | |
t1 = targ[-1] | |
sym.endlineno = getattr(t1, 'endlineno', t1.lineno) | |
sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos) | |
#--! TRACKING | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# The code enclosed in this section is duplicated | |
# below as a performance optimization. Make sure | |
# changes get made in both locations. | |
pslice.slice = targ | |
try: | |
# Call the grammar rule with our special slice object | |
del symstack[-plen:] | |
self.state = state | |
p.callable(pslice) | |
del statestack[-plen:] | |
#--! DEBUG | |
debug.info('Result : %s', format_result(pslice[0])) | |
#--! DEBUG | |
symstack.append(sym) | |
state = goto[statestack[-1]][pname] | |
statestack.append(state) | |
except SyntaxError: | |
# If an error was set. Enter error recovery state | |
lookaheadstack.append(lookahead) # Save the current lookahead token | |
symstack.extend(targ[1:-1]) # Put the production slice back on the stack | |
statestack.pop() # Pop back one state (before the reduce) | |
state = statestack[-1] | |
sym.type = 'error' | |
sym.value = 'error' | |
lookahead = sym | |
errorcount = error_count | |
self.errorok = False | |
continue | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
else: | |
#--! TRACKING | |
if tracking: | |
sym.lineno = lexer.lineno | |
sym.lexpos = lexer.lexpos | |
#--! TRACKING | |
targ = [sym] | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# The code enclosed in this section is duplicated | |
# above as a performance optimization. Make sure | |
# changes get made in both locations. | |
pslice.slice = targ | |
try: | |
# Call the grammar rule with our special slice object | |
self.state = state | |
p.callable(pslice) | |
#--! DEBUG | |
debug.info('Result : %s', format_result(pslice[0])) | |
#--! DEBUG | |
symstack.append(sym) | |
state = goto[statestack[-1]][pname] | |
statestack.append(state) | |
except SyntaxError: | |
# If an error was set. Enter error recovery state | |
lookaheadstack.append(lookahead) # Save the current lookahead token | |
statestack.pop() # Pop back one state (before the reduce) | |
state = statestack[-1] | |
sym.type = 'error' | |
sym.value = 'error' | |
lookahead = sym | |
errorcount = error_count | |
self.errorok = False | |
continue | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
if t == 0: | |
n = symstack[-1] | |
result = getattr(n, 'value', None) | |
#--! DEBUG | |
debug.info('Done : Returning %s', format_result(result)) | |
debug.info('PLY: PARSE DEBUG END') | |
#--! DEBUG | |
return result | |
if t is None: | |
#--! DEBUG | |
debug.error('Error : %s', | |
('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) | |
#--! DEBUG | |
# We have some kind of parsing error here. To handle | |
# this, we are going to push the current token onto | |
# the tokenstack and replace it with an 'error' token. | |
# If there are any synchronization rules, they may | |
# catch it. | |
# | |
# In addition to pushing the error token, we call call | |
# the user defined p_error() function if this is the | |
# first syntax error. This function is only called if | |
# errorcount == 0. | |
if errorcount == 0 or self.errorok: | |
errorcount = error_count | |
self.errorok = False | |
errtoken = lookahead | |
if errtoken.type == '$end': | |
errtoken = None # End of file! | |
if self.errorfunc: | |
if errtoken and not hasattr(errtoken, 'lexer'): | |
errtoken.lexer = lexer | |
self.state = state | |
tok = call_errorfunc(self.errorfunc, errtoken, self) | |
if self.errorok: | |
# User must have done some kind of panic | |
# mode recovery on their own. The | |
# returned token is the next lookahead | |
lookahead = tok | |
errtoken = None | |
continue | |
else: | |
if errtoken: | |
if hasattr(errtoken, 'lineno'): | |
lineno = lookahead.lineno | |
else: | |
lineno = 0 | |
if lineno: | |
sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) | |
else: | |
sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) | |
else: | |
sys.stderr.write('yacc: Parse error in input. EOF\n') | |
return | |
else: | |
errorcount = error_count | |
# case 1: the statestack only has 1 entry on it. If we're in this state, the | |
# entire parse has been rolled back and we're completely hosed. The token is | |
# discarded and we just keep going. | |
if len(statestack) <= 1 and lookahead.type != '$end': | |
lookahead = None | |
errtoken = None | |
state = 0 | |
# Nuke the pushback stack | |
del lookaheadstack[:] | |
continue | |
# case 2: the statestack has a couple of entries on it, but we're | |
# at the end of the file. nuke the top entry and generate an error token | |
# Start nuking entries on the stack | |
if lookahead.type == '$end': | |
# Whoa. We're really hosed here. Bail out | |
return | |
if lookahead.type != 'error': | |
sym = symstack[-1] | |
if sym.type == 'error': | |
# Hmmm. Error is on top of stack, we'll just nuke input | |
# symbol and continue | |
#--! TRACKING | |
if tracking: | |
sym.endlineno = getattr(lookahead, 'lineno', sym.lineno) | |
sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos) | |
#--! TRACKING | |
lookahead = None | |
continue | |
# Create the error symbol for the first time and make it the new lookahead symbol | |
t = YaccSymbol() | |
t.type = 'error' | |
if hasattr(lookahead, 'lineno'): | |
t.lineno = t.endlineno = lookahead.lineno | |
if hasattr(lookahead, 'lexpos'): | |
t.lexpos = t.endlexpos = lookahead.lexpos | |
t.value = lookahead | |
lookaheadstack.append(lookahead) | |
lookahead = t | |
else: | |
sym = symstack.pop() | |
#--! TRACKING | |
if tracking: | |
lookahead.lineno = sym.lineno | |
lookahead.lexpos = sym.lexpos | |
#--! TRACKING | |
statestack.pop() | |
state = statestack[-1] | |
continue | |
# Call an error function here | |
raise RuntimeError('yacc: internal parser error!!!\n') | |
#--! parsedebug-end | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# parseopt(). | |
# | |
# Optimized version of parse() method. DO NOT EDIT THIS CODE DIRECTLY! | |
# This code is automatically generated by the ply/ygen.py script. Make | |
# changes to the parsedebug() method instead. | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
def parseopt(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): | |
#--! parseopt-start | |
lookahead = None # Current lookahead symbol | |
lookaheadstack = [] # Stack of lookahead symbols | |
actions = self.action # Local reference to action table (to avoid lookup on self.) | |
goto = self.goto # Local reference to goto table (to avoid lookup on self.) | |
prod = self.productions # Local reference to production list (to avoid lookup on self.) | |
defaulted_states = self.defaulted_states # Local reference to defaulted states | |
pslice = YaccProduction(None) # Production object passed to grammar rules | |
errorcount = 0 # Used during error recovery | |
# If no lexer was given, we will try to use the lex module | |
if not lexer: | |
from . import lex | |
lexer = lex.lexer | |
# Set up the lexer and parser objects on pslice | |
pslice.lexer = lexer | |
pslice.parser = self | |
# If input was supplied, pass to lexer | |
if input is not None: | |
lexer.input(input) | |
if tokenfunc is None: | |
# Tokenize function | |
get_token = lexer.token | |
else: | |
get_token = tokenfunc | |
# Set the parser() token method (sometimes used in error recovery) | |
self.token = get_token | |
# Set up the state and symbol stacks | |
statestack = [] # Stack of parsing states | |
self.statestack = statestack | |
symstack = [] # Stack of grammar symbols | |
self.symstack = symstack | |
pslice.stack = symstack # Put in the production | |
errtoken = None # Err token | |
# The start state is assumed to be (0,$end) | |
statestack.append(0) | |
sym = YaccSymbol() | |
sym.type = '$end' | |
symstack.append(sym) | |
state = 0 | |
while True: | |
# Get the next symbol on the input. If a lookahead symbol | |
# is already set, we just use that. Otherwise, we'll pull | |
# the next token off of the lookaheadstack or from the lexer | |
if state not in defaulted_states: | |
if not lookahead: | |
if not lookaheadstack: | |
lookahead = get_token() # Get the next token | |
else: | |
lookahead = lookaheadstack.pop() | |
if not lookahead: | |
lookahead = YaccSymbol() | |
lookahead.type = '$end' | |
# Check the action table | |
ltype = lookahead.type | |
t = actions[state].get(ltype) | |
else: | |
t = defaulted_states[state] | |
if t is not None: | |
if t > 0: | |
# shift a symbol on the stack | |
statestack.append(t) | |
state = t | |
symstack.append(lookahead) | |
lookahead = None | |
# Decrease error count on successful shift | |
if errorcount: | |
errorcount -= 1 | |
continue | |
if t < 0: | |
# reduce a symbol on the stack, emit a production | |
p = prod[-t] | |
pname = p.name | |
plen = p.len | |
# Get production function | |
sym = YaccSymbol() | |
sym.type = pname # Production name | |
sym.value = None | |
if plen: | |
targ = symstack[-plen-1:] | |
targ[0] = sym | |
#--! TRACKING | |
if tracking: | |
t1 = targ[1] | |
sym.lineno = t1.lineno | |
sym.lexpos = t1.lexpos | |
t1 = targ[-1] | |
sym.endlineno = getattr(t1, 'endlineno', t1.lineno) | |
sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos) | |
#--! TRACKING | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# The code enclosed in this section is duplicated | |
# below as a performance optimization. Make sure | |
# changes get made in both locations. | |
pslice.slice = targ | |
try: | |
# Call the grammar rule with our special slice object | |
del symstack[-plen:] | |
self.state = state | |
p.callable(pslice) | |
del statestack[-plen:] | |
symstack.append(sym) | |
state = goto[statestack[-1]][pname] | |
statestack.append(state) | |
except SyntaxError: | |
# If an error was set. Enter error recovery state | |
lookaheadstack.append(lookahead) # Save the current lookahead token | |
symstack.extend(targ[1:-1]) # Put the production slice back on the stack | |
statestack.pop() # Pop back one state (before the reduce) | |
state = statestack[-1] | |
sym.type = 'error' | |
sym.value = 'error' | |
lookahead = sym | |
errorcount = error_count | |
self.errorok = False | |
continue | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
else: | |
#--! TRACKING | |
if tracking: | |
sym.lineno = lexer.lineno | |
sym.lexpos = lexer.lexpos | |
#--! TRACKING | |
targ = [sym] | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# The code enclosed in this section is duplicated | |
# above as a performance optimization. Make sure | |
# changes get made in both locations. | |
pslice.slice = targ | |
try: | |
# Call the grammar rule with our special slice object | |
self.state = state | |
p.callable(pslice) | |
symstack.append(sym) | |
state = goto[statestack[-1]][pname] | |
statestack.append(state) | |
except SyntaxError: | |
# If an error was set. Enter error recovery state | |
lookaheadstack.append(lookahead) # Save the current lookahead token | |
statestack.pop() # Pop back one state (before the reduce) | |
state = statestack[-1] | |
sym.type = 'error' | |
sym.value = 'error' | |
lookahead = sym | |
errorcount = error_count | |
self.errorok = False | |
continue | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
if t == 0: | |
n = symstack[-1] | |
result = getattr(n, 'value', None) | |
return result | |
if t is None: | |
# We have some kind of parsing error here. To handle | |
# this, we are going to push the current token onto | |
# the tokenstack and replace it with an 'error' token. | |
# If there are any synchronization rules, they may | |
# catch it. | |
# | |
# In addition to pushing the error token, we call call | |
# the user defined p_error() function if this is the | |
# first syntax error. This function is only called if | |
# errorcount == 0. | |
if errorcount == 0 or self.errorok: | |
errorcount = error_count | |
self.errorok = False | |
errtoken = lookahead | |
if errtoken.type == '$end': | |
errtoken = None # End of file! | |
if self.errorfunc: | |
if errtoken and not hasattr(errtoken, 'lexer'): | |
errtoken.lexer = lexer | |
self.state = state | |
tok = call_errorfunc(self.errorfunc, errtoken, self) | |
if self.errorok: | |
# User must have done some kind of panic | |
# mode recovery on their own. The | |
# returned token is the next lookahead | |
lookahead = tok | |
errtoken = None | |
continue | |
else: | |
if errtoken: | |
if hasattr(errtoken, 'lineno'): | |
lineno = lookahead.lineno | |
else: | |
lineno = 0 | |
if lineno: | |
sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) | |
else: | |
sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) | |
else: | |
sys.stderr.write('yacc: Parse error in input. EOF\n') | |
return | |
else: | |
errorcount = error_count | |
# case 1: the statestack only has 1 entry on it. If we're in this state, the | |
# entire parse has been rolled back and we're completely hosed. The token is | |
# discarded and we just keep going. | |
if len(statestack) <= 1 and lookahead.type != '$end': | |
lookahead = None | |
errtoken = None | |
state = 0 | |
# Nuke the pushback stack | |
del lookaheadstack[:] | |
continue | |
# case 2: the statestack has a couple of entries on it, but we're | |
# at the end of the file. nuke the top entry and generate an error token | |
# Start nuking entries on the stack | |
if lookahead.type == '$end': | |
# Whoa. We're really hosed here. Bail out | |
return | |
if lookahead.type != 'error': | |
sym = symstack[-1] | |
if sym.type == 'error': | |
# Hmmm. Error is on top of stack, we'll just nuke input | |
# symbol and continue | |
#--! TRACKING | |
if tracking: | |
sym.endlineno = getattr(lookahead, 'lineno', sym.lineno) | |
sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos) | |
#--! TRACKING | |
lookahead = None | |
continue | |
# Create the error symbol for the first time and make it the new lookahead symbol | |
t = YaccSymbol() | |
t.type = 'error' | |
if hasattr(lookahead, 'lineno'): | |
t.lineno = t.endlineno = lookahead.lineno | |
if hasattr(lookahead, 'lexpos'): | |
t.lexpos = t.endlexpos = lookahead.lexpos | |
t.value = lookahead | |
lookaheadstack.append(lookahead) | |
lookahead = t | |
else: | |
sym = symstack.pop() | |
#--! TRACKING | |
if tracking: | |
lookahead.lineno = sym.lineno | |
lookahead.lexpos = sym.lexpos | |
#--! TRACKING | |
statestack.pop() | |
state = statestack[-1] | |
continue | |
# Call an error function here | |
raise RuntimeError('yacc: internal parser error!!!\n') | |
#--! parseopt-end | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# parseopt_notrack(). | |
# | |
# Optimized version of parseopt() with line number tracking removed. | |
# DO NOT EDIT THIS CODE DIRECTLY. This code is automatically generated | |
# by the ply/ygen.py script. Make changes to the parsedebug() method instead. | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
def parseopt_notrack(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): | |
#--! parseopt-notrack-start | |
lookahead = None # Current lookahead symbol | |
lookaheadstack = [] # Stack of lookahead symbols | |
actions = self.action # Local reference to action table (to avoid lookup on self.) | |
goto = self.goto # Local reference to goto table (to avoid lookup on self.) | |
prod = self.productions # Local reference to production list (to avoid lookup on self.) | |
defaulted_states = self.defaulted_states # Local reference to defaulted states | |
pslice = YaccProduction(None) # Production object passed to grammar rules | |
errorcount = 0 # Used during error recovery | |
# If no lexer was given, we will try to use the lex module | |
if not lexer: | |
from . import lex | |
lexer = lex.lexer | |
# Set up the lexer and parser objects on pslice | |
pslice.lexer = lexer | |
pslice.parser = self | |
# If input was supplied, pass to lexer | |
if input is not None: | |
lexer.input(input) | |
if tokenfunc is None: | |
# Tokenize function | |
get_token = lexer.token | |
else: | |
get_token = tokenfunc | |
# Set the parser() token method (sometimes used in error recovery) | |
self.token = get_token | |
# Set up the state and symbol stacks | |
statestack = [] # Stack of parsing states | |
self.statestack = statestack | |
symstack = [] # Stack of grammar symbols | |
self.symstack = symstack | |
pslice.stack = symstack # Put in the production | |
errtoken = None # Err token | |
# The start state is assumed to be (0,$end) | |
statestack.append(0) | |
sym = YaccSymbol() | |
sym.type = '$end' | |
symstack.append(sym) | |
state = 0 | |
while True: | |
# Get the next symbol on the input. If a lookahead symbol | |
# is already set, we just use that. Otherwise, we'll pull | |
# the next token off of the lookaheadstack or from the lexer | |
if state not in defaulted_states: | |
if not lookahead: | |
if not lookaheadstack: | |
lookahead = get_token() # Get the next token | |
else: | |
lookahead = lookaheadstack.pop() | |
if not lookahead: | |
lookahead = YaccSymbol() | |
lookahead.type = '$end' | |
# Check the action table | |
ltype = lookahead.type | |
t = actions[state].get(ltype) | |
else: | |
t = defaulted_states[state] | |
if t is not None: | |
if t > 0: | |
# shift a symbol on the stack | |
statestack.append(t) | |
state = t | |
symstack.append(lookahead) | |
lookahead = None | |
# Decrease error count on successful shift | |
if errorcount: | |
errorcount -= 1 | |
continue | |
if t < 0: | |
# reduce a symbol on the stack, emit a production | |
p = prod[-t] | |
pname = p.name | |
plen = p.len | |
# Get production function | |
sym = YaccSymbol() | |
sym.type = pname # Production name | |
sym.value = None | |
if plen: | |
targ = symstack[-plen-1:] | |
targ[0] = sym | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# The code enclosed in this section is duplicated | |
# below as a performance optimization. Make sure | |
# changes get made in both locations. | |
pslice.slice = targ | |
try: | |
# Call the grammar rule with our special slice object | |
del symstack[-plen:] | |
self.state = state | |
p.callable(pslice) | |
del statestack[-plen:] | |
symstack.append(sym) | |
state = goto[statestack[-1]][pname] | |
statestack.append(state) | |
except SyntaxError: | |
# If an error was set. Enter error recovery state | |
lookaheadstack.append(lookahead) # Save the current lookahead token | |
symstack.extend(targ[1:-1]) # Put the production slice back on the stack | |
statestack.pop() # Pop back one state (before the reduce) | |
state = statestack[-1] | |
sym.type = 'error' | |
sym.value = 'error' | |
lookahead = sym | |
errorcount = error_count | |
self.errorok = False | |
continue | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
else: | |
targ = [sym] | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# The code enclosed in this section is duplicated | |
# above as a performance optimization. Make sure | |
# changes get made in both locations. | |
pslice.slice = targ | |
try: | |
# Call the grammar rule with our special slice object | |
self.state = state | |
p.callable(pslice) | |
symstack.append(sym) | |
state = goto[statestack[-1]][pname] | |
statestack.append(state) | |
except SyntaxError: | |
# If an error was set. Enter error recovery state | |
lookaheadstack.append(lookahead) # Save the current lookahead token | |
statestack.pop() # Pop back one state (before the reduce) | |
state = statestack[-1] | |
sym.type = 'error' | |
sym.value = 'error' | |
lookahead = sym | |
errorcount = error_count | |
self.errorok = False | |
continue | |
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
if t == 0: | |
n = symstack[-1] | |
result = getattr(n, 'value', None) | |
return result | |
if t is None: | |
# We have some kind of parsing error here. To handle | |
# this, we are going to push the current token onto | |
# the tokenstack and replace it with an 'error' token. | |
# If there are any synchronization rules, they may | |
# catch it. | |
# | |
# In addition to pushing the error token, we call call | |
# the user defined p_error() function if this is the | |
# first syntax error. This function is only called if | |
# errorcount == 0. | |
if errorcount == 0 or self.errorok: | |
errorcount = error_count | |
self.errorok = False | |
errtoken = lookahead | |
if errtoken.type == '$end': | |
errtoken = None # End of file! | |
if self.errorfunc: | |
if errtoken and not hasattr(errtoken, 'lexer'): | |
errtoken.lexer = lexer | |
self.state = state | |
tok = call_errorfunc(self.errorfunc, errtoken, self) | |
if self.errorok: | |
# User must have done some kind of panic | |
# mode recovery on their own. The | |
# returned token is the next lookahead | |
lookahead = tok | |
errtoken = None | |
continue | |
else: | |
if errtoken: | |
if hasattr(errtoken, 'lineno'): | |
lineno = lookahead.lineno | |
else: | |
lineno = 0 | |
if lineno: | |
sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) | |
else: | |
sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) | |
else: | |
sys.stderr.write('yacc: Parse error in input. EOF\n') | |
return | |
else: | |
errorcount = error_count | |
# case 1: the statestack only has 1 entry on it. If we're in this state, the | |
# entire parse has been rolled back and we're completely hosed. The token is | |
# discarded and we just keep going. | |
if len(statestack) <= 1 and lookahead.type != '$end': | |
lookahead = None | |
errtoken = None | |
state = 0 | |
# Nuke the pushback stack | |
del lookaheadstack[:] | |
continue | |
# case 2: the statestack has a couple of entries on it, but we're | |
# at the end of the file. nuke the top entry and generate an error token | |
# Start nuking entries on the stack | |
if lookahead.type == '$end': | |
# Whoa. We're really hosed here. Bail out | |
return | |
if lookahead.type != 'error': | |
sym = symstack[-1] | |
if sym.type == 'error': | |
# Hmmm. Error is on top of stack, we'll just nuke input | |
# symbol and continue | |
lookahead = None | |
continue | |
# Create the error symbol for the first time and make it the new lookahead symbol | |
t = YaccSymbol() | |
t.type = 'error' | |
if hasattr(lookahead, 'lineno'): | |
t.lineno = t.endlineno = lookahead.lineno | |
if hasattr(lookahead, 'lexpos'): | |
t.lexpos = t.endlexpos = lookahead.lexpos | |
t.value = lookahead | |
lookaheadstack.append(lookahead) | |
lookahead = t | |
else: | |
sym = symstack.pop() | |
statestack.pop() | |
state = statestack[-1] | |
continue | |
# Call an error function here | |
raise RuntimeError('yacc: internal parser error!!!\n') | |
#--! parseopt-notrack-end | |
# ----------------------------------------------------------------------------- | |
# === Grammar Representation === | |
# | |
# The following functions, classes, and variables are used to represent and | |
# manipulate the rules that make up a grammar. | |
# ----------------------------------------------------------------------------- | |
# regex matching identifiers | |
_is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') | |
# ----------------------------------------------------------------------------- | |
# class Production: | |
# | |
# This class stores the raw information about a single production or grammar rule. | |
# A grammar rule refers to a specification such as this: | |
# | |
# expr : expr PLUS term | |
# | |
# Here are the basic attributes defined on all productions | |
# | |
# name - Name of the production. For example 'expr' | |
# prod - A list of symbols on the right side ['expr','PLUS','term'] | |
# prec - Production precedence level | |
# number - Production number. | |
# func - Function that executes on reduce | |
# file - File where production function is defined | |
# lineno - Line number where production function is defined | |
# | |
# The following attributes are defined or optional. | |
# | |
# len - Length of the production (number of symbols on right hand side) | |
# usyms - Set of unique symbols found in the production | |
# ----------------------------------------------------------------------------- | |
class Production(object): | |
reduced = 0 | |
def __init__(self, number, name, prod, precedence=('right', 0), func=None, file='', line=0): | |
self.name = name | |
self.prod = tuple(prod) | |
self.number = number | |
self.func = func | |
self.callable = None | |
self.file = file | |
self.line = line | |
self.prec = precedence | |
# Internal settings used during table construction | |
self.len = len(self.prod) # Length of the production | |
# Create a list of unique production symbols used in the production | |
self.usyms = [] | |
for s in self.prod: | |
if s not in self.usyms: | |
self.usyms.append(s) | |
# List of all LR items for the production | |
self.lr_items = [] | |
self.lr_next = None | |
# Create a string representation | |
if self.prod: | |
self.str = '%s -> %s' % (self.name, ' '.join(self.prod)) | |
else: | |
self.str = '%s -> <empty>' % self.name | |
def __str__(self): | |
return self.str | |
def __repr__(self): | |
return 'Production(' + str(self) + ')' | |
def __len__(self): | |
return len(self.prod) | |
def __nonzero__(self): | |
return 1 | |
def __getitem__(self, index): | |
return self.prod[index] | |
# Return the nth lr_item from the production (or None if at the end) | |
def lr_item(self, n): | |
if n > len(self.prod): | |
return None | |
p = LRItem(self, n) | |
# Precompute the list of productions immediately following. | |
try: | |
p.lr_after = Prodnames[p.prod[n+1]] | |
except (IndexError, KeyError): | |
p.lr_after = [] | |
try: | |
p.lr_before = p.prod[n-1] | |
except IndexError: | |
p.lr_before = None | |
return p | |
# Bind the production function name to a callable | |
def bind(self, pdict): | |
if self.func: | |
self.callable = pdict[self.func] | |
# This class serves as a minimal standin for Production objects when | |
# reading table data from files. It only contains information | |
# actually used by the LR parsing engine, plus some additional | |
# debugging information. | |
class MiniProduction(object): | |
def __init__(self, str, name, len, func, file, line): | |
self.name = name | |
self.len = len | |
self.func = func | |
self.callable = None | |
self.file = file | |
self.line = line | |
self.str = str | |
def __str__(self): | |
return self.str | |
def __repr__(self): | |
return 'MiniProduction(%s)' % self.str | |
# Bind the production function name to a callable | |
def bind(self, pdict): | |
if self.func: | |
self.callable = pdict[self.func] | |
# ----------------------------------------------------------------------------- | |
# class LRItem | |
# | |
# This class represents a specific stage of parsing a production rule. For | |
# example: | |
# | |
# expr : expr . PLUS term | |
# | |
# In the above, the "." represents the current location of the parse. Here | |
# basic attributes: | |
# | |
# name - Name of the production. For example 'expr' | |
# prod - A list of symbols on the right side ['expr','.', 'PLUS','term'] | |
# number - Production number. | |
# | |
# lr_next Next LR item. Example, if we are ' expr -> expr . PLUS term' | |
# then lr_next refers to 'expr -> expr PLUS . term' | |
# lr_index - LR item index (location of the ".") in the prod list. | |
# lookaheads - LALR lookahead symbols for this item | |
# len - Length of the production (number of symbols on right hand side) | |
# lr_after - List of all productions that immediately follow | |
# lr_before - Grammar symbol immediately before | |
# ----------------------------------------------------------------------------- | |
class LRItem(object): | |
def __init__(self, p, n): | |
self.name = p.name | |
self.prod = list(p.prod) | |
self.number = p.number | |
self.lr_index = n | |
self.lookaheads = {} | |
self.prod.insert(n, '.') | |
self.prod = tuple(self.prod) | |
self.len = len(self.prod) | |
self.usyms = p.usyms | |
def __str__(self): | |
if self.prod: | |
s = '%s -> %s' % (self.name, ' '.join(self.prod)) | |
else: | |
s = '%s -> <empty>' % self.name | |
return s | |
def __repr__(self): | |
return 'LRItem(' + str(self) + ')' | |
# ----------------------------------------------------------------------------- | |
# rightmost_terminal() | |
# | |
# Return the rightmost terminal from a list of symbols. Used in add_production() | |
# ----------------------------------------------------------------------------- | |
def rightmost_terminal(symbols, terminals): | |
i = len(symbols) - 1 | |
while i >= 0: | |
if symbols[i] in terminals: | |
return symbols[i] | |
i -= 1 | |
return None | |
# ----------------------------------------------------------------------------- | |
# === GRAMMAR CLASS === | |
# | |
# The following class represents the contents of the specified grammar along | |
# with various computed properties such as first sets, follow sets, LR items, etc. | |
# This data is used for critical parts of the table generation process later. | |
# ----------------------------------------------------------------------------- | |
class GrammarError(YaccError): | |
pass | |
class Grammar(object): | |
def __init__(self, terminals): | |
self.Productions = [None] # A list of all of the productions. The first | |
# entry is always reserved for the purpose of | |
# building an augmented grammar | |
self.Prodnames = {} # A dictionary mapping the names of nonterminals to a list of all | |
# productions of that nonterminal. | |
self.Prodmap = {} # A dictionary that is only used to detect duplicate | |
# productions. | |
self.Terminals = {} # A dictionary mapping the names of terminal symbols to a | |
# list of the rules where they are used. | |
for term in terminals: | |
self.Terminals[term] = [] | |
self.Terminals['error'] = [] | |
self.Nonterminals = {} # A dictionary mapping names of nonterminals to a list | |
# of rule numbers where they are used. | |
self.First = {} # A dictionary of precomputed FIRST(x) symbols | |
self.Follow = {} # A dictionary of precomputed FOLLOW(x) symbols | |
self.Precedence = {} # Precedence rules for each terminal. Contains tuples of the | |
# form ('right',level) or ('nonassoc', level) or ('left',level) | |
self.UsedPrecedence = set() # Precedence rules that were actually used by the grammer. | |
# This is only used to provide error checking and to generate | |
# a warning about unused precedence rules. | |
self.Start = None # Starting symbol for the grammar | |
def __len__(self): | |
return len(self.Productions) | |
def __getitem__(self, index): | |
return self.Productions[index] | |
# ----------------------------------------------------------------------------- | |
# set_precedence() | |
# | |
# Sets the precedence for a given terminal. assoc is the associativity such as | |
# 'left','right', or 'nonassoc'. level is a numeric level. | |
# | |
# ----------------------------------------------------------------------------- | |
def set_precedence(self, term, assoc, level): | |
assert self.Productions == [None], 'Must call set_precedence() before add_production()' | |
if term in self.Precedence: | |
raise GrammarError('Precedence already specified for terminal %r' % term) | |
if assoc not in ['left', 'right', 'nonassoc']: | |
raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'") | |
self.Precedence[term] = (assoc, level) | |
# ----------------------------------------------------------------------------- | |
# add_production() | |
# | |
# Given an action function, this function assembles a production rule and | |
# computes its precedence level. | |
# | |
# The production rule is supplied as a list of symbols. For example, | |
# a rule such as 'expr : expr PLUS term' has a production name of 'expr' and | |
# symbols ['expr','PLUS','term']. | |
# | |
# Precedence is determined by the precedence of the right-most non-terminal | |
# or the precedence of a terminal specified by %prec. | |
# | |
# A variety of error checks are performed to make sure production symbols | |
# are valid and that %prec is used correctly. | |
# ----------------------------------------------------------------------------- | |
def add_production(self, prodname, syms, func=None, file='', line=0): | |
if prodname in self.Terminals: | |
raise GrammarError('%s:%d: Illegal rule name %r. Already defined as a token' % (file, line, prodname)) | |
if prodname == 'error': | |
raise GrammarError('%s:%d: Illegal rule name %r. error is a reserved word' % (file, line, prodname)) | |
if not _is_identifier.match(prodname): | |
raise GrammarError('%s:%d: Illegal rule name %r' % (file, line, prodname)) | |
# Look for literal tokens | |
for n, s in enumerate(syms): | |
if s[0] in "'\"": | |
try: | |
c = eval(s) | |
if (len(c) > 1): | |
raise GrammarError('%s:%d: Literal token %s in rule %r may only be a single character' % | |
(file, line, s, prodname)) | |
if c not in self.Terminals: | |
self.Terminals[c] = [] | |
syms[n] = c | |
continue | |
except SyntaxError: | |
pass | |
if not _is_identifier.match(s) and s != '%prec': | |
raise GrammarError('%s:%d: Illegal name %r in rule %r' % (file, line, s, prodname)) | |
# Determine the precedence level | |
if '%prec' in syms: | |
if syms[-1] == '%prec': | |
raise GrammarError('%s:%d: Syntax error. Nothing follows %%prec' % (file, line)) | |
if syms[-2] != '%prec': | |
raise GrammarError('%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule' % | |
(file, line)) | |
precname = syms[-1] | |
prodprec = self.Precedence.get(precname) | |
if not prodprec: | |
raise GrammarError('%s:%d: Nothing known about the precedence of %r' % (file, line, precname)) | |
else: | |
self.UsedPrecedence.add(precname) | |
del syms[-2:] # Drop %prec from the rule | |
else: | |
# If no %prec, precedence is determined by the rightmost terminal symbol | |
precname = rightmost_terminal(syms, self.Terminals) | |
prodprec = self.Precedence.get(precname, ('right', 0)) | |
# See if the rule is already in the rulemap | |
map = '%s -> %s' % (prodname, syms) | |
if map in self.Prodmap: | |
m = self.Prodmap[map] | |
raise GrammarError('%s:%d: Duplicate rule %s. ' % (file, line, m) + | |
'Previous definition at %s:%d' % (m.file, m.line)) | |
# From this point on, everything is valid. Create a new Production instance | |
pnumber = len(self.Productions) | |
if prodname not in self.Nonterminals: | |
self.Nonterminals[prodname] = [] | |
# Add the production number to Terminals and Nonterminals | |
for t in syms: | |
if t in self.Terminals: | |
self.Terminals[t].append(pnumber) | |
else: | |
if t not in self.Nonterminals: | |
self.Nonterminals[t] = [] | |
self.Nonterminals[t].append(pnumber) | |
# Create a production and add it to the list of productions | |
p = Production(pnumber, prodname, syms, prodprec, func, file, line) | |
self.Productions.append(p) | |
self.Prodmap[map] = p | |
# Add to the global productions list | |
try: | |
self.Prodnames[prodname].append(p) | |
except KeyError: | |
self.Prodnames[prodname] = [p] | |
# ----------------------------------------------------------------------------- | |
# set_start() | |
# | |
# Sets the starting symbol and creates the augmented grammar. Production | |
# rule 0 is S' -> start where start is the start symbol. | |
# ----------------------------------------------------------------------------- | |
def set_start(self, start=None): | |
if not start: | |
start = self.Productions[1].name | |
if start not in self.Nonterminals: | |
raise GrammarError('start symbol %s undefined' % start) | |
self.Productions[0] = Production(0, "S'", [start]) | |
self.Nonterminals[start].append(0) | |
self.Start = start | |
# ----------------------------------------------------------------------------- | |
# find_unreachable() | |
# | |
# Find all of the nonterminal symbols that can't be reached from the starting | |
# symbol. Returns a list of nonterminals that can't be reached. | |
# ----------------------------------------------------------------------------- | |
def find_unreachable(self): | |
# Mark all symbols that are reachable from a symbol s | |
def mark_reachable_from(s): | |
if s in reachable: | |
return | |
reachable.add(s) | |
for p in self.Prodnames.get(s, []): | |
for r in p.prod: | |
mark_reachable_from(r) | |
reachable = set() | |
mark_reachable_from(self.Productions[0].prod[0]) | |
return [s for s in self.Nonterminals if s not in reachable] | |
# ----------------------------------------------------------------------------- | |
# infinite_cycles() | |
# | |
# This function looks at the various parsing rules and tries to detect | |
# infinite recursion cycles (grammar rules where there is no possible way | |
# to derive a string of only terminals). | |
# ----------------------------------------------------------------------------- | |
def infinite_cycles(self): | |
terminates = {} | |
# Terminals: | |
for t in self.Terminals: | |
terminates[t] = True | |
terminates['$end'] = True | |
# Nonterminals: | |
# Initialize to false: | |
for n in self.Nonterminals: | |
terminates[n] = False | |
# Then propagate termination until no change: | |
while True: | |
some_change = False | |
for (n, pl) in self.Prodnames.items(): | |
# Nonterminal n terminates iff any of its productions terminates. | |
for p in pl: | |
# Production p terminates iff all of its rhs symbols terminate. | |
for s in p.prod: | |
if not terminates[s]: | |
# The symbol s does not terminate, | |
# so production p does not terminate. | |
p_terminates = False | |
break | |
else: | |
# didn't break from the loop, | |
# so every symbol s terminates | |
# so production p terminates. | |
p_terminates = True | |
if p_terminates: | |
# symbol n terminates! | |
if not terminates[n]: | |
terminates[n] = True | |
some_change = True | |
# Don't need to consider any more productions for this n. | |
break | |
if not some_change: | |
break | |
infinite = [] | |
for (s, term) in terminates.items(): | |
if not term: | |
if s not in self.Prodnames and s not in self.Terminals and s != 'error': | |
# s is used-but-not-defined, and we've already warned of that, | |
# so it would be overkill to say that it's also non-terminating. | |
pass | |
else: | |
infinite.append(s) | |
return infinite | |
# ----------------------------------------------------------------------------- | |
# undefined_symbols() | |
# | |
# Find all symbols that were used the grammar, but not defined as tokens or | |
# grammar rules. Returns a list of tuples (sym, prod) where sym in the symbol | |
# and prod is the production where the symbol was used. | |
# ----------------------------------------------------------------------------- | |
def undefined_symbols(self): | |
result = [] | |
for p in self.Productions: | |
if not p: | |
continue | |
for s in p.prod: | |
if s not in self.Prodnames and s not in self.Terminals and s != 'error': | |
result.append((s, p)) | |
return result | |
# ----------------------------------------------------------------------------- | |
# unused_terminals() | |
# | |
# Find all terminals that were defined, but not used by the grammar. Returns | |
# a list of all symbols. | |
# ----------------------------------------------------------------------------- | |
def unused_terminals(self): | |
unused_tok = [] | |
for s, v in self.Terminals.items(): | |
if s != 'error' and not v: | |
unused_tok.append(s) | |
return unused_tok | |
# ------------------------------------------------------------------------------ | |
# unused_rules() | |
# | |
# Find all grammar rules that were defined, but not used (maybe not reachable) | |
# Returns a list of productions. | |
# ------------------------------------------------------------------------------ | |
def unused_rules(self): | |
unused_prod = [] | |
for s, v in self.Nonterminals.items(): | |
if not v: | |
p = self.Prodnames[s][0] | |
unused_prod.append(p) | |
return unused_prod | |
# ----------------------------------------------------------------------------- | |
# unused_precedence() | |
# | |
# Returns a list of tuples (term,precedence) corresponding to precedence | |
# rules that were never used by the grammar. term is the name of the terminal | |
# on which precedence was applied and precedence is a string such as 'left' or | |
# 'right' corresponding to the type of precedence. | |
# ----------------------------------------------------------------------------- | |
def unused_precedence(self): | |
unused = [] | |
for termname in self.Precedence: | |
if not (termname in self.Terminals or termname in self.UsedPrecedence): | |
unused.append((termname, self.Precedence[termname][0])) | |
return unused | |
# ------------------------------------------------------------------------- | |
# _first() | |
# | |
# Compute the value of FIRST1(beta) where beta is a tuple of symbols. | |
# | |
# During execution of compute_first1, the result may be incomplete. | |
# Afterward (e.g., when called from compute_follow()), it will be complete. | |
# ------------------------------------------------------------------------- | |
def _first(self, beta): | |
# We are computing First(x1,x2,x3,...,xn) | |
result = [] | |
for x in beta: | |
x_produces_empty = False | |
# Add all the non-<empty> symbols of First[x] to the result. | |
for f in self.First[x]: | |
if f == '<empty>': | |
x_produces_empty = True | |
else: | |
if f not in result: | |
result.append(f) | |
if x_produces_empty: | |
# We have to consider the next x in beta, | |
# i.e. stay in the loop. | |
pass | |
else: | |
# We don't have to consider any further symbols in beta. | |
break | |
else: | |
# There was no 'break' from the loop, | |
# so x_produces_empty was true for all x in beta, | |
# so beta produces empty as well. | |
result.append('<empty>') | |
return result | |
# ------------------------------------------------------------------------- | |
# compute_first() | |
# | |
# Compute the value of FIRST1(X) for all symbols | |
# ------------------------------------------------------------------------- | |
def compute_first(self): | |
if self.First: | |
return self.First | |
# Terminals: | |
for t in self.Terminals: | |
self.First[t] = [t] | |
self.First['$end'] = ['$end'] | |
# Nonterminals: | |
# Initialize to the empty set: | |
for n in self.Nonterminals: | |
self.First[n] = [] | |
# Then propagate symbols until no change: | |
while True: | |
some_change = False | |
for n in self.Nonterminals: | |
for p in self.Prodnames[n]: | |
for f in self._first(p.prod): | |
if f not in self.First[n]: | |
self.First[n].append(f) | |
some_change = True | |
if not some_change: | |
break | |
return self.First | |
# --------------------------------------------------------------------- | |
# compute_follow() | |
# | |
# Computes all of the follow sets for every non-terminal symbol. The | |
# follow set is the set of all symbols that might follow a given | |
# non-terminal. See the Dragon book, 2nd Ed. p. 189. | |
# --------------------------------------------------------------------- | |
def compute_follow(self, start=None): | |
# If already computed, return the result | |
if self.Follow: | |
return self.Follow | |
# If first sets not computed yet, do that first. | |
if not self.First: | |
self.compute_first() | |
# Add '$end' to the follow list of the start symbol | |
for k in self.Nonterminals: | |
self.Follow[k] = [] | |
if not start: | |
start = self.Productions[1].name | |
self.Follow[start] = ['$end'] | |
while True: | |
didadd = False | |
for p in self.Productions[1:]: | |
# Here is the production set | |
for i, B in enumerate(p.prod): | |
if B in self.Nonterminals: | |
# Okay. We got a non-terminal in a production | |
fst = self._first(p.prod[i+1:]) | |
hasempty = False | |
for f in fst: | |
if f != '<empty>' and f not in self.Follow[B]: | |
self.Follow[B].append(f) | |
didadd = True | |
if f == '<empty>': | |
hasempty = True | |
if hasempty or i == (len(p.prod)-1): | |
# Add elements of follow(a) to follow(b) | |
for f in self.Follow[p.name]: | |
if f not in self.Follow[B]: | |
self.Follow[B].append(f) | |
didadd = True | |
if not didadd: | |
break | |
return self.Follow | |
# ----------------------------------------------------------------------------- | |
# build_lritems() | |
# | |
# This function walks the list of productions and builds a complete set of the | |
# LR items. The LR items are stored in two ways: First, they are uniquely | |
# numbered and placed in the list _lritems. Second, a linked list of LR items | |
# is built for each production. For example: | |
# | |
# E -> E PLUS E | |
# | |
# Creates the list | |
# | |
# [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ] | |
# ----------------------------------------------------------------------------- | |
def build_lritems(self): | |
for p in self.Productions: | |
lastlri = p | |
i = 0 | |
lr_items = [] | |
while True: | |
if i > len(p): | |
lri = None | |
else: | |
lri = LRItem(p, i) | |
# Precompute the list of productions immediately following | |
try: | |
lri.lr_after = self.Prodnames[lri.prod[i+1]] | |
except (IndexError, KeyError): | |
lri.lr_after = [] | |
try: | |
lri.lr_before = lri.prod[i-1] | |
except IndexError: | |
lri.lr_before = None | |
lastlri.lr_next = lri | |
if not lri: | |
break | |
lr_items.append(lri) | |
lastlri = lri | |
i += 1 | |
p.lr_items = lr_items | |
# ----------------------------------------------------------------------------- | |
# == Class LRTable == | |
# | |
# This basic class represents a basic table of LR parsing information. | |
# Methods for generating the tables are not defined here. They are defined | |
# in the derived class LRGeneratedTable. | |
# ----------------------------------------------------------------------------- | |
class VersionError(YaccError): | |
pass | |
class LRTable(object): | |
def __init__(self): | |
self.lr_action = None | |
self.lr_goto = None | |
self.lr_productions = None | |
self.lr_method = None | |
def read_table(self, module): | |
if isinstance(module, types.ModuleType): | |
parsetab = module | |
else: | |
exec('import %s' % module) | |
parsetab = sys.modules[module] | |
if parsetab._tabversion != __tabversion__: | |
raise VersionError('yacc table file version is out of date') | |
self.lr_action = parsetab._lr_action | |
self.lr_goto = parsetab._lr_goto | |
self.lr_productions = [] | |
for p in parsetab._lr_productions: | |
self.lr_productions.append(MiniProduction(*p)) | |
self.lr_method = parsetab._lr_method | |
return parsetab._lr_signature | |
def read_pickle(self, filename): | |
try: | |
import cPickle as pickle | |
except ImportError: | |
import pickle | |
if not os.path.exists(filename): | |
raise ImportError | |
in_f = open(filename, 'rb') | |
tabversion = pickle.load(in_f) | |
if tabversion != __tabversion__: | |
raise VersionError('yacc table file version is out of date') | |
self.lr_method = pickle.load(in_f) | |
signature = pickle.load(in_f) | |
self.lr_action = pickle.load(in_f) | |
self.lr_goto = pickle.load(in_f) | |
productions = pickle.load(in_f) | |
self.lr_productions = [] | |
for p in productions: | |
self.lr_productions.append(MiniProduction(*p)) | |
in_f.close() | |
return signature | |
# Bind all production function names to callable objects in pdict | |
def bind_callables(self, pdict): | |
for p in self.lr_productions: | |
p.bind(pdict) | |
# ----------------------------------------------------------------------------- | |
# === LR Generator === | |
# | |
# The following classes and functions are used to generate LR parsing tables on | |
# a grammar. | |
# ----------------------------------------------------------------------------- | |
# ----------------------------------------------------------------------------- | |
# digraph() | |
# traverse() | |
# | |
# The following two functions are used to compute set valued functions | |
# of the form: | |
# | |
# F(x) = F'(x) U U{F(y) | x R y} | |
# | |
# This is used to compute the values of Read() sets as well as FOLLOW sets | |
# in LALR(1) generation. | |
# | |
# Inputs: X - An input set | |
# R - A relation | |
# FP - Set-valued function | |
# ------------------------------------------------------------------------------ | |
def digraph(X, R, FP): | |
N = {} | |
for x in X: | |
N[x] = 0 | |
stack = [] | |
F = {} | |
for x in X: | |
if N[x] == 0: | |
traverse(x, N, stack, F, X, R, FP) | |
return F | |
def traverse(x, N, stack, F, X, R, FP): | |
stack.append(x) | |
d = len(stack) | |
N[x] = d | |
F[x] = FP(x) # F(X) <- F'(x) | |
rel = R(x) # Get y's related to x | |
for y in rel: | |
if N[y] == 0: | |
traverse(y, N, stack, F, X, R, FP) | |
N[x] = min(N[x], N[y]) | |
for a in F.get(y, []): | |
if a not in F[x]: | |
F[x].append(a) | |
if N[x] == d: | |
N[stack[-1]] = MAXINT | |
F[stack[-1]] = F[x] | |
element = stack.pop() | |
while element != x: | |
N[stack[-1]] = MAXINT | |
F[stack[-1]] = F[x] | |
element = stack.pop() | |
class LALRError(YaccError): | |
pass | |
# ----------------------------------------------------------------------------- | |
# == LRGeneratedTable == | |
# | |
# This class implements the LR table generation algorithm. There are no | |
# public methods except for write() | |
# ----------------------------------------------------------------------------- | |
class LRGeneratedTable(LRTable): | |
def __init__(self, grammar, method='LALR', log=None): | |
if method not in ['SLR', 'LALR']: | |
raise LALRError('Unsupported method %s' % method) | |
self.grammar = grammar | |
self.lr_method = method | |
# Set up the logger | |
if not log: | |
log = NullLogger() | |
self.log = log | |
# Internal attributes | |
self.lr_action = {} # Action table | |
self.lr_goto = {} # Goto table | |
self.lr_productions = grammar.Productions # Copy of grammar Production array | |
self.lr_goto_cache = {} # Cache of computed gotos | |
self.lr0_cidhash = {} # Cache of closures | |
self._add_count = 0 # Internal counter used to detect cycles | |
# Diagonistic information filled in by the table generator | |
self.sr_conflict = 0 | |
self.rr_conflict = 0 | |
self.conflicts = [] # List of conflicts | |
self.sr_conflicts = [] | |
self.rr_conflicts = [] | |
# Build the tables | |
self.grammar.build_lritems() | |
self.grammar.compute_first() | |
self.grammar.compute_follow() | |
self.lr_parse_table() | |
# Compute the LR(0) closure operation on I, where I is a set of LR(0) items. | |
def lr0_closure(self, I): | |
self._add_count += 1 | |
# Add everything in I to J | |
J = I[:] | |
didadd = True | |
while didadd: | |
didadd = False | |
for j in J: | |
for x in j.lr_after: | |
if getattr(x, 'lr0_added', 0) == self._add_count: | |
continue | |
# Add B --> .G to J | |
J.append(x.lr_next) | |
x.lr0_added = self._add_count | |
didadd = True | |
return J | |
# Compute the LR(0) goto function goto(I,X) where I is a set | |
# of LR(0) items and X is a grammar symbol. This function is written | |
# in a way that guarantees uniqueness of the generated goto sets | |
# (i.e. the same goto set will never be returned as two different Python | |
# objects). With uniqueness, we can later do fast set comparisons using | |
# id(obj) instead of element-wise comparison. | |
def lr0_goto(self, I, x): | |
# First we look for a previously cached entry | |
g = self.lr_goto_cache.get((id(I), x)) | |
if g: | |
return g | |
# Now we generate the goto set in a way that guarantees uniqueness | |
# of the result | |
s = self.lr_goto_cache.get(x) | |
if not s: | |
s = {} | |
self.lr_goto_cache[x] = s | |
gs = [] | |
for p in I: | |
n = p.lr_next | |
if n and n.lr_before == x: | |
s1 = s.get(id(n)) | |
if not s1: | |
s1 = {} | |
s[id(n)] = s1 | |
gs.append(n) | |
s = s1 | |
g = s.get('$end') | |
if not g: | |
if gs: | |
g = self.lr0_closure(gs) | |
s['$end'] = g | |
else: | |
s['$end'] = gs | |
self.lr_goto_cache[(id(I), x)] = g | |
return g | |
# Compute the LR(0) sets of item function | |
def lr0_items(self): | |
C = [self.lr0_closure([self.grammar.Productions[0].lr_next])] | |
i = 0 | |
for I in C: | |
self.lr0_cidhash[id(I)] = i | |
i += 1 | |
# Loop over the items in C and each grammar symbols | |
i = 0 | |
while i < len(C): | |
I = C[i] | |
i += 1 | |
# Collect all of the symbols that could possibly be in the goto(I,X) sets | |
asyms = {} | |
for ii in I: | |
for s in ii.usyms: | |
asyms[s] = None | |
for x in asyms: | |
g = self.lr0_goto(I, x) | |
if not g or id(g) in self.lr0_cidhash: | |
continue | |
self.lr0_cidhash[id(g)] = len(C) | |
C.append(g) | |
return C | |
# ----------------------------------------------------------------------------- | |
# ==== LALR(1) Parsing ==== | |
# | |
# LALR(1) parsing is almost exactly the same as SLR except that instead of | |
# relying upon Follow() sets when performing reductions, a more selective | |
# lookahead set that incorporates the state of the LR(0) machine is utilized. | |
# Thus, we mainly just have to focus on calculating the lookahead sets. | |
# | |
# The method used here is due to DeRemer and Pennelo (1982). | |
# | |
# DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1) | |
# Lookahead Sets", ACM Transactions on Programming Languages and Systems, | |
# Vol. 4, No. 4, Oct. 1982, pp. 615-649 | |
# | |
# Further details can also be found in: | |
# | |
# J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing", | |
# McGraw-Hill Book Company, (1985). | |
# | |
# ----------------------------------------------------------------------------- | |
# ----------------------------------------------------------------------------- | |
# compute_nullable_nonterminals() | |
# | |
# Creates a dictionary containing all of the non-terminals that might produce | |
# an empty production. | |
# ----------------------------------------------------------------------------- | |
def compute_nullable_nonterminals(self): | |
nullable = set() | |
num_nullable = 0 | |
while True: | |
for p in self.grammar.Productions[1:]: | |
if p.len == 0: | |
nullable.add(p.name) | |
continue | |
for t in p.prod: | |
if t not in nullable: | |
break | |
else: | |
nullable.add(p.name) | |
if len(nullable) == num_nullable: | |
break | |
num_nullable = len(nullable) | |
return nullable | |
# ----------------------------------------------------------------------------- | |
# find_nonterminal_trans(C) | |
# | |
# Given a set of LR(0) items, this functions finds all of the non-terminal | |
# transitions. These are transitions in which a dot appears immediately before | |
# a non-terminal. Returns a list of tuples of the form (state,N) where state | |
# is the state number and N is the nonterminal symbol. | |
# | |
# The input C is the set of LR(0) items. | |
# ----------------------------------------------------------------------------- | |
def find_nonterminal_transitions(self, C): | |
trans = [] | |
for stateno, state in enumerate(C): | |
for p in state: | |
if p.lr_index < p.len - 1: | |
t = (stateno, p.prod[p.lr_index+1]) | |
if t[1] in self.grammar.Nonterminals: | |
if t not in trans: | |
trans.append(t) | |
return trans | |
# ----------------------------------------------------------------------------- | |
# dr_relation() | |
# | |
# Computes the DR(p,A) relationships for non-terminal transitions. The input | |
# is a tuple (state,N) where state is a number and N is a nonterminal symbol. | |
# | |
# Returns a list of terminals. | |
# ----------------------------------------------------------------------------- | |
def dr_relation(self, C, trans, nullable): | |
dr_set = {} | |
state, N = trans | |
terms = [] | |
g = self.lr0_goto(C[state], N) | |
for p in g: | |
if p.lr_index < p.len - 1: | |
a = p.prod[p.lr_index+1] | |
if a in self.grammar.Terminals: | |
if a not in terms: | |
terms.append(a) | |
# This extra bit is to handle the start state | |
if state == 0 and N == self.grammar.Productions[0].prod[0]: | |
terms.append('$end') | |
return terms | |
# ----------------------------------------------------------------------------- | |
# reads_relation() | |
# | |
# Computes the READS() relation (p,A) READS (t,C). | |
# ----------------------------------------------------------------------------- | |
def reads_relation(self, C, trans, empty): | |
# Look for empty transitions | |
rel = [] | |
state, N = trans | |
g = self.lr0_goto(C[state], N) | |
j = self.lr0_cidhash.get(id(g), -1) | |
for p in g: | |
if p.lr_index < p.len - 1: | |
a = p.prod[p.lr_index + 1] | |
if a in empty: | |
rel.append((j, a)) | |
return rel | |
# ----------------------------------------------------------------------------- | |
# compute_lookback_includes() | |
# | |
# Determines the lookback and includes relations | |
# | |
# LOOKBACK: | |
# | |
# This relation is determined by running the LR(0) state machine forward. | |
# For example, starting with a production "N : . A B C", we run it forward | |
# to obtain "N : A B C ." We then build a relationship between this final | |
# state and the starting state. These relationships are stored in a dictionary | |
# lookdict. | |
# | |
# INCLUDES: | |
# | |
# Computes the INCLUDE() relation (p,A) INCLUDES (p',B). | |
# | |
# This relation is used to determine non-terminal transitions that occur | |
# inside of other non-terminal transition states. (p,A) INCLUDES (p', B) | |
# if the following holds: | |
# | |
# B -> LAT, where T -> epsilon and p' -L-> p | |
# | |
# L is essentially a prefix (which may be empty), T is a suffix that must be | |
# able to derive an empty string. State p' must lead to state p with the string L. | |
# | |
# ----------------------------------------------------------------------------- | |
def compute_lookback_includes(self, C, trans, nullable): | |
lookdict = {} # Dictionary of lookback relations | |
includedict = {} # Dictionary of include relations | |
# Make a dictionary of non-terminal transitions | |
dtrans = {} | |
for t in trans: | |
dtrans[t] = 1 | |
# Loop over all transitions and compute lookbacks and includes | |
for state, N in trans: | |
lookb = [] | |
includes = [] | |
for p in C[state]: | |
if p.name != N: | |
continue | |
# Okay, we have a name match. We now follow the production all the way | |
# through the state machine until we get the . on the right hand side | |
lr_index = p.lr_index | |
j = state | |
while lr_index < p.len - 1: | |
lr_index = lr_index + 1 | |
t = p.prod[lr_index] | |
# Check to see if this symbol and state are a non-terminal transition | |
if (j, t) in dtrans: | |
# Yes. Okay, there is some chance that this is an includes relation | |
# the only way to know for certain is whether the rest of the | |
# production derives empty | |
li = lr_index + 1 | |
while li < p.len: | |
if p.prod[li] in self.grammar.Terminals: | |
break # No forget it | |
if p.prod[li] not in nullable: | |
break | |
li = li + 1 | |
else: | |
# Appears to be a relation between (j,t) and (state,N) | |
includes.append((j, t)) | |
g = self.lr0_goto(C[j], t) # Go to next set | |
j = self.lr0_cidhash.get(id(g), -1) # Go to next state | |
# When we get here, j is the final state, now we have to locate the production | |
for r in C[j]: | |
if r.name != p.name: | |
continue | |
if r.len != p.len: | |
continue | |
i = 0 | |
# This look is comparing a production ". A B C" with "A B C ." | |
while i < r.lr_index: | |
if r.prod[i] != p.prod[i+1]: | |
break | |
i = i + 1 | |
else: | |
lookb.append((j, r)) | |
for i in includes: | |
if i not in includedict: | |
includedict[i] = [] | |
includedict[i].append((state, N)) | |
lookdict[(state, N)] = lookb | |
return lookdict, includedict | |
# ----------------------------------------------------------------------------- | |
# compute_read_sets() | |
# | |
# Given a set of LR(0) items, this function computes the read sets. | |
# | |
# Inputs: C = Set of LR(0) items | |
# ntrans = Set of nonterminal transitions | |
# nullable = Set of empty transitions | |
# | |
# Returns a set containing the read sets | |
# ----------------------------------------------------------------------------- | |
def compute_read_sets(self, C, ntrans, nullable): | |
FP = lambda x: self.dr_relation(C, x, nullable) | |
R = lambda x: self.reads_relation(C, x, nullable) | |
F = digraph(ntrans, R, FP) | |
return F | |
# ----------------------------------------------------------------------------- | |
# compute_follow_sets() | |
# | |
# Given a set of LR(0) items, a set of non-terminal transitions, a readset, | |
# and an include set, this function computes the follow sets | |
# | |
# Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)} | |
# | |
# Inputs: | |
# ntrans = Set of nonterminal transitions | |
# readsets = Readset (previously computed) | |
# inclsets = Include sets (previously computed) | |
# | |
# Returns a set containing the follow sets | |
# ----------------------------------------------------------------------------- | |
def compute_follow_sets(self, ntrans, readsets, inclsets): | |
FP = lambda x: readsets[x] | |
R = lambda x: inclsets.get(x, []) | |
F = digraph(ntrans, R, FP) | |
return F | |
# ----------------------------------------------------------------------------- | |
# add_lookaheads() | |
# | |
# Attaches the lookahead symbols to grammar rules. | |
# | |
# Inputs: lookbacks - Set of lookback relations | |
# followset - Computed follow set | |
# | |
# This function directly attaches the lookaheads to productions contained | |
# in the lookbacks set | |
# ----------------------------------------------------------------------------- | |
def add_lookaheads(self, lookbacks, followset): | |
for trans, lb in lookbacks.items(): | |
# Loop over productions in lookback | |
for state, p in lb: | |
if state not in p.lookaheads: | |
p.lookaheads[state] = [] | |
f = followset.get(trans, []) | |
for a in f: | |
if a not in p.lookaheads[state]: | |
p.lookaheads[state].append(a) | |
# ----------------------------------------------------------------------------- | |
# add_lalr_lookaheads() | |
# | |
# This function does all of the work of adding lookahead information for use | |
# with LALR parsing | |
# ----------------------------------------------------------------------------- | |
def add_lalr_lookaheads(self, C): | |
# Determine all of the nullable nonterminals | |
nullable = self.compute_nullable_nonterminals() | |
# Find all non-terminal transitions | |
trans = self.find_nonterminal_transitions(C) | |
# Compute read sets | |
readsets = self.compute_read_sets(C, trans, nullable) | |
# Compute lookback/includes relations | |
lookd, included = self.compute_lookback_includes(C, trans, nullable) | |
# Compute LALR FOLLOW sets | |
followsets = self.compute_follow_sets(trans, readsets, included) | |
# Add all of the lookaheads | |
self.add_lookaheads(lookd, followsets) | |
# ----------------------------------------------------------------------------- | |
# lr_parse_table() | |
# | |
# This function constructs the parse tables for SLR or LALR | |
# ----------------------------------------------------------------------------- | |
def lr_parse_table(self): | |
Productions = self.grammar.Productions | |
Precedence = self.grammar.Precedence | |
goto = self.lr_goto # Goto array | |
action = self.lr_action # Action array | |
log = self.log # Logger for output | |
actionp = {} # Action production array (temporary) | |
log.info('Parsing method: %s', self.lr_method) | |
# Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items | |
# This determines the number of states | |
C = self.lr0_items() | |
if self.lr_method == 'LALR': | |
self.add_lalr_lookaheads(C) | |
# Build the parser table, state by state | |
st = 0 | |
for I in C: | |
# Loop over each production in I | |
actlist = [] # List of actions | |
st_action = {} | |
st_actionp = {} | |
st_goto = {} | |
log.info('') | |
log.info('state %d', st) | |
log.info('') | |
for p in I: | |
log.info(' (%d) %s', p.number, p) | |
log.info('') | |
for p in I: | |
if p.len == p.lr_index + 1: | |
if p.name == "S'": | |
# Start symbol. Accept! | |
st_action['$end'] = 0 | |
st_actionp['$end'] = p | |
else: | |
# We are at the end of a production. Reduce! | |
if self.lr_method == 'LALR': | |
laheads = p.lookaheads[st] | |
else: | |
laheads = self.grammar.Follow[p.name] | |
for a in laheads: | |
actlist.append((a, p, 'reduce using rule %d (%s)' % (p.number, p))) | |
r = st_action.get(a) | |
if r is not None: | |
# Whoa. Have a shift/reduce or reduce/reduce conflict | |
if r > 0: | |
# Need to decide on shift or reduce here | |
# By default we favor shifting. Need to add | |
# some precedence rules here. | |
# Shift precedence comes from the token | |
sprec, slevel = Precedence.get(a, ('right', 0)) | |
# Reduce precedence comes from rule being reduced (p) | |
rprec, rlevel = Productions[p.number].prec | |
if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): | |
# We really need to reduce here. | |
st_action[a] = -p.number | |
st_actionp[a] = p | |
if not slevel and not rlevel: | |
log.info(' ! shift/reduce conflict for %s resolved as reduce', a) | |
self.sr_conflicts.append((st, a, 'reduce')) | |
Productions[p.number].reduced += 1 | |
elif (slevel == rlevel) and (rprec == 'nonassoc'): | |
st_action[a] = None | |
else: | |
# Hmmm. Guess we'll keep the shift | |
if not rlevel: | |
log.info(' ! shift/reduce conflict for %s resolved as shift', a) | |
self.sr_conflicts.append((st, a, 'shift')) | |
elif r < 0: | |
# Reduce/reduce conflict. In this case, we favor the rule | |
# that was defined first in the grammar file | |
oldp = Productions[-r] | |
pp = Productions[p.number] | |
if oldp.line > pp.line: | |
st_action[a] = -p.number | |
st_actionp[a] = p | |
chosenp, rejectp = pp, oldp | |
Productions[p.number].reduced += 1 | |
Productions[oldp.number].reduced -= 1 | |
else: | |
chosenp, rejectp = oldp, pp | |
self.rr_conflicts.append((st, chosenp, rejectp)) | |
log.info(' ! reduce/reduce conflict for %s resolved using rule %d (%s)', | |
a, st_actionp[a].number, st_actionp[a]) | |
else: | |
raise LALRError('Unknown conflict in state %d' % st) | |
else: | |
st_action[a] = -p.number | |
st_actionp[a] = p | |
Productions[p.number].reduced += 1 | |
else: | |
i = p.lr_index | |
a = p.prod[i+1] # Get symbol right after the "." | |
if a in self.grammar.Terminals: | |
g = self.lr0_goto(I, a) | |
j = self.lr0_cidhash.get(id(g), -1) | |
if j >= 0: | |
# We are in a shift state | |
actlist.append((a, p, 'shift and go to state %d' % j)) | |
r = st_action.get(a) | |
if r is not None: | |
# Whoa have a shift/reduce or shift/shift conflict | |
if r > 0: | |
if r != j: | |
raise LALRError('Shift/shift conflict in state %d' % st) | |
elif r < 0: | |
# Do a precedence check. | |
# - if precedence of reduce rule is higher, we reduce. | |
# - if precedence of reduce is same and left assoc, we reduce. | |
# - otherwise we shift | |
# Shift precedence comes from the token | |
sprec, slevel = Precedence.get(a, ('right', 0)) | |
# Reduce precedence comes from the rule that could have been reduced | |
rprec, rlevel = Productions[st_actionp[a].number].prec | |
if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')): | |
# We decide to shift here... highest precedence to shift | |
Productions[st_actionp[a].number].reduced -= 1 | |
st_action[a] = j | |
st_actionp[a] = p | |
if not rlevel: | |
log.info(' ! shift/reduce conflict for %s resolved as shift', a) | |
self.sr_conflicts.append((st, a, 'shift')) | |
elif (slevel == rlevel) and (rprec == 'nonassoc'): | |
st_action[a] = None | |
else: | |
# Hmmm. Guess we'll keep the reduce | |
if not slevel and not rlevel: | |
log.info(' ! shift/reduce conflict for %s resolved as reduce', a) | |
self.sr_conflicts.append((st, a, 'reduce')) | |
else: | |
raise LALRError('Unknown conflict in state %d' % st) | |
else: | |
st_action[a] = j | |
st_actionp[a] = p | |
# Print the actions associated with each terminal | |
_actprint = {} | |
for a, p, m in actlist: | |
if a in st_action: | |
if p is st_actionp[a]: | |
log.info(' %-15s %s', a, m) | |
_actprint[(a, m)] = 1 | |
log.info('') | |
# Print the actions that were not used. (debugging) | |
not_used = 0 | |
for a, p, m in actlist: | |
if a in st_action: | |
if p is not st_actionp[a]: | |
if not (a, m) in _actprint: | |
log.debug(' ! %-15s [ %s ]', a, m) | |
not_used = 1 | |
_actprint[(a, m)] = 1 | |
if not_used: | |
log.debug('') | |
# Construct the goto table for this state | |
nkeys = {} | |
for ii in I: | |
for s in ii.usyms: | |
if s in self.grammar.Nonterminals: | |
nkeys[s] = None | |
for n in nkeys: | |
g = self.lr0_goto(I, n) | |
j = self.lr0_cidhash.get(id(g), -1) | |
if j >= 0: | |
st_goto[n] = j | |
log.info(' %-30s shift and go to state %d', n, j) | |
action[st] = st_action | |
actionp[st] = st_actionp | |
goto[st] = st_goto | |
st += 1 | |
# ----------------------------------------------------------------------------- | |
# write() | |
# | |
# This function writes the LR parsing tables to a file | |
# ----------------------------------------------------------------------------- | |
def write_table(self, tabmodule, outputdir='', signature=''): | |
if isinstance(tabmodule, types.ModuleType): | |
raise IOError("Won't overwrite existing tabmodule") | |
basemodulename = tabmodule.split('.')[-1] | |
filename = os.path.join(outputdir, basemodulename) + '.py' | |
try: | |
f = open(filename, 'w') | |
f.write(''' | |
# %s | |
# This file is automatically generated. Do not edit. | |
_tabversion = %r | |
_lr_method = %r | |
_lr_signature = %r | |
''' % (os.path.basename(filename), __tabversion__, self.lr_method, signature)) | |
# Change smaller to 0 to go back to original tables | |
smaller = 1 | |
# Factor out names to try and make smaller | |
if smaller: | |
items = {} | |
for s, nd in self.lr_action.items(): | |
for name, v in nd.items(): | |
i = items.get(name) | |
if not i: | |
i = ([], []) | |
items[name] = i | |
i[0].append(s) | |
i[1].append(v) | |
f.write('\n_lr_action_items = {') | |
for k, v in items.items(): | |
f.write('%r:([' % k) | |
for i in v[0]: | |
f.write('%r,' % i) | |
f.write('],[') | |
for i in v[1]: | |
f.write('%r,' % i) | |
f.write(']),') | |
f.write('}\n') | |
f.write(''' | |
_lr_action = {} | |
for _k, _v in _lr_action_items.items(): | |
for _x,_y in zip(_v[0],_v[1]): | |
if not _x in _lr_action: _lr_action[_x] = {} | |
_lr_action[_x][_k] = _y | |
del _lr_action_items | |
''') | |
else: | |
f.write('\n_lr_action = { ') | |
for k, v in self.lr_action.items(): | |
f.write('(%r,%r):%r,' % (k[0], k[1], v)) | |
f.write('}\n') | |
if smaller: | |
# Factor out names to try and make smaller | |
items = {} | |
for s, nd in self.lr_goto.items(): | |
for name, v in nd.items(): | |
i = items.get(name) | |
if not i: | |
i = ([], []) | |
items[name] = i | |
i[0].append(s) | |
i[1].append(v) | |
f.write('\n_lr_goto_items = {') | |
for k, v in items.items(): | |
f.write('%r:([' % k) | |
for i in v[0]: | |
f.write('%r,' % i) | |
f.write('],[') | |
for i in v[1]: | |
f.write('%r,' % i) | |
f.write(']),') | |
f.write('}\n') | |
f.write(''' | |
_lr_goto = {} | |
for _k, _v in _lr_goto_items.items(): | |
for _x, _y in zip(_v[0], _v[1]): | |
if not _x in _lr_goto: _lr_goto[_x] = {} | |
_lr_goto[_x][_k] = _y | |
del _lr_goto_items | |
''') | |
else: | |
f.write('\n_lr_goto = { ') | |
for k, v in self.lr_goto.items(): | |
f.write('(%r,%r):%r,' % (k[0], k[1], v)) | |
f.write('}\n') | |
# Write production table | |
f.write('_lr_productions = [\n') | |
for p in self.lr_productions: | |
if p.func: | |
f.write(' (%r,%r,%d,%r,%r,%d),\n' % (p.str, p.name, p.len, | |
p.func, os.path.basename(p.file), p.line)) | |
else: | |
f.write(' (%r,%r,%d,None,None,None),\n' % (str(p), p.name, p.len)) | |
f.write(']\n') | |
f.close() | |
except IOError as e: | |
raise | |
# ----------------------------------------------------------------------------- | |
# pickle_table() | |
# | |
# This function pickles the LR parsing tables to a supplied file object | |
# ----------------------------------------------------------------------------- | |
def pickle_table(self, filename, signature=''): | |
try: | |
import cPickle as pickle | |
except ImportError: | |
import pickle | |
with open(filename, 'wb') as outf: | |
pickle.dump(__tabversion__, outf, pickle_protocol) | |
pickle.dump(self.lr_method, outf, pickle_protocol) | |
pickle.dump(signature, outf, pickle_protocol) | |
pickle.dump(self.lr_action, outf, pickle_protocol) | |
pickle.dump(self.lr_goto, outf, pickle_protocol) | |
outp = [] | |
for p in self.lr_productions: | |
if p.func: | |
outp.append((p.str, p.name, p.len, p.func, os.path.basename(p.file), p.line)) | |
else: | |
outp.append((str(p), p.name, p.len, None, None, None)) | |
pickle.dump(outp, outf, pickle_protocol) | |
# ----------------------------------------------------------------------------- | |
# === INTROSPECTION === | |
# | |
# The following functions and classes are used to implement the PLY | |
# introspection features followed by the yacc() function itself. | |
# ----------------------------------------------------------------------------- | |
# ----------------------------------------------------------------------------- | |
# get_caller_module_dict() | |
# | |
# This function returns a dictionary containing all of the symbols defined within | |
# a caller further down the call stack. This is used to get the environment | |
# associated with the yacc() call if none was provided. | |
# ----------------------------------------------------------------------------- | |
def get_caller_module_dict(levels): | |
f = sys._getframe(levels) | |
ldict = f.f_globals.copy() | |
if f.f_globals != f.f_locals: | |
ldict.update(f.f_locals) | |
return ldict | |
# ----------------------------------------------------------------------------- | |
# parse_grammar() | |
# | |
# This takes a raw grammar rule string and parses it into production data | |
# ----------------------------------------------------------------------------- | |
def parse_grammar(doc, file, line): | |
grammar = [] | |
# Split the doc string into lines | |
pstrings = doc.splitlines() | |
lastp = None | |
dline = line | |
for ps in pstrings: | |
dline += 1 | |
p = ps.split() | |
if not p: | |
continue | |
try: | |
if p[0] == '|': | |
# This is a continuation of a previous rule | |
if not lastp: | |
raise SyntaxError("%s:%d: Misplaced '|'" % (file, dline)) | |
prodname = lastp | |
syms = p[1:] | |
else: | |
prodname = p[0] | |
lastp = prodname | |
syms = p[2:] | |
assign = p[1] | |
if assign != ':' and assign != '::=': | |
raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file, dline)) | |
grammar.append((file, dline, prodname, syms)) | |
except SyntaxError: | |
raise | |
except Exception: | |
raise SyntaxError('%s:%d: Syntax error in rule %r' % (file, dline, ps.strip())) | |
return grammar | |
# ----------------------------------------------------------------------------- | |
# ParserReflect() | |
# | |
# This class represents information extracted for building a parser including | |
# start symbol, error function, tokens, precedence list, action functions, | |
# etc. | |
# ----------------------------------------------------------------------------- | |
class ParserReflect(object): | |
def __init__(self, pdict, log=None): | |
self.pdict = pdict | |
self.start = None | |
self.error_func = None | |
self.tokens = None | |
self.modules = set() | |
self.grammar = [] | |
self.error = False | |
if log is None: | |
self.log = PlyLogger(sys.stderr) | |
else: | |
self.log = log | |
# Get all of the basic information | |
def get_all(self): | |
self.get_start() | |
self.get_error_func() | |
self.get_tokens() | |
self.get_precedence() | |
self.get_pfunctions() | |
# Validate all of the information | |
def validate_all(self): | |
self.validate_start() | |
self.validate_error_func() | |
self.validate_tokens() | |
self.validate_precedence() | |
self.validate_pfunctions() | |
self.validate_modules() | |
return self.error | |
# Compute a signature over the grammar | |
def signature(self): | |
parts = [] | |
try: | |
if self.start: | |
parts.append(self.start) | |
if self.prec: | |
parts.append(''.join([''.join(p) for p in self.prec])) | |
if self.tokens: | |
parts.append(' '.join(self.tokens)) | |
for f in self.pfuncs: | |
if f[3]: | |
parts.append(f[3]) | |
except (TypeError, ValueError): | |
pass | |
return ''.join(parts) | |
# ----------------------------------------------------------------------------- | |
# validate_modules() | |
# | |
# This method checks to see if there are duplicated p_rulename() functions | |
# in the parser module file. Without this function, it is really easy for | |
# users to make mistakes by cutting and pasting code fragments (and it's a real | |
# bugger to try and figure out why the resulting parser doesn't work). Therefore, | |
# we just do a little regular expression pattern matching of def statements | |
# to try and detect duplicates. | |
# ----------------------------------------------------------------------------- | |
def validate_modules(self): | |
# Match def p_funcname( | |
fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') | |
for module in self.modules: | |
try: | |
lines, linen = inspect.getsourcelines(module) | |
except IOError: | |
continue | |
counthash = {} | |
for linen, line in enumerate(lines): | |
linen += 1 | |
m = fre.match(line) | |
if m: | |
name = m.group(1) | |
prev = counthash.get(name) | |
if not prev: | |
counthash[name] = linen | |
else: | |
filename = inspect.getsourcefile(module) | |
self.log.warning('%s:%d: Function %s redefined. Previously defined on line %d', | |
filename, linen, name, prev) | |
# Get the start symbol | |
def get_start(self): | |
self.start = self.pdict.get('start') | |
# Validate the start symbol | |
def validate_start(self): | |
if self.start is not None: | |
if not isinstance(self.start, string_types): | |
self.log.error("'start' must be a string") | |
# Look for error handler | |
def get_error_func(self): | |
self.error_func = self.pdict.get('p_error') | |
# Validate the error function | |
def validate_error_func(self): | |
if self.error_func: | |
if isinstance(self.error_func, types.FunctionType): | |
ismethod = 0 | |
elif isinstance(self.error_func, types.MethodType): | |
ismethod = 1 | |
else: | |
self.log.error("'p_error' defined, but is not a function or method") | |
self.error = True | |
return | |
eline = self.error_func.__code__.co_firstlineno | |
efile = self.error_func.__code__.co_filename | |
module = inspect.getmodule(self.error_func) | |
self.modules.add(module) | |
argcount = self.error_func.__code__.co_argcount - ismethod | |
if argcount != 1: | |
self.log.error('%s:%d: p_error() requires 1 argument', efile, eline) | |
self.error = True | |
# Get the tokens map | |
def get_tokens(self): | |
tokens = self.pdict.get('tokens') | |
if not tokens: | |
self.log.error('No token list is defined') | |
self.error = True | |
return | |
if not isinstance(tokens, (list, tuple)): | |
self.log.error('tokens must be a list or tuple') | |
self.error = True | |
return | |
if not tokens: | |
self.log.error('tokens is empty') | |
self.error = True | |
return | |
self.tokens = tokens | |
# Validate the tokens | |
def validate_tokens(self): | |
# Validate the tokens. | |
if 'error' in self.tokens: | |
self.log.error("Illegal token name 'error'. Is a reserved word") | |
self.error = True | |
return | |
terminals = set() | |
for n in self.tokens: | |
if n in terminals: | |
self.log.warning('Token %r multiply defined', n) | |
terminals.add(n) | |
# Get the precedence map (if any) | |
def get_precedence(self): | |
self.prec = self.pdict.get('precedence') | |
# Validate and parse the precedence map | |
def validate_precedence(self): | |
preclist = [] | |
if self.prec: | |
if not isinstance(self.prec, (list, tuple)): | |
self.log.error('precedence must be a list or tuple') | |
self.error = True | |
return | |
for level, p in enumerate(self.prec): | |
if not isinstance(p, (list, tuple)): | |
self.log.error('Bad precedence table') | |
self.error = True | |
return | |
if len(p) < 2: | |
self.log.error('Malformed precedence entry %s. Must be (assoc, term, ..., term)', p) | |
self.error = True | |
return | |
assoc = p[0] | |
if not isinstance(assoc, string_types): | |
self.log.error('precedence associativity must be a string') | |
self.error = True | |
return | |
for term in p[1:]: | |
if not isinstance(term, string_types): | |
self.log.error('precedence items must be strings') | |
self.error = True | |
return | |
preclist.append((term, assoc, level+1)) | |
self.preclist = preclist | |
# Get all p_functions from the grammar | |
def get_pfunctions(self): | |
p_functions = [] | |
for name, item in self.pdict.items(): | |
if not name.startswith('p_') or name == 'p_error': | |
continue | |
if isinstance(item, (types.FunctionType, types.MethodType)): | |
line = getattr(item, 'co_firstlineno', item.__code__.co_firstlineno) | |
module = inspect.getmodule(item) | |
p_functions.append((line, module, name, item.__doc__)) | |
# Sort all of the actions by line number; make sure to stringify | |
# modules to make them sortable, since `line` may not uniquely sort all | |
# p functions | |
p_functions.sort(key=lambda p_function: ( | |
p_function[0], | |
str(p_function[1]), | |
p_function[2], | |
p_function[3])) | |
self.pfuncs = p_functions | |
# Validate all of the p_functions | |
def validate_pfunctions(self): | |
grammar = [] | |
# Check for non-empty symbols | |
if len(self.pfuncs) == 0: | |
self.log.error('no rules of the form p_rulename are defined') | |
self.error = True | |
return | |
for line, module, name, doc in self.pfuncs: | |
file = inspect.getsourcefile(module) | |
func = self.pdict[name] | |
if isinstance(func, types.MethodType): | |
reqargs = 2 | |
else: | |
reqargs = 1 | |
if func.__code__.co_argcount > reqargs: | |
self.log.error('%s:%d: Rule %r has too many arguments', file, line, func.__name__) | |
self.error = True | |
elif func.__code__.co_argcount < reqargs: | |
self.log.error('%s:%d: Rule %r requires an argument', file, line, func.__name__) | |
self.error = True | |
elif not func.__doc__: | |
self.log.warning('%s:%d: No documentation string specified in function %r (ignored)', | |
file, line, func.__name__) | |
else: | |
try: | |
parsed_g = parse_grammar(doc, file, line) | |
for g in parsed_g: | |
grammar.append((name, g)) | |
except SyntaxError as e: | |
self.log.error(str(e)) | |
self.error = True | |
# Looks like a valid grammar rule | |
# Mark the file in which defined. | |
self.modules.add(module) | |
# Secondary validation step that looks for p_ definitions that are not functions | |
# or functions that look like they might be grammar rules. | |
for n, v in self.pdict.items(): | |
if n.startswith('p_') and isinstance(v, (types.FunctionType, types.MethodType)): | |
continue | |
if n.startswith('t_'): | |
continue | |
if n.startswith('p_') and n != 'p_error': | |
self.log.warning('%r not defined as a function', n) | |
if ((isinstance(v, types.FunctionType) and v.__code__.co_argcount == 1) or | |
(isinstance(v, types.MethodType) and v.__func__.__code__.co_argcount == 2)): | |
if v.__doc__: | |
try: | |
doc = v.__doc__.split(' ') | |
if doc[1] == ':': | |
self.log.warning('%s:%d: Possible grammar rule %r defined without p_ prefix', | |
v.__code__.co_filename, v.__code__.co_firstlineno, n) | |
except IndexError: | |
pass | |
self.grammar = grammar | |
# ----------------------------------------------------------------------------- | |
# yacc(module) | |
# | |
# Build a parser | |
# ----------------------------------------------------------------------------- | |
def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None, | |
check_recursion=True, optimize=False, write_tables=True, debugfile=debug_file, | |
outputdir=None, debuglog=None, errorlog=None, picklefile=None): | |
if tabmodule is None: | |
tabmodule = tab_module | |
# Reference to the parsing method of the last built parser | |
global parse | |
# If pickling is enabled, table files are not created | |
if picklefile: | |
write_tables = 0 | |
if errorlog is None: | |
errorlog = PlyLogger(sys.stderr) | |
# Get the module dictionary used for the parser | |
if module: | |
_items = [(k, getattr(module, k)) for k in dir(module)] | |
pdict = dict(_items) | |
# If no __file__ attribute is available, try to obtain it from the __module__ instead | |
if '__file__' not in pdict: | |
pdict['__file__'] = sys.modules[pdict['__module__']].__file__ | |
else: | |
pdict = get_caller_module_dict(2) | |
if outputdir is None: | |
# If no output directory is set, the location of the output files | |
# is determined according to the following rules: | |
# - If tabmodule specifies a package, files go into that package directory | |
# - Otherwise, files go in the same directory as the specifying module | |
if isinstance(tabmodule, types.ModuleType): | |
srcfile = tabmodule.__file__ | |
else: | |
if '.' not in tabmodule: | |
srcfile = pdict['__file__'] | |
else: | |
parts = tabmodule.split('.') | |
pkgname = '.'.join(parts[:-1]) | |
exec('import %s' % pkgname) | |
srcfile = getattr(sys.modules[pkgname], '__file__', '') | |
outputdir = os.path.dirname(srcfile) | |
# Determine if the module is package of a package or not. | |
# If so, fix the tabmodule setting so that tables load correctly | |
pkg = pdict.get('__package__') | |
if pkg and isinstance(tabmodule, str): | |
if '.' not in tabmodule: | |
tabmodule = pkg + '.' + tabmodule | |
# Set start symbol if it's specified directly using an argument | |
if start is not None: | |
pdict['start'] = start | |
# Collect parser information from the dictionary | |
pinfo = ParserReflect(pdict, log=errorlog) | |
pinfo.get_all() | |
if pinfo.error: | |
raise YaccError('Unable to build parser') | |
# Check signature against table files (if any) | |
signature = pinfo.signature() | |
# Read the tables | |
try: | |
lr = LRTable() | |
if picklefile: | |
read_signature = lr.read_pickle(picklefile) | |
else: | |
read_signature = lr.read_table(tabmodule) | |
if optimize or (read_signature == signature): | |
try: | |
lr.bind_callables(pinfo.pdict) | |
parser = LRParser(lr, pinfo.error_func) | |
parse = parser.parse | |
return parser | |
except Exception as e: | |
errorlog.warning('There was a problem loading the table file: %r', e) | |
except VersionError as e: | |
errorlog.warning(str(e)) | |
except ImportError: | |
pass | |
if debuglog is None: | |
if debug: | |
try: | |
debuglog = PlyLogger(open(os.path.join(outputdir, debugfile), 'w')) | |
except IOError as e: | |
errorlog.warning("Couldn't open %r. %s" % (debugfile, e)) | |
debuglog = NullLogger() | |
else: | |
debuglog = NullLogger() | |
debuglog.info('Created by PLY version %s (http://www.dabeaz.com/ply)', __version__) | |
errors = False | |
# Validate the parser information | |
if pinfo.validate_all(): | |
raise YaccError('Unable to build parser') | |
if not pinfo.error_func: | |
errorlog.warning('no p_error() function is defined') | |
# Create a grammar object | |
grammar = Grammar(pinfo.tokens) | |
# Set precedence level for terminals | |
for term, assoc, level in pinfo.preclist: | |
try: | |
grammar.set_precedence(term, assoc, level) | |
except GrammarError as e: | |
errorlog.warning('%s', e) | |
# Add productions to the grammar | |
for funcname, gram in pinfo.grammar: | |
file, line, prodname, syms = gram | |
try: | |
grammar.add_production(prodname, syms, funcname, file, line) | |
except GrammarError as e: | |
errorlog.error('%s', e) | |
errors = True | |
# Set the grammar start symbols | |
try: | |
if start is None: | |
grammar.set_start(pinfo.start) | |
else: | |
grammar.set_start(start) | |
except GrammarError as e: | |
errorlog.error(str(e)) | |
errors = True | |
if errors: | |
raise YaccError('Unable to build parser') | |
# Verify the grammar structure | |
undefined_symbols = grammar.undefined_symbols() | |
for sym, prod in undefined_symbols: | |
errorlog.error('%s:%d: Symbol %r used, but not defined as a token or a rule', prod.file, prod.line, sym) | |
errors = True | |
unused_terminals = grammar.unused_terminals() | |
if unused_terminals: | |
debuglog.info('') | |
debuglog.info('Unused terminals:') | |
debuglog.info('') | |
for term in unused_terminals: | |
errorlog.warning('Token %r defined, but not used', term) | |
debuglog.info(' %s', term) | |
# Print out all productions to the debug log | |
if debug: | |
debuglog.info('') | |
debuglog.info('Grammar') | |
debuglog.info('') | |
for n, p in enumerate(grammar.Productions): | |
debuglog.info('Rule %-5d %s', n, p) | |
# Find unused non-terminals | |
unused_rules = grammar.unused_rules() | |
for prod in unused_rules: | |
errorlog.warning('%s:%d: Rule %r defined, but not used', prod.file, prod.line, prod.name) | |
if len(unused_terminals) == 1: | |
errorlog.warning('There is 1 unused token') | |
if len(unused_terminals) > 1: | |
errorlog.warning('There are %d unused tokens', len(unused_terminals)) | |
if len(unused_rules) == 1: | |
errorlog.warning('There is 1 unused rule') | |
if len(unused_rules) > 1: | |
errorlog.warning('There are %d unused rules', len(unused_rules)) | |
if debug: | |
debuglog.info('') | |
debuglog.info('Terminals, with rules where they appear') | |
debuglog.info('') | |
terms = list(grammar.Terminals) | |
terms.sort() | |
for term in terms: | |
debuglog.info('%-20s : %s', term, ' '.join([str(s) for s in grammar.Terminals[term]])) | |
debuglog.info('') | |
debuglog.info('Nonterminals, with rules where they appear') | |
debuglog.info('') | |
nonterms = list(grammar.Nonterminals) | |
nonterms.sort() | |
for nonterm in nonterms: | |
debuglog.info('%-20s : %s', nonterm, ' '.join([str(s) for s in grammar.Nonterminals[nonterm]])) | |
debuglog.info('') | |
if check_recursion: | |
unreachable = grammar.find_unreachable() | |
for u in unreachable: | |
errorlog.warning('Symbol %r is unreachable', u) | |
infinite = grammar.infinite_cycles() | |
for inf in infinite: | |
errorlog.error('Infinite recursion detected for symbol %r', inf) | |
errors = True | |
unused_prec = grammar.unused_precedence() | |
for term, assoc in unused_prec: | |
errorlog.error('Precedence rule %r defined for unknown symbol %r', assoc, term) | |
errors = True | |
if errors: | |
raise YaccError('Unable to build parser') | |
# Run the LRGeneratedTable on the grammar | |
if debug: | |
errorlog.debug('Generating %s tables', method) | |
lr = LRGeneratedTable(grammar, method, debuglog) | |
if debug: | |
num_sr = len(lr.sr_conflicts) | |
# Report shift/reduce and reduce/reduce conflicts | |
if num_sr == 1: | |
errorlog.warning('1 shift/reduce conflict') | |
elif num_sr > 1: | |
errorlog.warning('%d shift/reduce conflicts', num_sr) | |
num_rr = len(lr.rr_conflicts) | |
if num_rr == 1: | |
errorlog.warning('1 reduce/reduce conflict') | |
elif num_rr > 1: | |
errorlog.warning('%d reduce/reduce conflicts', num_rr) | |
# Write out conflicts to the output file | |
if debug and (lr.sr_conflicts or lr.rr_conflicts): | |
debuglog.warning('') | |
debuglog.warning('Conflicts:') | |
debuglog.warning('') | |
for state, tok, resolution in lr.sr_conflicts: | |
debuglog.warning('shift/reduce conflict for %s in state %d resolved as %s', tok, state, resolution) | |
already_reported = set() | |
for state, rule, rejected in lr.rr_conflicts: | |
if (state, id(rule), id(rejected)) in already_reported: | |
continue | |
debuglog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) | |
debuglog.warning('rejected rule (%s) in state %d', rejected, state) | |
errorlog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) | |
errorlog.warning('rejected rule (%s) in state %d', rejected, state) | |
already_reported.add((state, id(rule), id(rejected))) | |
warned_never = [] | |
for state, rule, rejected in lr.rr_conflicts: | |
if not rejected.reduced and (rejected not in warned_never): | |
debuglog.warning('Rule (%s) is never reduced', rejected) | |
errorlog.warning('Rule (%s) is never reduced', rejected) | |
warned_never.append(rejected) | |
# Write the table file if requested | |
if write_tables: | |
try: | |
lr.write_table(tabmodule, outputdir, signature) | |
except IOError as e: | |
errorlog.warning("Couldn't create %r. %s" % (tabmodule, e)) | |
# Write a pickled version of the tables | |
if picklefile: | |
try: | |
lr.pickle_table(picklefile, signature) | |
except IOError as e: | |
errorlog.warning("Couldn't create %r. %s" % (picklefile, e)) | |
# Build the parser | |
lr.bind_callables(pinfo.pdict) | |
parser = LRParser(lr, pinfo.error_func) | |
parse = parser.parse | |
return parser | |