| Index: tools/nixysa/third_party/ply-3.1/ply/yacc.py
|
| ===================================================================
|
| --- tools/nixysa/third_party/ply-3.1/ply/yacc.py (revision 0)
|
| +++ tools/nixysa/third_party/ply-3.1/ply/yacc.py (revision 0)
|
| @@ -0,0 +1,3186 @@
|
| +# -----------------------------------------------------------------------------
|
| +# ply: yacc.py
|
| +#
|
| +# Author(s): David M. Beazley (dave@dabeaz.com)
|
| +#
|
| +# Copyright (C) 2001-2009, David M. Beazley
|
| +#
|
| +# This library is free software; you can redistribute it and/or
|
| +# modify it under the terms of the GNU Lesser General Public
|
| +# License as published by the Free Software Foundation; either
|
| +# version 2.1 of the License, or (at your option) any later version.
|
| +#
|
| +# This library is distributed in the hope that it will be useful,
|
| +# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
| +# Lesser General Public License for more details.
|
| +#
|
| +# You should have received a copy of the GNU Lesser General Public
|
| +# License along with this library; if not, write to the Free Software
|
| +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
| +#
|
| +# See the file COPYING for a complete copy of the LGPL.
|
| +#
|
| +#
|
| +# 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!
|
| +# ----------------------------------------------------------------------------
|
| +
|
| +__version__ = "3.0"
|
| +__tabversion__ = "3.0" # Table version
|
| +
|
| +#-----------------------------------------------------------------------------
|
| +# === User configurable parameters ===
|
| +#
|
| +# Change these to modify the default behavior of yacc (if you wish)
|
| +#-----------------------------------------------------------------------------
|
| +
|
| +yaccdebug = 1 # 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 = 0 # 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.
|
| +
|
| +import re, types, sys, os.path
|
| +
|
| +# Compatibility function for python 2.6/3.0
|
| +if sys.version_info[0] < 3:
|
| + def func_code(f):
|
| + return f.func_code
|
| +else:
|
| + def func_code(f):
|
| + return f.__code__
|
| +
|
| +# Compatibility
|
| +try:
|
| + MAXINT = sys.maxint
|
| +except AttributeError:
|
| + MAXINT = sys.maxsize
|
| +
|
| +# Python 2.x/3.0 compatibility.
|
| +def load_ply_lex():
|
| + if sys.version_info[0] < 3:
|
| + import lex
|
| + else:
|
| + import ply.lex as lex
|
| + return lex
|
| +
|
| +# 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))
|
| +
|
| +#-----------------------------------------------------------------------------
|
| +# === 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 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 = n
|
| +
|
| + 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
|
| +
|
| + def errok(self):
|
| + self.errorok = 1
|
| +
|
| + def restart(self):
|
| + del self.statestack[:]
|
| + del self.symstack[:]
|
| + sym = YaccSymbol()
|
| + sym.type = '$end'
|
| + self.symstack.append(sym)
|
| + self.statestack.append(0)
|
| +
|
| + def parse(self,input=None,lexer=None,debug=0,tracking=0,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. For the non-debugging version,
|
| + # copy this code to a method parseopt() and delete all of the sections
|
| + # enclosed in:
|
| + #
|
| + # #--! DEBUG
|
| + # statements
|
| + # #--! DEBUG
|
| + #
|
| + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| +
|
| + def parsedebug(self,input=None,lexer=None,debug=None,tracking=0,tokenfunc=None):
|
| + 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.)
|
| + 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:
|
| + lex = load_ply_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 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 1:
|
| + # 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 not lookahead:
|
| + if not lookaheadstack:
|
| + lookahead = get_token() # Get the next token
|
| + else:
|
| + lookahead = lookaheadstack.pop()
|
| + if not lookahead:
|
| + lookahead = YaccSymbol()
|
| + lookahead.type = "$end"
|
| +
|
| + # --! DEBUG
|
| + debug.debug('Stack : %s',
|
| + ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip())
|
| + # --! DEBUG
|
| +
|
| + # Check the action table
|
| + ltype = lookahead.type
|
| + t = actions[state].get(ltype)
|
| +
|
| + 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:]])+"]",-t)
|
| + else:
|
| + debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, [],-t)
|
| +
|
| + # --! 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:]
|
| + del statestack[-plen:]
|
| + 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)
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1]
|
| + sym.type = 'error'
|
| + lookahead = sym
|
| + errorcount = error_count
|
| + self.errorok = 0
|
| + 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
|
| + 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)
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1]
|
| + sym.type = 'error'
|
| + lookahead = sym
|
| + errorcount = error_count
|
| + self.errorok = 0
|
| + 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 == 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 = 0
|
| + errtoken = lookahead
|
| + if errtoken.type == "$end":
|
| + errtoken = None # End of file!
|
| + if self.errorfunc:
|
| + global errok,token,restart
|
| + errok = self.errok # Set some special functions available in error recovery
|
| + token = get_token
|
| + restart = self.restart
|
| + if errtoken and not hasattr(errtoken,'lexer'):
|
| + errtoken.lexer = lexer
|
| + tok = self.errorfunc(errtoken)
|
| + del errok, token, restart # Delete special functions
|
| +
|
| + 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
|
| + t = YaccSymbol()
|
| + t.type = 'error'
|
| + if hasattr(lookahead,"lineno"):
|
| + t.lineno = lookahead.lineno
|
| + t.value = lookahead
|
| + lookaheadstack.append(lookahead)
|
| + lookahead = t
|
| + else:
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1] # Potential bug fix
|
| +
|
| + continue
|
| +
|
| + # Call an error function here
|
| + raise RuntimeError("yacc: internal parser error!!!\n")
|
| +
|
| + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| + # parseopt().
|
| + #
|
| + # Optimized version of parse() method. DO NOT EDIT THIS CODE DIRECTLY.
|
| + # Edit the debug version above, then copy any modifications to the method
|
| + # below while removing #--! DEBUG sections.
|
| + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| +
|
| +
|
| + def parseopt(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None):
|
| + 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.)
|
| + 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:
|
| + lex = load_ply_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 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 1:
|
| + # 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 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)
|
| +
|
| + 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:]
|
| + del statestack[-plen:]
|
| + 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)
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1]
|
| + sym.type = 'error'
|
| + lookahead = sym
|
| + errorcount = error_count
|
| + self.errorok = 0
|
| + 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
|
| + 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)
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1]
|
| + sym.type = 'error'
|
| + lookahead = sym
|
| + errorcount = error_count
|
| + self.errorok = 0
|
| + continue
|
| + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| +
|
| + if t == 0:
|
| + n = symstack[-1]
|
| + return getattr(n,"value",None)
|
| +
|
| + if t == 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 = 0
|
| + errtoken = lookahead
|
| + if errtoken.type == '$end':
|
| + errtoken = None # End of file!
|
| + if self.errorfunc:
|
| + global errok,token,restart
|
| + errok = self.errok # Set some special functions available in error recovery
|
| + token = get_token
|
| + restart = self.restart
|
| + if errtoken and not hasattr(errtoken,'lexer'):
|
| + errtoken.lexer = lexer
|
| + tok = self.errorfunc(errtoken)
|
| + del errok, token, restart # Delete special functions
|
| +
|
| + 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
|
| + t = YaccSymbol()
|
| + t.type = 'error'
|
| + if hasattr(lookahead,"lineno"):
|
| + t.lineno = lookahead.lineno
|
| + t.value = lookahead
|
| + lookaheadstack.append(lookahead)
|
| + lookahead = t
|
| + else:
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1] # Potential bug fix
|
| +
|
| + continue
|
| +
|
| + # Call an error function here
|
| + raise RuntimeError("yacc: internal parser error!!!\n")
|
| +
|
| + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| + # parseopt_notrack().
|
| + #
|
| + # Optimized version of parseopt() with line number tracking removed.
|
| + # DO NOT EDIT THIS CODE DIRECTLY. Copy the optimized version and remove
|
| + # code in the #--! TRACKING sections
|
| + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| +
|
| + def parseopt_notrack(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None):
|
| + 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.)
|
| + 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:
|
| + lex = load_ply_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 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 1:
|
| + # 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 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)
|
| +
|
| + 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:]
|
| + del statestack[-plen:]
|
| + 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)
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1]
|
| + sym.type = 'error'
|
| + lookahead = sym
|
| + errorcount = error_count
|
| + self.errorok = 0
|
| + 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
|
| + 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)
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1]
|
| + sym.type = 'error'
|
| + lookahead = sym
|
| + errorcount = error_count
|
| + self.errorok = 0
|
| + continue
|
| + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| +
|
| + if t == 0:
|
| + n = symstack[-1]
|
| + return getattr(n,"value",None)
|
| +
|
| + if t == 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 = 0
|
| + errtoken = lookahead
|
| + if errtoken.type == '$end':
|
| + errtoken = None # End of file!
|
| + if self.errorfunc:
|
| + global errok,token,restart
|
| + errok = self.errok # Set some special functions available in error recovery
|
| + token = get_token
|
| + restart = self.restart
|
| + if errtoken and not hasattr(errtoken,'lexer'):
|
| + errtoken.lexer = lexer
|
| + tok = self.errorfunc(errtoken)
|
| + del errok, token, restart # Delete special functions
|
| +
|
| + 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
|
| + t = YaccSymbol()
|
| + t.type = 'error'
|
| + if hasattr(lookahead,"lineno"):
|
| + t.lineno = lookahead.lineno
|
| + t.value = lookahead
|
| + lookaheadstack.append(lookahead)
|
| + lookahead = t
|
| + else:
|
| + symstack.pop()
|
| + statestack.pop()
|
| + state = statestack[-1] # Potential bug fix
|
| +
|
| + continue
|
| +
|
| + # Call an error function here
|
| + raise RuntimeError("yacc: internal parser error!!!\n")
|
| +
|
| +# -----------------------------------------------------------------------------
|
| +# === Grammar Representation ===
|
| +#
|
| +# The following functions, classes, and variables are used to represent and
|
| +# manipulate the rules that make up a grammar.
|
| +# -----------------------------------------------------------------------------
|
| +
|
| +import re
|
| +
|
| +# 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):
|
| + 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. Hack. Remove later
|
| + 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)+")"
|
| +
|
| + def __len__(self):
|
| + return len(self.prod)
|
| +
|
| + def __getitem__(self,index):
|
| + return self.prod[index]
|
| +
|
| +# -----------------------------------------------------------------------------
|
| +# 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 = { } # 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 '%s'" % 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 '%s'. Already defined as a token" % (file,line,prodname))
|
| + if prodname == 'error':
|
| + raise GrammarError("%s:%d: Illegal rule name '%s'. error is a reserved word" % (file,line,prodname))
|
| + if not _is_identifier.match(prodname):
|
| + raise GrammarError("%s:%d: Illegal rule name '%s'" % (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 '%s' may only be a single character" % (file,line,s, prodname))
|
| + if not c 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 '%s' in rule '%s'" % (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,None)
|
| + if not prodprec:
|
| + raise GrammarError("%s:%d: Nothing known about the precedence of '%s'" % (file,line,precname))
|
| + else:
|
| + self.UsedPrecedence[precname] = 1
|
| + 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" % (file,line, m.file, m.line))
|
| +
|
| + # From this point on, everything is valid. Create a new Production instance
|
| + pnumber = len(self.Productions)
|
| + if not prodname 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 not t 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 ]
|
| + return 0
|
| +
|
| + # -----------------------------------------------------------------------------
|
| + # 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 reachable[s]:
|
| + # We've already reached symbol s.
|
| + return
|
| + reachable[s] = 1
|
| + for p in self.Prodnames.get(s,[]):
|
| + for r in p.prod:
|
| + mark_reachable_from(r)
|
| +
|
| + reachable = { }
|
| + for s in list(self.Terminals) + list(self.Nonterminals):
|
| + reachable[s] = 0
|
| +
|
| + mark_reachable_from( self.Productions[0].prod[0] )
|
| +
|
| + return [s for s in list(self.Nonterminals)
|
| + if not reachable[s]]
|
| +
|
| + # -----------------------------------------------------------------------------
|
| + # 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] = 1
|
| +
|
| + terminates['$end'] = 1
|
| +
|
| + # Nonterminals:
|
| +
|
| + # Initialize to false:
|
| + for n in self.Nonterminals:
|
| + terminates[n] = 0
|
| +
|
| + # Then propagate termination until no change:
|
| + while 1:
|
| + some_change = 0
|
| + 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 = 0
|
| + break
|
| + else:
|
| + # didn't break from the loop,
|
| + # so every symbol s terminates
|
| + # so production p terminates.
|
| + p_terminates = 1
|
| +
|
| + if p_terminates:
|
| + # symbol n terminates!
|
| + if not terminates[n]:
|
| + terminates[n] = 1
|
| + some_change = 1
|
| + # 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 not s in self.Prodnames and not s 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 not s in self.Prodnames and not s 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 = 0
|
| +
|
| + # Add all the non-<empty> symbols of First[x] to the result.
|
| + for f in self.First[x]:
|
| + if f == '<empty>':
|
| + x_produces_empty = 1
|
| + 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 1:
|
| + some_change = 0
|
| + 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 = 1
|
| + 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 1:
|
| + didadd = 0
|
| + for p in self.Productions[1:]:
|
| + # Here is the production set
|
| + for i in range(len(p.prod)):
|
| + B = p.prod[i]
|
| + if B in self.Nonterminals:
|
| + # Okay. We got a non-terminal in a production
|
| + fst = self._first(p.prod[i+1:])
|
| + hasempty = 0
|
| + for f in fst:
|
| + if f != '<empty>' and f not in self.Follow[B]:
|
| + self.Follow[B].append(f)
|
| + didadd = 1
|
| + if f == '<empty>':
|
| + hasempty = 1
|
| + 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 = 1
|
| + 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 1:
|
| + 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:
|
| + if sys.version_info[0] < 3:
|
| + exec("import %s as parsetab" % module)
|
| + else:
|
| + env = { }
|
| + exec("import %s as parsetab" % module, env, env)
|
| + parsetab = env['parsetab']
|
| +
|
| + 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
|
| +
|
| + # 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 = 1
|
| + while didadd:
|
| + didadd = 0
|
| + 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 = 1
|
| +
|
| + 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),None)
|
| + 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,None)
|
| + 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),None)
|
| + if not s1:
|
| + s1 = { }
|
| + s[id(n)] = s1
|
| + gs.append(n)
|
| + s = s1
|
| + g = s.get('$end',None)
|
| + 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: continue
|
| + if 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 = {}
|
| + num_nullable = 0
|
| + while 1:
|
| + for p in self.grammar.Productions[1:]:
|
| + if p.len == 0:
|
| + nullable[p.name] = 1
|
| + continue
|
| + for t in p.prod:
|
| + if not t in nullable: break
|
| + else:
|
| + nullable[p.name] = 1
|
| + 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 state in range(len(C)):
|
| + for p in C[state]:
|
| + if p.lr_index < p.len - 1:
|
| + t = (state,p.prod[p.lr_index+1])
|
| + if t[1] in self.grammar.Nonterminals:
|
| + if t not in trans: trans.append(t)
|
| + state = state + 1
|
| + 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 not p.prod[li] 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 not i 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 not state 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):
|
| + 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, str(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,None)
|
| + 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.
|
| + sprec,slevel = self.grammar.Productions[st_actionp[a].number].prec
|
| + rprec,rlevel = self.grammar.Precedence.get(a,('right',0))
|
| + 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'))
|
| + 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 = self.grammar.Productions[-r]
|
| + pp = self.grammar.Productions[p.number]
|
| + if oldp.line > pp.line:
|
| + st_action[a] = -p.number
|
| + st_actionp[a] = p
|
| + chosenp,rejectp = pp,oldp
|
| + 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
|
| + 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,None)
|
| + 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
|
| + rprec,rlevel = self.grammar.Productions[st_actionp[a].number].prec
|
| + sprec,slevel = self.grammar.Precedence.get(a,('right',0))
|
| + if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')):
|
| + # We decide to shift here... highest precedence to shift
|
| + 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,modulename,outputdir='',signature=""):
|
| + basemodulename = modulename.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
|
| + """ % (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,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:
|
| + e = sys.exc_info()[1]
|
| + sys.stderr.write("Unable to create '%s'\n" % filename)
|
| + sys.stderr.write(str(e)+"\n")
|
| + return
|
| +
|
| +
|
| +# -----------------------------------------------------------------------------
|
| +# === 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):
|
| + try:
|
| + raise RuntimeError
|
| + except RuntimeError:
|
| + e,b,t = sys.exc_info()
|
| + f = t.tb_frame
|
| + while levels > 0:
|
| + f = f.f_back
|
| + levels -= 1
|
| + 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 '%s'" % (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.files = {}
|
| + self.grammar = []
|
| + self.error = 0
|
| +
|
| + 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_files()
|
| + return self.error
|
| +
|
| + # Compute a signature over the grammar
|
| + def signature(self):
|
| + from binascii import crc32
|
| + sig = 0
|
| + try:
|
| + if self.start:
|
| + sig = crc32(self.start.encode('latin-1'),sig)
|
| + if self.prec:
|
| + sig = crc32("".join(["".join(p) for p in self.prec]).encode('latin-1'),sig)
|
| + if self.tokens:
|
| + sig = crc32(" ".join(self.tokens).encode('latin-1'),sig)
|
| + for f in self.pfuncs:
|
| + if f[3]:
|
| + sig = crc32(f[3].encode('latin-1'),sig)
|
| + except (TypeError,ValueError):
|
| + pass
|
| + return sig
|
| +
|
| + # -----------------------------------------------------------------------------
|
| + # validate_file()
|
| + #
|
| + # 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_files(self):
|
| + # Match def p_funcname(
|
| + fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(')
|
| +
|
| + for filename in self.files.keys():
|
| + base,ext = os.path.splitext(filename)
|
| + if ext != '.py': return 1 # No idea. Assume it's okay.
|
| +
|
| + try:
|
| + f = open(filename)
|
| + lines = f.readlines()
|
| + f.close()
|
| + except IOError:
|
| + continue
|
| +
|
| + counthash = { }
|
| + for linen,l in enumerate(lines):
|
| + linen += 1
|
| + m = fre.match(l)
|
| + if m:
|
| + name = m.group(1)
|
| + prev = counthash.get(name)
|
| + if not prev:
|
| + counthash[name] = linen
|
| + else:
|
| + 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,str):
|
| + 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 = 1
|
| + return
|
| +
|
| + eline = func_code(self.error_func).co_firstlineno
|
| + efile = func_code(self.error_func).co_filename
|
| + self.files[efile] = 1
|
| +
|
| + if (func_code(self.error_func).co_argcount != 1+ismethod):
|
| + self.log.error("%s:%d: p_error() requires 1 argument",efile,eline)
|
| + self.error = 1
|
| +
|
| + # Get the tokens map
|
| + def get_tokens(self):
|
| + tokens = self.pdict.get("tokens",None)
|
| + if not tokens:
|
| + self.log.error("No token list is defined")
|
| + self.error = 1
|
| + return
|
| +
|
| + if not isinstance(tokens,(list, tuple)):
|
| + self.log.error("tokens must be a list or tuple")
|
| + self.error = 1
|
| + return
|
| +
|
| + if not tokens:
|
| + self.log.error("tokens is empty")
|
| + self.error = 1
|
| + 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 = 1
|
| + return
|
| +
|
| + terminals = {}
|
| + for n in self.tokens:
|
| + if n in terminals:
|
| + self.log.warning("Token '%s' multiply defined", n)
|
| + terminals[n] = 1
|
| +
|
| + # Get the precedence map (if any)
|
| + def get_precedence(self):
|
| + self.prec = self.pdict.get("precedence",None)
|
| +
|
| + # 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 = 1
|
| + return
|
| + for level,p in enumerate(self.prec):
|
| + if not isinstance(p,(list,tuple)):
|
| + self.log.error("Bad precedence table")
|
| + self.error = 1
|
| + return
|
| +
|
| + if len(p) < 2:
|
| + self.log.error("Malformed precedence entry %s. Must be (assoc, term, ..., term)",p)
|
| + self.error = 1
|
| + return
|
| + assoc = p[0]
|
| + if not isinstance(assoc,str):
|
| + self.log.error("precedence associativity must be a string")
|
| + self.error = 1
|
| + return
|
| + for term in p[1:]:
|
| + if not isinstance(term,str):
|
| + self.log.error("precedence items must be strings")
|
| + self.error = 1
|
| + 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 name[:2] != 'p_': continue
|
| + if name == 'p_error': continue
|
| + if isinstance(item,(types.FunctionType,types.MethodType)):
|
| + line = func_code(item).co_firstlineno
|
| + file = func_code(item).co_filename
|
| + p_functions.append((line,file,name,item.__doc__))
|
| +
|
| + # Sort all of the actions by line number
|
| + p_functions.sort()
|
| + 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 = 1
|
| + return
|
| +
|
| + for line, file, name, doc in self.pfuncs:
|
| + func = self.pdict[name]
|
| + if isinstance(func, types.MethodType):
|
| + reqargs = 2
|
| + else:
|
| + reqargs = 1
|
| + if func_code(func).co_argcount > reqargs:
|
| + self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,func.__name__)
|
| + self.error = 1
|
| + elif func_code(func).co_argcount < reqargs:
|
| + self.log.error("%s:%d: Rule '%s' requires an argument",file,line,func.__name__)
|
| + self.error = 1
|
| + elif not func.__doc__:
|
| + self.log.warning("%s:%d: No documentation string specified in function '%s' (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:
|
| + e = sys.exc_info()[1]
|
| + self.log.error(str(e))
|
| + self.error = 1
|
| +
|
| + # Looks like a valid grammar rule
|
| + # Mark the file in which defined.
|
| + self.files[file] = 1
|
| +
|
| + # 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[0:2] == 'p_' and isinstance(v, (types.FunctionType, types.MethodType)): continue
|
| + if n[0:2] == 't_': continue
|
| + if n[0:2] == 'p_' and n != 'p_error':
|
| + self.log.warning("'%s' not defined as a function", n)
|
| + if ((isinstance(v,types.FunctionType) and func_code(v).co_argcount == 1) or
|
| + (isinstance(v,types.MethodType) and func_code(v).co_argcount == 2)):
|
| + try:
|
| + doc = v.__doc__.split(" ")
|
| + if doc[1] == ':':
|
| + self.log.warning("%s:%d: Possible grammar rule '%s' defined without p_ prefix",
|
| + func_code(v).co_filename, func_code(v).co_firstlineno,n)
|
| + except Exception:
|
| + pass
|
| +
|
| + self.grammar = grammar
|
| +
|
| +# -----------------------------------------------------------------------------
|
| +# yacc(module)
|
| +#
|
| +# Build a parser
|
| +# -----------------------------------------------------------------------------
|
| +
|
| +def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None,
|
| + check_recursion=1, optimize=0, write_tables=1, debugfile=debug_file,outputdir='',
|
| + debuglog=None, errorlog = None):
|
| +
|
| + global parse # Reference to the parsing method of the last built parser
|
| +
|
| + 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)
|
| + else:
|
| + pdict = get_caller_module_dict(2)
|
| +
|
| + # 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()
|
| + 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:
|
| + e = sys.exc_info()[1]
|
| + errorlog.warning("There was a problem loading the table file: %s", repr(e))
|
| + except VersionError:
|
| + e = sys.exc_info()
|
| + errorlog.warning(str(e))
|
| + except Exception:
|
| + pass
|
| +
|
| + if debuglog is None:
|
| + if debug:
|
| + debuglog = PlyLogger(open(debugfile,"w"))
|
| + else:
|
| + debuglog = NullLogger()
|
| +
|
| + debuglog.info("Created by PLY version %s (http://www.dabeaz.com/ply)", __version__)
|
| +
|
| +
|
| + errors = 0
|
| +
|
| + # 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:
|
| + e = sys.exc_info()[1]
|
| + errorlog.warning("%s",str(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:
|
| + e = sys.exc_info()[1]
|
| + errorlog.error("%s",str(e))
|
| + errors = 1
|
| +
|
| + # Set the grammar start symbols
|
| + try:
|
| + if start is None:
|
| + grammar.set_start(pinfo.start)
|
| + else:
|
| + grammar.set_start(start)
|
| + except GrammarError:
|
| + e = sys.exc_info()[1]
|
| + errorlog.error(str(e))
|
| + errors = 1
|
| +
|
| + 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 '%s' used, but not defined as a token or a rule",prod.file,prod.line,sym)
|
| + errors = 1
|
| +
|
| + unused_terminals = grammar.unused_terminals()
|
| + if unused_terminals:
|
| + debuglog.info("")
|
| + debuglog.info("Unused terminals:")
|
| + debuglog.info("")
|
| + for term in unused_terminals:
|
| + errorlog.warning("Token '%s' 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+1, p)
|
| +
|
| + # Find unused non-terminals
|
| + unused_rules = grammar.unused_rules()
|
| + for prod in unused_rules:
|
| + errorlog.warning("%s:%d: Rule '%s' 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 '%s' is unreachable",u)
|
| +
|
| + infinite = grammar.infinite_cycles()
|
| + for inf in infinite:
|
| + errorlog.error("Infinite recursion detected for symbol '%s'", inf)
|
| + errors = 1
|
| +
|
| + unused_prec = grammar.unused_precedence()
|
| + for term, assoc in unused_prec:
|
| + errorlog.error("Precedence rule '%s' defined for unknown symbol '%s'", assoc, term)
|
| + errors = 1
|
| +
|
| + 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)
|
| +
|
| + for state, rule, rejected in lr.rr_conflicts:
|
| + debuglog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule)
|
| + debuglog.warning("rejected rule (%s)", rejected)
|
| + errorlog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule)
|
| + errorlog.warning("rejected rule (%s)", rejected)
|
| +
|
| + # Write the table file if requested
|
| + if write_tables:
|
| + lr.write_table(tabmodule,outputdir,signature)
|
| +
|
| + # Build the parser
|
| + lr.bind_callables(pinfo.pdict)
|
| + parser = LRParser(lr,pinfo.error_func)
|
| +
|
| + parse = parser.parse
|
| + return parser
|
|
|
| Property changes on: tools/nixysa/third_party/ply-3.1/ply/yacc.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|