3495 lines
134 KiB
Python
3495 lines
134 KiB
Python
# -----------------------------------------------------------------------------
|
|
# 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
|