ports/145450: [ERROR] cannot compile www/firefox version 3.6.3

Andrei V. Lavreniyuk andy.lavr at reactor-xg.kiev.ua
Wed Apr 7 09:11:37 UTC 2010


07.04.2010 11:26, Florian Smeets пишет:

>>   show me the output of python -V and pkg_info|grep python?
>>
>
> Sorry, there is a "can you please" missing. Did not want to sound as
> harsh as it seems.
>
> So, can you please show me the output? :-)


:)



Workaround:


# portupgrade -f boost-python-libs-1.41.0 py26-notify-0.1.1_7 python26-2.6.4

+

Attached patch.





-- 
  Best regards, Andrei V. Lavreniyuk.

-------------- next part --------------
--- xpcom/idl-parser/header.py.orig	2010-04-02 19:03:13.000000000 +0300
+++ xpcom/idl-parser/header.py	2010-04-07 11:23:45.088650024 +0300
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/local/bin/python
 # header.py - Generate C++ header files from IDL.
 #
 # ***** BEGIN LICENSE BLOCK *****
-------------- next part --------------
diff -ruN ply.orig/lex.py ply/lex.py
--- other-licenses/ply/ply.orig/lex.py	2010-04-02 19:02:56.000000000 +0300
+++ other-licenses/ply/ply/lex.py	2009-08-27 04:28:42.000000000 +0300
@@ -1,45 +1,61 @@
 # -----------------------------------------------------------------------------
 # ply: lex.py
 #
-# Author: David M. Beazley (dave at dabeaz.com)
+# Copyright (C) 2001-2009,
+# David M. Beazley (Dabeaz LLC)
+# All rights reserved.
 #
-# Copyright (C) 2001-2008, David M. Beazley
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# 
+# * Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.  
+# * Redistributions in binary form must reproduce the above copyright notice, 
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.  
+# * Neither the name of the David Beazley or Dabeaz LLC may be used to
+#   endorse or promote products derived from this software without
+#  specific prior written permission. 
 #
-# This 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 SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 # -----------------------------------------------------------------------------
 
-__version__    = "2.5"
-__tabversion__ = "2.4"       # Version of table file used
+__version__    = "3.3"
+__tabversion__ = "3.2"       # Version of table file used
 
 import re, sys, types, copy, os
 
-# This regular expression is used to match valid token names
-_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$')
-
-# _INSTANCETYPE sets the valid set of instance types recognized
-# by PLY when lexers are defined by a class. In order to maintain
-# backwards compatibility with Python-2.0, we have to check for
-# the existence of ObjectType.
-
+# This tuple contains known string types
 try:
-    _INSTANCETYPE = (types.InstanceType, types.ObjectType)
+    # Python 2.6
+    StringTypes = (types.StringType, types.UnicodeType)
 except AttributeError:
-    _INSTANCETYPE = types.InstanceType
-    class object: pass       # Note: needed if no new-style classes present
+    # Python 3.0
+    StringTypes = (str, bytes)
+
+# Extract the code attribute of a function. Different implementations
+# are for Python 2/3 compatibility.
+
+if sys.version_info[0] < 3:
+    def func_code(f):
+        return f.func_code
+else:
+    def func_code(f):
+        return f.__code__
+
+# This regular expression is used to match valid token names
+_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$')
 
 # Exception thrown when invalid token encountered and no default error
 # handler is defined.
@@ -49,35 +65,50 @@
          self.args = (message,)
          self.text = s
 
-# An object used to issue one-time warning messages for various features
-
-class LexWarning(object):
-   def __init__(self):
-      self.warned = 0
-   def __call__(self,msg):
-      if not self.warned:
-         sys.stderr.write("ply.lex: Warning: " + msg+"\n")
-         self.warned = 1
-
-_SkipWarning = LexWarning()         # Warning for use of t.skip() on tokens
-
 # Token class.  This class is used to represent the tokens produced.
 class LexToken(object):
     def __str__(self):
         return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos)
     def __repr__(self):
         return str(self)
-    def skip(self,n):
-        self.lexer.skip(n)
-        _SkipWarning("Calling t.skip() on a token is deprecated.  Please use t.lexer.skip()")
+
+# This object is a stand-in for a logging object created by the 
+# logging module.  
+
+class PlyLogger(object):
+    def __init__(self,f):
+        self.f = f
+    def critical(self,msg,*args,**kwargs):
+        self.f.write((msg % args) + "\n")
+
+    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")
+
+    info = critical
+    debug = critical
+
+# 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
 
 # -----------------------------------------------------------------------------
-# Lexer class
+#                        === Lexing Engine ===
 #
-# This class encapsulates all of the methods and data associated with a lexer.
+# The following Lexer class implements the lexer runtime.   There are only
+# a few public methods and attributes:
 #
 #    input()          -  Store a new string in the lexer
 #    token()          -  Get the next token
+#    clone()          -  Clone the lexer
+#
+#    lineno           -  Current line number
+#    lexpos           -  Current position in the input string
 # -----------------------------------------------------------------------------
 
 class Lexer:
@@ -105,7 +136,6 @@
         self.lexliterals = ""         # Literal characters that can be passed through
         self.lexmodule = None         # Module
         self.lineno = 1               # Current line number
-        self.lexdebug = 0             # Debugging mode
         self.lexoptimize = 0          # Optimized mode
 
     def clone(self,object=None):
@@ -145,6 +175,7 @@
         filename = os.path.join(outputdir,basetabfilename)+".py"
         tf = open(filename,"w")
         tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__))
+        tf.write("_tabversion   = %s\n" % repr(__version__))
         tf.write("_lextokens    = %s\n" % repr(self.lextokens))
         tf.write("_lexreflags   = %s\n" % repr(self.lexreflags))
         tf.write("_lexliterals  = %s\n" % repr(self.lexliterals))
@@ -184,7 +215,16 @@
         if isinstance(tabfile,types.ModuleType):
             lextab = tabfile
         else:
-            exec "import %s as lextab" % tabfile
+            if sys.version_info[0] < 3:
+                exec("import %s as lextab" % tabfile)
+            else:
+                env = { }
+                exec("import %s as lextab" % tabfile, env,env)
+                lextab = env['lextab']
+
+        if getattr(lextab,"_tabversion","0.0") != __version__:
+            raise ImportError("Inconsistent PLY version")
+
         self.lextokens      = lextab._lextokens
         self.lexreflags     = lextab._lexreflags
         self.lexliterals    = lextab._lexliterals
@@ -196,7 +236,7 @@
              titem = []
              txtitem = []
              for i in range(len(lre)):
-                  titem.append((re.compile(lre[i][0],lextab._lexreflags),_names_to_funcs(lre[i][1],fdict)))
+                  titem.append((re.compile(lre[i][0],lextab._lexreflags | re.VERBOSE),_names_to_funcs(lre[i][1],fdict)))
                   txtitem.append(lre[i][0])
              self.lexstatere[key] = titem
              self.lexstateretext[key] = txtitem
@@ -211,8 +251,8 @@
     def input(self,s):
         # Pull off the first character to see if s looks like a string
         c = s[:1]
-        if not (isinstance(c,types.StringType) or isinstance(c,types.UnicodeType)):
-            raise ValueError, "Expected a string"
+        if not isinstance(c,StringTypes):
+            raise ValueError("Expected a string")
         self.lexdata = s
         self.lexpos = 0
         self.lexlen = len(s)
@@ -221,8 +261,8 @@
     # begin() - Changes the lexing state
     # ------------------------------------------------------------
     def begin(self,state):
-        if not self.lexstatere.has_key(state):
-            raise ValueError, "Undefined state"
+        if not state in self.lexstatere:
+            raise ValueError("Undefined state")
         self.lexre = self.lexstatere[state]
         self.lexretext = self.lexstateretext[state]
         self.lexignore = self.lexstateignore.get(state,"")
@@ -255,7 +295,7 @@
         self.lexpos += n
 
     # ------------------------------------------------------------
-    # token() - Return the next token from the Lexer
+    # opttoken() - Return the next token from the Lexer
     #
     # Note: This function has been carefully implemented to be as fast
     # as possible.  Don't make changes unless you really know what
@@ -299,10 +339,6 @@
 
                 lexpos = m.end()
 
-                # if func not callable, it means it's an ignored token
-                if not callable(func):
-                   break
-
                 # If token is processed by a function, call it
 
                 tok.lexer = self      # Set additional attributes useful in token rules
@@ -319,9 +355,9 @@
 
                 # Verify type of the token.  If not in the token map, raise an error
                 if not self.lexoptimize:
-                    if not self.lextokens.has_key(newtok.type):
-                        raise LexError, ("%s:%d: Rule '%s' returned an unknown token type '%s'" % (
-                            func.func_code.co_filename, func.func_code.co_firstlineno,
+                    if not newtok.type in self.lextokens:
+                        raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % (
+                            func_code(func).co_filename, func_code(func).co_firstlineno,
                             func.__name__, newtok.type),lexdata[lexpos:])
 
                 return newtok
@@ -348,60 +384,60 @@
                     newtok = self.lexerrorf(tok)
                     if lexpos == self.lexpos:
                         # Error method didn't change text position at all. This is an error.
-                        raise LexError, ("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:])
+                        raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:])
                     lexpos = self.lexpos
                     if not newtok: continue
                     return newtok
 
                 self.lexpos = lexpos
-                raise LexError, ("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:])
+                raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:])
 
         self.lexpos = lexpos + 1
         if self.lexdata is None:
-             raise RuntimeError, "No input string given with input()"
+             raise RuntimeError("No input string given with input()")
         return None
 
+    # Iterator interface
+    def __iter__(self):
+        return self
+
+    def next(self):
+        t = self.token()
+        if t is None:
+            raise StopIteration
+        return t
+
+    __next__ = next
+
 # -----------------------------------------------------------------------------
-# _validate_file()
+#                           ==== Lex Builder ===
 #
-# This checks to see if there are duplicated t_rulename() functions or strings
-# in the parser input file.  This is done using a simple regular expression
-# match on each line in the given file.  If the file can't be located or opened,
-# a true result is returned by default.
+# The functions and classes below are used to collect lexing information
+# and build a Lexer object from it.
 # -----------------------------------------------------------------------------
 
-def _validate_file(filename):
-    import os.path
-    base,ext = os.path.splitext(filename)
-    if ext != '.py': return 1        # No idea what the file is. Return OK
+# -----------------------------------------------------------------------------
+# 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:
-        f = open(filename)
-        lines = f.readlines()
-        f.close()
-    except IOError:
-        return 1                     # Couldn't find the file.  Don't worry about it
-
-    fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(')
-    sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=')
-
-    counthash = { }
-    linen = 1
-    noerror = 1
-    for l in lines:
-        m = fre.match(l)
-        if not m:
-            m = sre.match(l)
-        if m:
-            name = m.group(1)
-            prev = counthash.get(name)
-            if not prev:
-                counthash[name] = linen
-            else:
-                print >>sys.stderr, "%s:%d: Rule %s redefined. Previously defined on line %d" % (filename,linen,name,prev)
-                noerror = 0
-        linen += 1
-    return noerror
+        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
 
 # -----------------------------------------------------------------------------
 # _funcs_to_names()
@@ -466,7 +502,7 @@
                     lexindexfunc[i] = (None, toknames[f])
         
         return [(lexre,lexindexfunc)],[regex],[lexindexnames]
-    except Exception,e:
+    except Exception:
         m = int(len(relist)/2)
         if m == 0: m = 1
         llist, lre, lnames = _form_master_re(relist[:m],reflags,ldict,toknames)
@@ -486,319 +522,446 @@
     nonstate = 1
     parts = s.split("_")
     for i in range(1,len(parts)):
-         if not names.has_key(parts[i]) and parts[i] != 'ANY': break
+         if not parts[i] in names and parts[i] != 'ANY': break
     if i > 1:
        states = tuple(parts[1:i])
     else:
        states = ('INITIAL',)
 
     if 'ANY' in states:
-       states = tuple(names.keys())
+       states = tuple(names)
 
     tokenname = "_".join(parts[i:])
     return (states,tokenname)
 
+
 # -----------------------------------------------------------------------------
-# lex(module)
+# LexerReflect()
 #
-# Build all of the regular expression rules from definitions in the supplied module
+# This class represents information needed to build a lexer as extracted from a
+# user's input file.
 # -----------------------------------------------------------------------------
-def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0,outputdir=""):
-    global lexer
-    ldict = None
-    stateinfo  = { 'INITIAL' : 'inclusive'}
-    error = 0
-    files = { }
-    lexobj = Lexer()
-    lexobj.lexdebug = debug
-    lexobj.lexoptimize = optimize
-    global token,input
+class LexerReflect(object):
+    def __init__(self,ldict,log=None,reflags=0):
+        self.ldict      = ldict
+        self.error_func = None
+        self.tokens     = []
+        self.reflags    = reflags
+        self.stateinfo  = { 'INITIAL' : 'inclusive'}
+        self.files      = {}
+        self.error      = 0
 
-    if nowarn: warn = 0
-    else: warn = 1
+        if log is None:
+            self.log = PlyLogger(sys.stderr)
+        else:
+            self.log = log
 
-    if object: module = object
+    # Get all of the basic information
+    def get_all(self):
+        self.get_tokens()
+        self.get_literals()
+        self.get_states()
+        self.get_rules()
+        
+    # Validate all of the information
+    def validate_all(self):
+        self.validate_tokens()
+        self.validate_literals()
+        self.validate_rules()
+        return self.error
+
+    # Get the tokens map
+    def get_tokens(self):
+        tokens = self.ldict.get("tokens",None)
+        if not tokens:
+            self.log.error("No token list is defined")
+            self.error = 1
+            return
 
-    if module:
-        # User supplied a module object.
-        if isinstance(module, types.ModuleType):
-            ldict = module.__dict__
-        elif isinstance(module, _INSTANCETYPE):
-            _items = [(k,getattr(module,k)) for k in dir(module)]
-            ldict = { }
-            for (i,v) in _items:
-                ldict[i] = v
-        else:
-            raise ValueError,"Expected a module or instance"
-        lexobj.lexmodule = module
+        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
 
-    else:
-        # No module given.  We might be able to get information from the caller.
-        try:
-            raise RuntimeError
-        except RuntimeError:
-            e,b,t = sys.exc_info()
-            f = t.tb_frame
-            f = f.f_back                    # Walk out to our calling function
-            if f.f_globals is f.f_locals:   # Collect global and local variations from caller
-               ldict = f.f_globals
-            else:
-               ldict = f.f_globals.copy()
-               ldict.update(f.f_locals)
+        self.tokens = tokens
 
-    if optimize and lextab:
+    # Validate the tokens
+    def validate_tokens(self):
+        terminals = {}
+        for n in self.tokens:
+            if not _is_identifier.match(n):
+                self.log.error("Bad token name '%s'",n)
+                self.error = 1
+            if n in terminals:
+                self.log.warning("Token '%s' multiply defined", n)
+            terminals[n] = 1
+
+    # Get the literals specifier
+    def get_literals(self):
+        self.literals = self.ldict.get("literals","")
+
+    # Validate literals
+    def validate_literals(self):
         try:
-            lexobj.readtab(lextab,ldict)
-            token = lexobj.token
-            input = lexobj.input
-            lexer = lexobj
-            return lexobj
+            for c in self.literals:
+                if not isinstance(c,StringTypes) or len(c) > 1:
+                    self.log.error("Invalid literal %s. Must be a single character", repr(c))
+                    self.error = 1
+                    continue
 
-        except ImportError:
-            pass
+        except TypeError:
+            self.log.error("Invalid literals specification. literals must be a sequence of characters")
+            self.error = 1
+
+    def get_states(self):
+        self.states = self.ldict.get("states",None)
+        # Build statemap
+        if self.states:
+             if not isinstance(self.states,(tuple,list)):
+                  self.log.error("states must be defined as a tuple or list")
+                  self.error = 1
+             else:
+                  for s in self.states:
+                        if not isinstance(s,tuple) or len(s) != 2:
+                               self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')",repr(s))
+                               self.error = 1
+                               continue
+                        name, statetype = s
+                        if not isinstance(name,StringTypes):
+                               self.log.error("State name %s must be a string", repr(name))
+                               self.error = 1
+                               continue
+                        if not (statetype == 'inclusive' or statetype == 'exclusive'):
+                               self.log.error("State type for state %s must be 'inclusive' or 'exclusive'",name)
+                               self.error = 1
+                               continue
+                        if name in self.stateinfo:
+                               self.log.error("State '%s' already defined",name)
+                               self.error = 1
+                               continue
+                        self.stateinfo[name] = statetype
+
+    # Get all of the symbols with a t_ prefix and sort them into various
+    # categories (functions, strings, error functions, and ignore characters)
+
+    def get_rules(self):
+        tsymbols = [f for f in self.ldict if f[:2] == 't_' ]
+
+        # Now build up a list of functions and a list of strings
+
+        self.toknames = { }        # Mapping of symbols to token names
+        self.funcsym =  { }        # Symbols defined as functions
+        self.strsym =   { }        # Symbols defined as strings
+        self.ignore   = { }        # Ignore strings by state
+        self.errorf   = { }        # Error functions by state
+
+        for s in self.stateinfo:
+             self.funcsym[s] = []
+             self.strsym[s] = []
+
+        if len(tsymbols) == 0:
+            self.log.error("No rules of the form t_rulename are defined")
+            self.error = 1
+            return
 
-    # Get the tokens, states, and literals variables (if any)
+        for f in tsymbols:
+            t = self.ldict[f]
+            states, tokname = _statetoken(f,self.stateinfo)
+            self.toknames[f] = tokname
 
-    tokens = ldict.get("tokens",None)
-    states = ldict.get("states",None)
-    literals = ldict.get("literals","")
+            if hasattr(t,"__call__"):
+                if tokname == 'error':
+                    for s in states:
+                        self.errorf[s] = t
+                elif tokname == 'ignore':
+                    line = func_code(t).co_firstlineno
+                    file = func_code(t).co_filename
+                    self.log.error("%s:%d: Rule '%s' must be defined as a string",file,line,t.__name__)
+                    self.error = 1
+                else:
+                    for s in states: 
+                        self.funcsym[s].append((f,t))
+            elif isinstance(t, StringTypes):
+                if tokname == 'ignore':
+                    for s in states:
+                        self.ignore[s] = t
+                    if "\\" in t:
+                        self.log.warning("%s contains a literal backslash '\\'",f)
+
+                elif tokname == 'error':
+                    self.log.error("Rule '%s' must be defined as a function", f)
+                    self.error = 1
+                else:
+                    for s in states: 
+                        self.strsym[s].append((f,t))
+            else:
+                self.log.error("%s not defined as a function or string", f)
+                self.error = 1
 
-    if not tokens:
-        raise SyntaxError,"lex: module does not define 'tokens'"
+        # Sort the functions by line number
+        for f in self.funcsym.values():
+            if sys.version_info[0] < 3:
+                f.sort(lambda x,y: cmp(func_code(x[1]).co_firstlineno,func_code(y[1]).co_firstlineno))
+            else:
+                # Python 3.0
+                f.sort(key=lambda x: func_code(x[1]).co_firstlineno)
 
-    if not (isinstance(tokens,types.ListType) or isinstance(tokens,types.TupleType)):
-        raise SyntaxError,"lex: tokens must be a list or tuple."
+        # Sort the strings by regular expression length
+        for s in self.strsym.values():
+            if sys.version_info[0] < 3:
+                s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1])))
+            else:
+                # Python 3.0
+                s.sort(key=lambda x: len(x[1]),reverse=True)
 
-    # Build a dictionary of valid token names
-    lexobj.lextokens = { }
-    if not optimize:
-        for n in tokens:
-            if not _is_identifier.match(n):
-                print >>sys.stderr, "lex: Bad token name '%s'" % n
-                error = 1
-            if warn and lexobj.lextokens.has_key(n):
-                print >>sys.stderr, "lex: Warning. Token '%s' multiply defined." % n
-            lexobj.lextokens[n] = None
-    else:
-        for n in tokens: lexobj.lextokens[n] = None
+    # Validate all of the t_rules collected 
+    def validate_rules(self):
+        for state in self.stateinfo:
+            # Validate all rules defined by functions
+
+            
+
+            for fname, f in self.funcsym[state]:
+                line = func_code(f).co_firstlineno
+                file = func_code(f).co_filename
+                self.files[file] = 1
 
-    if debug:
-        print "lex: tokens = '%s'" % lexobj.lextokens.keys()
+                tokname = self.toknames[fname]
+                if isinstance(f, types.MethodType):
+                    reqargs = 2
+                else:
+                    reqargs = 1
+                nargs = func_code(f).co_argcount
+                if nargs > reqargs:
+                    self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__)
+                    self.error = 1
+                    continue
 
-    try:
-         for c in literals:
-               if not (isinstance(c,types.StringType) or isinstance(c,types.UnicodeType)) or len(c) > 1:
-                    print >>sys.stderr, "lex: Invalid literal %s. Must be a single character" % repr(c)
-                    error = 1
+                if nargs < reqargs:
+                    self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__)
+                    self.error = 1
                     continue
 
-    except TypeError:
-         print >>sys.stderr, "lex: Invalid literals specification. literals must be a sequence of characters."
-         error = 1
-
-    lexobj.lexliterals = literals
-
-    # Build statemap
-    if states:
-         if not (isinstance(states,types.TupleType) or isinstance(states,types.ListType)):
-              print >>sys.stderr, "lex: states must be defined as a tuple or list."
-              error = 1
-         else:
-              for s in states:
-                    if not isinstance(s,types.TupleType) or len(s) != 2:
-                           print >>sys.stderr, "lex: invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')" % repr(s)
-                           error = 1
-                           continue
-                    name, statetype = s
-                    if not isinstance(name,types.StringType):
-                           print >>sys.stderr, "lex: state name %s must be a string" % repr(name)
-                           error = 1
-                           continue
-                    if not (statetype == 'inclusive' or statetype == 'exclusive'):
-                           print >>sys.stderr, "lex: state type for state %s must be 'inclusive' or 'exclusive'" % name
-                           error = 1
-                           continue
-                    if stateinfo.has_key(name):
-                           print >>sys.stderr, "lex: state '%s' already defined." % name
-                           error = 1
-                           continue
-                    stateinfo[name] = statetype
-
-    # Get a list of symbols with the t_ or s_ prefix
-    tsymbols = [f for f in ldict.keys() if f[:2] == 't_' ]
-
-    # Now build up a list of functions and a list of strings
-
-    funcsym =  { }        # Symbols defined as functions
-    strsym =   { }        # Symbols defined as strings
-    toknames = { }        # Mapping of symbols to token names
-
-    for s in stateinfo.keys():
-         funcsym[s] = []
-         strsym[s] = []
-
-    ignore   = { }        # Ignore strings by state
-    errorf   = { }        # Error functions by state
-
-    if len(tsymbols) == 0:
-        raise SyntaxError,"lex: no rules of the form t_rulename are defined."
-
-    for f in tsymbols:
-        t = ldict[f]
-        states, tokname = _statetoken(f,stateinfo)
-        toknames[f] = tokname
-
-        if callable(t):
-            for s in states: funcsym[s].append((f,t))
-        elif (isinstance(t, types.StringType) or isinstance(t,types.UnicodeType)):
-            for s in states: strsym[s].append((f,t))
-        else:
-            print >>sys.stderr, "lex: %s not defined as a function or string" % f
-            error = 1
+                if not f.__doc__:
+                    self.log.error("%s:%d: No regular expression defined for rule '%s'",file,line,f.__name__)
+                    self.error = 1
+                    continue
 
-    # Sort the functions by line number
-    for f in funcsym.values():
-        f.sort(lambda x,y: cmp(x[1].func_code.co_firstlineno,y[1].func_code.co_firstlineno))
-
-    # Sort the strings by regular expression length
-    for s in strsym.values():
-        s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1])))
+                try:
+                    c = re.compile("(?P<%s>%s)" % (fname,f.__doc__), re.VERBOSE | self.reflags)
+                    if c.match(""):
+                        self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file,line,f.__name__)
+                        self.error = 1
+                except re.error:
+                    _etype, e, _etrace = sys.exc_info()
+                    self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file,line,f.__name__,e)
+                    if '#' in f.__doc__:
+                        self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'",file,line, f.__name__)
+                    self.error = 1
+
+            # Validate all rules defined by strings
+            for name,r in self.strsym[state]:
+                tokname = self.toknames[name]
+                if tokname == 'error':
+                    self.log.error("Rule '%s' must be defined as a function", name)
+                    self.error = 1
+                    continue
 
-    regexs = { }
+                if not tokname in self.tokens and tokname.find("ignore_") < 0:
+                    self.log.error("Rule '%s' defined for an unspecified token %s",name,tokname)
+                    self.error = 1
+                    continue
 
-    # Build the master regular expressions
-    for state in stateinfo.keys():
-        regex_list = []
+                try:
+                    c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | self.reflags)
+                    if (c.match("")):
+                         self.log.error("Regular expression for rule '%s' matches empty string",name)
+                         self.error = 1
+                except re.error:
+                    _etype, e, _etrace = sys.exc_info()
+                    self.log.error("Invalid regular expression for rule '%s'. %s",name,e)
+                    if '#' in r:
+                         self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'",name)
+                    self.error = 1
 
-        # Add rules defined by functions first
-        for fname, f in funcsym[state]:
-            line = f.func_code.co_firstlineno
-            file = f.func_code.co_filename
-            files[file] = None
-            tokname = toknames[fname]
-
-            ismethod = isinstance(f, types.MethodType)
-
-            if not optimize:
-                nargs = f.func_code.co_argcount
-                if ismethod:
+            if not self.funcsym[state] and not self.strsym[state]:
+                self.log.error("No rules defined for state '%s'",state)
+                self.error = 1
+
+            # Validate the error function
+            efunc = self.errorf.get(state,None)
+            if efunc:
+                f = efunc
+                line = func_code(f).co_firstlineno
+                file = func_code(f).co_filename
+                self.files[file] = 1
+
+                if isinstance(f, types.MethodType):
                     reqargs = 2
                 else:
                     reqargs = 1
+                nargs = func_code(f).co_argcount
                 if nargs > reqargs:
-                    print >>sys.stderr, "%s:%d: Rule '%s' has too many arguments." % (file,line,f.__name__)
-                    error = 1
-                    continue
+                    self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__)
+                    self.error = 1
 
                 if nargs < reqargs:
-                    print >>sys.stderr, "%s:%d: Rule '%s' requires an argument." % (file,line,f.__name__)
-                    error = 1
-                    continue
+                    self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__)
+                    self.error = 1
 
-                if tokname == 'ignore':
-                    print >>sys.stderr, "%s:%d: Rule '%s' must be defined as a string." % (file,line,f.__name__)
-                    error = 1
-                    continue
+        for f in self.files:
+            self.validate_file(f)
 
-            if tokname == 'error':
-                errorf[state] = f
-                continue
 
-            if f.__doc__:
-                if not optimize:
-                    try:
-                        c = re.compile("(?P<%s>%s)" % (fname,f.__doc__), re.VERBOSE | reflags)
-                        if c.match(""):
-                             print >>sys.stderr, "%s:%d: Regular expression for rule '%s' matches empty string." % (file,line,f.__name__)
-                             error = 1
-                             continue
-                    except re.error,e:
-                        print >>sys.stderr, "%s:%d: Invalid regular expression for rule '%s'. %s" % (file,line,f.__name__,e)
-                        if '#' in f.__doc__:
-                             print >>sys.stderr, "%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'." % (file,line, f.__name__)
-                        error = 1
-                        continue
+    # -----------------------------------------------------------------------------
+    # validate_file()
+    #
+    # This checks to see if there are duplicated t_rulename() functions or strings
+    # in the parser input file.  This is done using a simple regular expression
+    # match on each line in the given file.  
+    # -----------------------------------------------------------------------------
+
+    def validate_file(self,filename):
+        import os.path
+        base,ext = os.path.splitext(filename)
+        if ext != '.py': return         # No idea what the file is. Return OK
 
-                    if debug:
-                        print "lex: Adding rule %s -> '%s' (state '%s')" % (f.__name__,f.__doc__, state)
+        try:
+            f = open(filename)
+            lines = f.readlines()
+            f.close()
+        except IOError:
+            return                      # Couldn't find the file.  Don't worry about it
 
-                # Okay. The regular expression seemed okay.  Let's append it to the master regular
-                # expression we're building
+        fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(')
+        sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=')
 
-                regex_list.append("(?P<%s>%s)" % (fname,f.__doc__))
-            else:
-                print >>sys.stderr, "%s:%d: No regular expression defined for rule '%s'" % (file,line,f.__name__)
+        counthash = { }
+        linen = 1
+        for l in lines:
+            m = fre.match(l)
+            if not m:
+                m = sre.match(l)
+            if m:
+                name = m.group(1)
+                prev = counthash.get(name)
+                if not prev:
+                    counthash[name] = linen
+                else:
+                    self.log.error("%s:%d: Rule %s redefined. Previously defined on line %d",filename,linen,name,prev)
+                    self.error = 1
+            linen += 1
+            
+# -----------------------------------------------------------------------------
+# lex(module)
+#
+# Build all of the regular expression rules from definitions in the supplied module
+# -----------------------------------------------------------------------------
+def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0,outputdir="", debuglog=None, errorlog=None):
+    global lexer
+    ldict = None
+    stateinfo  = { 'INITIAL' : 'inclusive'}
+    lexobj = Lexer()
+    lexobj.lexoptimize = optimize
+    global token,input
 
-        # Now add all of the simple rules
-        for name,r in strsym[state]:
-            tokname = toknames[name]
+    if errorlog is None:
+        errorlog = PlyLogger(sys.stderr)
+
+    if debug:
+        if debuglog is None:
+            debuglog = PlyLogger(sys.stderr)
 
-            if tokname == 'ignore':
-                 if "\\" in r:
-                      print >>sys.stderr, "lex: Warning. %s contains a literal backslash '\\'" % name
-                 ignore[state] = r
-                 continue
+    # Get the module dictionary used for the lexer
+    if object: module = object
 
-            if not optimize:
-                if tokname == 'error':
-                    raise SyntaxError,"lex: Rule '%s' must be defined as a function" % name
-                    error = 1
-                    continue
+    if module:
+        _items = [(k,getattr(module,k)) for k in dir(module)]
+        ldict = dict(_items)
+    else:
+        ldict = get_caller_module_dict(2)
 
-                if not lexobj.lextokens.has_key(tokname) and tokname.find("ignore_") < 0:
-                    print >>sys.stderr, "lex: Rule '%s' defined for an unspecified token %s." % (name,tokname)
-                    error = 1
-                    continue
-                try:
-                    c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | reflags)
-                    if (c.match("")):
-                         print >>sys.stderr, "lex: Regular expression for rule '%s' matches empty string." % name
-                         error = 1
-                         continue
-                except re.error,e:
-                    print >>sys.stderr, "lex: Invalid regular expression for rule '%s'. %s" % (name,e)
-                    if '#' in r:
-                         print >>sys.stderr, "lex: Make sure '#' in rule '%s' is escaped with '\\#'." % name
+    # Collect parser information from the dictionary
+    linfo = LexerReflect(ldict,log=errorlog,reflags=reflags)
+    linfo.get_all()
+    if not optimize:
+        if linfo.validate_all():
+            raise SyntaxError("Can't build lexer")
 
-                    error = 1
-                    continue
-                if debug:
-                    print "lex: Adding rule %s -> '%s' (state '%s')" % (name,r,state)
+    if optimize and lextab:
+        try:
+            lexobj.readtab(lextab,ldict)
+            token = lexobj.token
+            input = lexobj.input
+            lexer = lexobj
+            return lexobj
 
-            regex_list.append("(?P<%s>%s)" % (name,r))
+        except ImportError:
+            pass
 
-        if not regex_list:
-             print >>sys.stderr, "lex: No rules defined for state '%s'" % state
-             error = 1
+    # Dump some basic debugging information
+    if debug:
+        debuglog.info("lex: tokens   = %r", linfo.tokens)
+        debuglog.info("lex: literals = %r", linfo.literals)
+        debuglog.info("lex: states   = %r", linfo.stateinfo)
 
-        regexs[state] = regex_list
+    # Build a dictionary of valid token names
+    lexobj.lextokens = { }
+    for n in linfo.tokens:
+        lexobj.lextokens[n] = 1
 
+    # Get literals specification
+    if isinstance(linfo.literals,(list,tuple)):
+        lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals)
+    else:
+        lexobj.lexliterals = linfo.literals
 
-    if not optimize:
-        for f in files.keys():
-           if not _validate_file(f):
-                error = 1
+    # Get the stateinfo dictionary
+    stateinfo = linfo.stateinfo
+
+    regexs = { }
+    # Build the master regular expressions
+    for state in stateinfo:
+        regex_list = []
+
+        # Add rules defined by functions first
+        for fname, f in linfo.funcsym[state]:
+            line = func_code(f).co_firstlineno
+            file = func_code(f).co_filename
+            regex_list.append("(?P<%s>%s)" % (fname,f.__doc__))
+            if debug:
+                debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",fname,f.__doc__, state)
 
-    if error:
-        raise SyntaxError,"lex: Unable to build lexer."
+        # Now add all of the simple rules
+        for name,r in linfo.strsym[state]:
+            regex_list.append("(?P<%s>%s)" % (name,r))
+            if debug:
+                debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",name,r, state)
 
-    # From this point forward, we're reasonably confident that we can build the lexer.
-    # No more errors will be generated, but there might be some warning messages.
+        regexs[state] = regex_list
 
     # Build the master regular expressions
 
-    for state in regexs.keys():
-        lexre, re_text, re_names = _form_master_re(regexs[state],reflags,ldict,toknames)
+    if debug:
+        debuglog.info("lex: ==== MASTER REGEXS FOLLOW ====")
+
+    for state in regexs:
+        lexre, re_text, re_names = _form_master_re(regexs[state],reflags,ldict,linfo.toknames)
         lexobj.lexstatere[state] = lexre
         lexobj.lexstateretext[state] = re_text
         lexobj.lexstaterenames[state] = re_names
         if debug:
             for i in range(len(re_text)):
-                 print "lex: state '%s'. regex[%d] = '%s'" % (state, i, re_text[i])
+                debuglog.info("lex: state '%s' : regex[%d] = '%s'",state, i, re_text[i])
 
-    # For inclusive states, we need to add the INITIAL state
-    for state,type in stateinfo.items():
-        if state != "INITIAL" and type == 'inclusive':
+    # For inclusive states, we need to add the regular expressions from the INITIAL state
+    for state,stype in stateinfo.items():
+        if state != "INITIAL" and stype == 'inclusive':
              lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL'])
              lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL'])
              lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL'])
@@ -806,30 +969,30 @@
     lexobj.lexstateinfo = stateinfo
     lexobj.lexre = lexobj.lexstatere["INITIAL"]
     lexobj.lexretext = lexobj.lexstateretext["INITIAL"]
+    lexobj.lexreflags = reflags
 
     # Set up ignore variables
-    lexobj.lexstateignore = ignore
+    lexobj.lexstateignore = linfo.ignore
     lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","")
 
     # Set up error functions
-    lexobj.lexstateerrorf = errorf
-    lexobj.lexerrorf = errorf.get("INITIAL",None)
-    if warn and not lexobj.lexerrorf:
-        print >>sys.stderr, "lex: Warning. no t_error rule is defined."
+    lexobj.lexstateerrorf = linfo.errorf
+    lexobj.lexerrorf = linfo.errorf.get("INITIAL",None)
+    if not lexobj.lexerrorf:
+        errorlog.warning("No t_error rule is defined")
 
     # Check state information for ignore and error rules
     for s,stype in stateinfo.items():
         if stype == 'exclusive':
-              if warn and not errorf.has_key(s):
-                   print >>sys.stderr, "lex: Warning. no error rule is defined for exclusive state '%s'" % s
-              if warn and not ignore.has_key(s) and lexobj.lexignore:
-                   print >>sys.stderr, "lex: Warning. no ignore rule is defined for exclusive state '%s'" % s
+              if not s in linfo.errorf:
+                   errorlog.warning("No error rule is defined for exclusive state '%s'", s)
+              if not s in linfo.ignore and lexobj.lexignore:
+                   errorlog.warning("No ignore rule is defined for exclusive state '%s'", s)
         elif stype == 'inclusive':
-              if not errorf.has_key(s):
-                   errorf[s] = errorf.get("INITIAL",None)
-              if not ignore.has_key(s):
-                   ignore[s] = ignore.get("INITIAL","")
-
+              if not s in linfo.errorf:
+                   linfo.errorf[s] = linfo.errorf.get("INITIAL",None)
+              if not s in linfo.ignore:
+                   linfo.ignore[s] = linfo.ignore.get("INITIAL","")
 
     # Create global versions of the token() and input() functions
     token = lexobj.token
@@ -856,7 +1019,7 @@
             data = f.read()
             f.close()
         except IndexError:
-            print "Reading from standard input (type EOF to end):"
+            sys.stdout.write("Reading from standard input (type EOF to end):\n")
             data = sys.stdin.read()
 
     if lexer:
@@ -872,8 +1035,7 @@
     while 1:
         tok = _token()
         if not tok: break
-        print "(%s,%r,%d,%d)" % (tok.type, tok.value, tok.lineno,tok.lexpos)
-
+        sys.stdout.write("(%s,%r,%d,%d)\n" % (tok.type, tok.value, tok.lineno,tok.lexpos))
 
 # -----------------------------------------------------------------------------
 # @TOKEN(regex)
@@ -884,7 +1046,7 @@
 
 def TOKEN(r):
     def set_doc(f):
-        if callable(r):
+        if hasattr(r,"__call__"):
             f.__doc__ = r.__doc__
         else:
             f.__doc__ = r
diff -ruN ply.orig/yacc.py ply/yacc.py
--- other-licenses/ply/ply.orig/yacc.py	2010-04-02 19:02:56.000000000 +0300
+++ other-licenses/ply/ply/yacc.py	2009-09-02 17:27:23.000000000 +0300
@@ -1,26 +1,35 @@
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
 # ply: yacc.py
 #
-# Author(s): David M. Beazley (dave at dabeaz.com)
-#
-# Copyright (C) 2001-2008, 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.
-#
+# Copyright (C) 2001-2009,
+# David M. Beazley (Dabeaz LLC)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# 
+# * Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.  
+# * Redistributions in binary form must reproduce the above copyright notice, 
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.  
+# * Neither the name of the David Beazley or Dabeaz LLC may be used to
+#   endorse or promote products derived from this software without
+#  specific prior written permission. 
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------------------------
 #
 # This implements an LR parser that is constructed from grammar rules defined
 # as Python functions. The grammer is specified by supplying the BNF inside
@@ -50,8 +59,8 @@
 # own risk!
 # ----------------------------------------------------------------------------
 
-__version__    = "2.5"
-__tabversion__ = "2.4"       # Table version
+__version__    = "3.3"
+__tabversion__ = "3.2"       # Table version
 
 #-----------------------------------------------------------------------------
 #                     === User configurable parameters ===
@@ -71,24 +80,83 @@
 yaccdevel   = 0                # Set to True if developing yacc.  This turns off optimized
                                # implementations of certain functions.
 
-import re, types, sys, cStringIO, md5, os.path
+resultlimit = 40               # Size limit of results when running in debug mode.
 
-# Exception raised for yacc-related errors
-class YaccError(Exception):   pass
+pickle_protocol = 0            # Protocol to use when writing pickle files
 
-# Exception raised for errors raised in production rules
-class SyntaxError(Exception): pass
+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__
 
-# Available instance types.  This is used when parsers are defined by a class.
-# it's a little funky because I want to preserve backwards compatibility
-# with Python 2.0 where types.ObjectType is undefined.
-
+# Compatibility
 try:
-    _INSTANCETYPE = (types.InstanceType, types.ObjectType)
+    MAXINT = sys.maxint
 except AttributeError:
-    _INSTANCETYPE = types.InstanceType
-    class object: pass     # Note: needed if no new-style classes present
+    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 ===
@@ -142,6 +210,9 @@
     def lineno(self,n):
         return getattr(self.slice[n],"lineno",0)
 
+    def set_lineno(self,n,lineno):
+        self.slice[n].lineno = lineno
+
     def linespan(self,n):
         startline = getattr(self.slice[n],"lineno",0)
         endline = getattr(self.slice[n],"endlineno",startline)
@@ -159,27 +230,18 @@
        raise SyntaxError
 
 
-# The LR Parsing engine.   This is defined as a class so that multiple parsers
-# can exist in the same process.  A user never instantiates this directly.
-# Instead, the global yacc() function should be used to create a suitable Parser
-# object.
-
-class Parser:
-    def __init__(self,magic=None):
-
-        # This is a hack to keep users from trying to instantiate a Parser
-        # object directly.
-
-        if magic != "xyzzy":
-            raise YaccError, "Can't directly instantiate Parser. Use yacc() instead."
-
-        # Reset internal state
-        self.productions = None          # List of productions
-        self.errorfunc   = None          # Error handling function
-        self.action      = { }           # LR Action table
-        self.goto        = { }           # LR goto table
-        self.require     = { }           # Attribute require table
-        self.method      = "Unknown LR"  # Table construction method used
+# -----------------------------------------------------------------------------
+#                               == 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
@@ -194,6 +256,8 @@
 
     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)
@@ -215,7 +279,7 @@
     #
     # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
-    def parsedebug(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None):
+    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.)
@@ -223,12 +287,16 @@
         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 
-        endsym  = "$end"                 # End symbol
+
+        # --! DEBUG
+        debug.info("PLY: PARSE DEBUG START")
+        # --! DEBUG
+
         # If no lexer was given, we will try to use the lex module
         if not lexer:
-            import lex
+            lex = load_ply_lex()
             lexer = lex.lexer
-        
+
         # Set up the lexer and parser objects on pslice
         pslice.lexer = lexer
         pslice.parser = self
@@ -257,7 +325,7 @@
 
         statestack.append(0)
         sym = YaccSymbol()
-        sym.type = endsym
+        sym.type = "$end"
         symstack.append(sym)
         state = 0
         while 1:
@@ -266,8 +334,8 @@
             # the next token off of the lookaheadstack or from the lexer
 
             # --! DEBUG
-            if debug > 1:
-                print 'state', state
+            debug.debug('')
+            debug.debug('State  : %s', state)
             # --! DEBUG
 
             if not lookahead:
@@ -277,35 +345,25 @@
                     lookahead = lookaheadstack.pop()
                 if not lookahead:
                     lookahead = YaccSymbol()
-                    lookahead.type = endsym
+                    lookahead.type = "$end"
 
             # --! DEBUG
-            if debug:
-                errorlead = ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()
+            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)
 
-            # --! DEBUG
-            if debug > 1:
-                print 'action', t
-            # --! DEBUG
-
             if t is not None:
                 if t > 0:
                     # shift a symbol on the stack
-                    if ltype is endsym:
-                        # Error, end of input
-                        sys.stderr.write("yacc: Parse error. EOF\n")
-                        return
                     statestack.append(t)
                     state = t
                     
                     # --! DEBUG
-                    if debug > 1:
-                        sys.stderr.write("%-60s shift state %s\n" % (errorlead, t))
+                    debug.debug("Action : Shift and goto state %s", t)
                     # --! DEBUG
 
                     symstack.append(lookahead)
@@ -327,8 +385,11 @@
                     sym.value = None
 
                     # --! DEBUG
-                    if debug > 1:
-                        sys.stderr.write("%-60s reduce %d\n" % (errorlead, -t))
+                    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:
@@ -355,9 +416,12 @@
                         
                         try:
                             # Call the grammar rule with our special slice object
-                            p.func(pslice)
                             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)
@@ -393,7 +457,10 @@
 
                         try:
                             # Call the grammar rule with our special slice object
-                            p.func(pslice)
+                            p.callable(pslice)
+                            # --! DEBUG
+                            debug.info("Result : %s", format_result(pslice[0]))
+                            # --! DEBUG
                             symstack.append(sym)
                             state = goto[statestack[-1]][pname]
                             statestack.append(state)
@@ -412,13 +479,18 @@
 
                 if t == 0:
                     n = symstack[-1]
-                    return getattr(n,"value",None)
+                    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
-                if debug:
-                    sys.stderr.write(errorlead + "\n")
+                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
@@ -435,13 +507,15 @@
                     errorcount = error_count
                     self.errorok = 0
                     errtoken = lookahead
-                    if errtoken.type is endsym:
+                    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
 
@@ -471,7 +545,7 @@
                 # 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 is not endsym:
+                if len(statestack) <= 1 and lookahead.type != "$end":
                     lookahead = None
                     errtoken = None
                     state = 0
@@ -483,7 +557,7 @@
                 # at the end of the file. nuke the top entry and generate an error token
 
                 # Start nuking entries on the stack
-                if lookahead.type is endsym:
+                if lookahead.type == "$end":
                     # Whoa. We're really hosed here. Bail out
                     return
 
@@ -509,7 +583,7 @@
                 continue
 
             # Call an error function here
-            raise RuntimeError, "yacc: internal parser error!!!\n"
+            raise RuntimeError("yacc: internal parser error!!!\n")
 
     # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     # parseopt().
@@ -531,7 +605,7 @@
 
         # If no lexer was given, we will try to use the lex module
         if not lexer:
-            import lex
+            lex = load_ply_lex()
             lexer = lex.lexer
         
         # Set up the lexer and parser objects on pslice
@@ -586,10 +660,6 @@
             if t is not None:
                 if t > 0:
                     # shift a symbol on the stack
-                    if ltype == '$end':
-                        # Error, end of input
-                        sys.stderr.write("yacc: Parse error. EOF\n")
-                        return
                     statestack.append(t)
                     state = t
 
@@ -635,9 +705,9 @@
                         
                         try:
                             # Call the grammar rule with our special slice object
-                            p.func(pslice)
                             del symstack[-plen:]
                             del statestack[-plen:]
+                            p.callable(pslice)
                             symstack.append(sym)
                             state = goto[statestack[-1]][pname]
                             statestack.append(state)
@@ -673,7 +743,7 @@
 
                         try:
                             # Call the grammar rule with our special slice object
-                            p.func(pslice)
+                            p.callable(pslice)
                             symstack.append(sym)
                             state = goto[statestack[-1]][pname]
                             statestack.append(state)
@@ -717,6 +787,8 @@
                         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
 
@@ -784,7 +856,7 @@
                 continue
 
             # Call an error function here
-            raise RuntimeError, "yacc: internal parser error!!!\n"
+            raise RuntimeError("yacc: internal parser error!!!\n")
 
     # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     # parseopt_notrack().
@@ -805,7 +877,7 @@
 
         # If no lexer was given, we will try to use the lex module
         if not lexer:
-            import lex
+            lex = load_ply_lex()
             lexer = lex.lexer
         
         # Set up the lexer and parser objects on pslice
@@ -860,10 +932,6 @@
             if t is not None:
                 if t > 0:
                     # shift a symbol on the stack
-                    if ltype == '$end':
-                        # Error, end of input
-                        sys.stderr.write("yacc: Parse error. EOF\n")
-                        return
                     statestack.append(t)
                     state = t
 
@@ -898,9 +966,9 @@
                         
                         try:
                             # Call the grammar rule with our special slice object
-                            p.func(pslice)
                             del symstack[-plen:]
                             del statestack[-plen:]
+                            p.callable(pslice)
                             symstack.append(sym)
                             state = goto[statestack[-1]][pname]
                             statestack.append(state)
@@ -930,7 +998,7 @@
 
                         try:
                             # Call the grammar rule with our special slice object
-                            p.func(pslice)
+                            p.callable(pslice)
                             symstack.append(sym)
                             state = goto[statestack[-1]][pname]
                             statestack.append(state)
@@ -974,6 +1042,8 @@
                         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
 
@@ -1041,169 +1111,172 @@
                 continue
 
             # Call an error function here
-            raise RuntimeError, "yacc: internal parser error!!!\n"
-
+            raise RuntimeError("yacc: internal parser error!!!\n")
 
 # -----------------------------------------------------------------------------
-#                          === Parser Construction ===
+#                          === Grammar Representation ===
 #
-# The following functions and variables are used to implement the yacc() function
-# itself.   This is pretty hairy stuff involving lots of error checking,
-# construction of LR items, kernels, and so forth.   Although a lot of
-# this work is done using global variables, the resulting Parser object
-# is completely self contained--meaning that it is safe to repeatedly
-# call yacc() with different grammars in the same application.
+# The following functions, classes, and variables are used to represent and
+# manipulate the rules that make up a grammar. 
 # -----------------------------------------------------------------------------
 
-# -----------------------------------------------------------------------------
-# validate_file()
-#
-# This function 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_file(filename):
-    base,ext = os.path.splitext(filename)
-    if ext != '.py': return 1          # No idea. Assume it's okay.
+import re
 
-    try:
-        f = open(filename)
-        lines = f.readlines()
-        f.close()
-    except IOError:
-        return 1                       # Oh well
-
-    # Match def p_funcname(
-    fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(')
-    counthash = { }
-    linen = 1
-    noerror = 1
-    for l in lines:
-        m = fre.match(l)
-        if m:
-            name = m.group(1)
-            prev = counthash.get(name)
-            if not prev:
-                counthash[name] = linen
-            else:
-                sys.stderr.write("%s:%d: Function %s redefined. Previously defined on line %d\n" % (filename,linen,name,prev))
-                noerror = 0
-        linen += 1
-    return noerror
-
-# This function looks for functions that might be grammar rules, but which don't have the proper p_suffix.
-def validate_dict(d):
-    for n,v in d.items():
-        if n[0:2] == 'p_' and type(v) in (types.FunctionType, types.MethodType): continue
-        if n[0:2] == 't_': continue
-
-        if n[0:2] == 'p_':
-            sys.stderr.write("yacc: Warning. '%s' not defined as a function\n" % n)
-        if 1 and isinstance(v,types.FunctionType) and v.func_code.co_argcount == 1:
-            try:
-                doc = v.__doc__.split(" ")
-                if doc[1] == ':':
-                    sys.stderr.write("%s:%d: Warning. Possible grammar rule '%s' defined without p_ prefix.\n" % (v.func_code.co_filename, v.func_code.co_firstlineno,n))
-            except StandardError:
-                pass
+# regex matching identifiers
+_is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$')
 
 # -----------------------------------------------------------------------------
-#                           === GRAMMAR FUNCTIONS ===
+# class Production:
 #
-# The following global variables and functions are used to store, manipulate,
-# and verify the grammar rules specified by the user.
-# -----------------------------------------------------------------------------
-
-# Initialize all of the global variables used during grammar construction
-def initialize_vars():
-    global Productions, Prodnames, Prodmap, Terminals
-    global Nonterminals, First, Follow, Precedence, UsedPrecedence, LRitems
-    global Errorfunc, Signature, Requires
-
-    Productions  = [None]  # A list of all of the productions.  The first
-                           # entry is always reserved for the purpose of
-                           # building an augmented grammar
-
-    Prodnames    = { }     # A dictionary mapping the names of nonterminals to a list of all
-                           # productions of that nonterminal.
+# This class stores the raw information about a single production or grammar rule.
+# A grammar rule refers to a specification such as this:
+#
+#       expr : expr PLUS term 
+#
+# Here are the basic attributes defined on all productions
+#
+#       name     - Name of the production.  For example 'expr'
+#       prod     - A list of symbols on the right side ['expr','PLUS','term']
+#       prec     - Production precedence level
+#       number   - Production number.
+#       func     - Function that executes on reduce
+#       file     - File where production function is defined
+#       lineno   - Line number where production function is defined
+#
+# The following attributes are defined or optional.
+#
+#       len       - Length of the production (number of symbols on right hand side)
+#       usyms     - Set of unique symbols found in the production
+# -----------------------------------------------------------------------------
+
+class Production(object):
+    reduced = 0
+    def __init__(self,number,name,prod,precedence=('right',0),func=None,file='',line=0):
+        self.name     = name
+        self.prod     = tuple(prod)
+        self.number   = number
+        self.func     = func
+        self.callable = None
+        self.file     = file
+        self.line     = line
+        self.prec     = precedence
 
-    Prodmap      = { }     # A dictionary that is only used to detect duplicate
-                           # productions.
+        # Internal settings used during table construction
+        
+        self.len  = len(self.prod)   # Length of the production
 
-    Terminals    = { }     # A dictionary mapping the names of terminal symbols to a
-                           # list of the rules where they are used.
+        # 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
 
-    Nonterminals = { }     # A dictionary mapping names of nonterminals to a list
-                           # of rule numbers where they are used.
+        # Create a string representation
+        if self.prod:
+            self.str = "%s -> %s" % (self.name," ".join(self.prod))
+        else:
+            self.str = "%s -> <empty>" % self.name
 
-    First        = { }     # A dictionary of precomputed FIRST(x) symbols
+    def __str__(self):
+        return self.str
 
-    Follow       = { }     # A dictionary of precomputed FOLLOW(x) symbols
+    def __repr__(self):
+        return "Production("+str(self)+")"
 
-    Precedence   = { }     # Precedence rules for each terminal. Contains tuples of the
-                           # form ('right',level) or ('nonassoc', level) or ('left',level)
+    def __len__(self):
+        return len(self.prod)
 
-    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.
+    def __nonzero__(self):
+        return 1
 
-    LRitems      = [ ]     # A list of all LR items for the grammar.  These are the
-                           # productions with the "dot" like E -> E . PLUS E
+    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)
 
-    Errorfunc    = None    # User defined error handler
+        # 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
 
-    Signature    = md5.new()   # Digital signature of the grammar rules, precedence
-                               # and other information.  Used to determined when a
-                               # parsing table needs to be regenerated.
+        return p
     
-    Signature.update(__tabversion__)
+    # 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
 
-    Requires     = { }     # Requires list
+    # Bind the production function name to a callable
+    def bind(self,pdict):
+        if self.func:
+            self.callable = pdict[self.func]
 
-    # File objects used when creating the parser.out debugging file
-    global _vf, _vfc
-    _vf           = cStringIO.StringIO()
-    _vfc          = cStringIO.StringIO()
 
 # -----------------------------------------------------------------------------
-# class Production:
+# class LRItem
 #
-# This class stores the raw information about a single production or grammar rule.
-# It has a few required attributes:
+# This class represents a specific stage of parsing a production rule.  For
+# example: 
 #
-#       name     - Name of the production (nonterminal)
-#       prod     - A list of symbols making up its production
-#       number   - Production number.
+#       expr : expr . PLUS term 
 #
-# In addition, a few additional attributes are used to help with debugging or
-# optimization of table generation.
+# In the above, the "." represents the current location of the parse.  Here
+# basic attributes:
 #
-#       file     - File where production action is defined.
-#       lineno   - Line number where action is defined
-#       func     - Action function
-#       prec     - Precedence level
-#       lr_next  - Next LR item. Example, if we are ' E -> E . PLUS E'
-#                  then lr_next refers to 'E -> E PLUS . E'
-#       lr_index - LR item index (location of the ".") in the prod list.
+#       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)
+#       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 Production:
-    def __init__(self,**kw):
-        for k,v in kw.items():
-            setattr(self,k,v)
-        self.lr_index = -1
-        self.lr0_added = 0    # Flag indicating whether or not added to LR0 closure
-        self.lr1_added = 0    # Flag indicating whether or not added to LR1
-        self.usyms = [ ]
+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.lk_added = { }
-        self.setnumbers = [ ]
+        self.prod.insert(n,".")
+        self.prod       = tuple(self.prod)
+        self.len        = len(self.prod)
+        self.usyms      = p.usyms
 
     def __str__(self):
         if self.prod:
@@ -1213,932 +1286,597 @@
         return s
 
     def __repr__(self):
-        return str(self)
-
-    # Compute lr_items from the production
-    def lr_item(self,n):
-        if n > len(self.prod): return None
-        p = Production()
-        p.name = self.name
-        p.prod = list(self.prod)
-        p.number = self.number
-        p.lr_index = n
-        p.lookaheads = { }
-        p.setnumbers = self.setnumbers
-        p.prod.insert(n,".")
-        p.prod = tuple(p.prod)
-        p.len = len(p.prod)
-        p.usyms = self.usyms
-
-        # Precompute list of productions immediately following
-        try:
-            p.lrafter = Prodnames[p.prod[n+1]]
-        except (IndexError,KeyError),e:
-            p.lrafter = []
-        try:
-            p.lrbefore = p.prod[n-1]
-        except IndexError:
-            p.lrbefore = None
-
-        return p
-
-class MiniProduction:
-    pass
+        return "LRItem("+str(self)+")"
 
-# regex matching identifiers
-_is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$')
+# -----------------------------------------------------------------------------
+# 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
 
 # -----------------------------------------------------------------------------
-# add_production()
+#                           === GRAMMAR CLASS ===
 #
-# Given an action function, this function assembles a production rule.
-# The production rule is assumed to be found in the function's docstring.
-# This rule has the general syntax:
-#
-#              name1 ::= production1
-#                     |  production2
-#                     |  production3
-#                    ...
-#                     |  productionn
-#              name2 ::= production1
-#                     |  production2
-#                    ...
-# -----------------------------------------------------------------------------
-
-def add_production(f,file,line,prodname,syms):
-
-    if Terminals.has_key(prodname):
-        sys.stderr.write("%s:%d: Illegal rule name '%s'. Already defined as a token.\n" % (file,line,prodname))
-        return -1
-    if prodname == 'error':
-        sys.stderr.write("%s:%d: Illegal rule name '%s'. error is a reserved word.\n" % (file,line,prodname))
-        return -1
-
-    if not _is_identifier.match(prodname):
-        sys.stderr.write("%s:%d: Illegal rule name '%s'\n" % (file,line,prodname))
-        return -1
-
-    for x in range(len(syms)):
-        s = syms[x]
-        if s[0] in "'\"":
-             try:
-                 c = eval(s)
-                 if (len(c) > 1):
-                      sys.stderr.write("%s:%d: Literal token %s in rule '%s' may only be a single character\n" % (file,line,s, prodname))
-                      return -1
-                 if not Terminals.has_key(c):
-                      Terminals[c] = []
-                 syms[x] = c
-                 continue
-             except SyntaxError:
-                 pass
-        if not _is_identifier.match(s) and s != '%prec':
-            sys.stderr.write("%s:%d: Illegal name '%s' in rule '%s'\n" % (file,line,s, prodname))
-            return -1
-
-    # See if the rule is already in the rulemap
-    map = "%s -> %s" % (prodname,syms)
-    if Prodmap.has_key(map):
-        m = Prodmap[map]
-        sys.stderr.write("%s:%d: Duplicate rule %s.\n" % (file,line, m))
-        sys.stderr.write("%s:%d: Previous definition at %s:%d\n" % (file,line, m.file, m.line))
-        return -1
-
-    p = Production()
-    p.name = prodname
-    p.prod = syms
-    p.file = file
-    p.line = line
-    p.func = f
-    p.number = len(Productions)
-
-
-    Productions.append(p)
-    Prodmap[map] = p
-    if not Nonterminals.has_key(prodname):
-        Nonterminals[prodname] = [ ]
-
-    # Add all terminals to Terminals
-    i = 0
-    while i < len(p.prod):
-        t = p.prod[i]
-        if t == '%prec':
-            try:
-                precname = p.prod[i+1]
-            except IndexError:
-                sys.stderr.write("%s:%d: Syntax error. Nothing follows %%prec.\n" % (p.file,p.line))
-                return -1
-
-            prec = Precedence.get(precname,None)
-            if not prec:
-                sys.stderr.write("%s:%d: Nothing known about the precedence of '%s'\n" % (p.file,p.line,precname))
-                return -1
-            else:
-                p.prec = prec
-                UsedPrecedence[precname] = 1
-            del p.prod[i]
-            del p.prod[i]
-            continue
-
-        if Terminals.has_key(t):
-            Terminals[t].append(p.number)
-            # Is a terminal.  We'll assign a precedence to p based on this
-            if not hasattr(p,"prec"):
-                p.prec = Precedence.get(t,('right',0))
-        else:
-            if not Nonterminals.has_key(t):
-                Nonterminals[t] = [ ]
-            Nonterminals[t].append(p.number)
-        i += 1
-
-    if not hasattr(p,"prec"):
-        p.prec = ('right',0)
-
-    # Set final length of productions
-    p.len  = len(p.prod)
-    p.prod = tuple(p.prod)
-
-    # Calculate unique syms in the production
-    p.usyms = [ ]
-    for s in p.prod:
-        if s not in p.usyms:
-            p.usyms.append(s)
+# 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.
+# -----------------------------------------------------------------------------
 
-    # Add to the global productions list
-    try:
-        Prodnames[p.name].append(p)
-    except KeyError:
-        Prodnames[p.name] = [ p ]
-    return 0
-
-# Given a raw rule function, this function rips out its doc string
-# and adds rules to the grammar
-
-def add_function(f):
-    line = f.func_code.co_firstlineno
-    file = f.func_code.co_filename
-    error = 0
+class GrammarError(YaccError): pass
 
-    if isinstance(f,types.MethodType):
-        reqdargs = 2
-    else:
-        reqdargs = 1
+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
 
-    if f.func_code.co_argcount > reqdargs:
-        sys.stderr.write("%s:%d: Rule '%s' has too many arguments.\n" % (file,line,f.__name__))
-        return -1
-
-    if f.func_code.co_argcount < reqdargs:
-        sys.stderr.write("%s:%d: Rule '%s' requires an argument.\n" % (file,line,f.__name__))
-        return -1
-
-    if f.__doc__:
-        # Split the doc string into lines
-        pstrings = f.__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:
-                        sys.stderr.write("%s:%d: Misplaced '|'.\n" % (file,dline))
-                        return -1
-                    prodname = lastp
-                    if len(p) > 1:
-                        syms = p[1:]
-                    else:
-                        syms = [ ]
-                else:
-                    prodname = p[0]
-                    lastp = prodname
-                    assign = p[1]
-                    if len(p) > 2:
-                        syms = p[2:]
-                    else:
-                        syms = [ ]
-                    if assign != ':' and assign != '::=':
-                        sys.stderr.write("%s:%d: Syntax error. Expected ':'\n" % (file,dline))
-                        return -1
+        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.
 
-                e = add_production(f,file,dline,prodname,syms)
-                error += e
+        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] = []
 
-            except StandardError:
-                sys.stderr.write("%s:%d: Syntax error in rule '%s'\n" % (file,dline,ps))
-                error -= 1
-    else:
-        sys.stderr.write("%s:%d: No documentation string specified in function '%s'\n" % (file,line,f.__name__))
-    return error
+        self.Terminals['error'] = []
 
+        self.Nonterminals = { }     # A dictionary mapping names of nonterminals to a list
+                                    # of rule numbers where they are used.
 
-# Cycle checking code (Michael Dyck)
+        self.First        = { }     # A dictionary of precomputed FIRST(x) symbols
 
-def compute_reachable():
-    '''
-    Find each symbol that can be reached from the start symbol.
-    Print a warning for any nonterminals that can't be reached.
-    (Unused terminals have already had their warning.)
-    '''
-    Reachable = { }
-    for s in Terminals.keys() + Nonterminals.keys():
-        Reachable[s] = 0
-
-    mark_reachable_from( Productions[0].prod[0], Reachable )
-
-    for s in Nonterminals.keys():
-        if not Reachable[s]:
-            sys.stderr.write("yacc: Symbol '%s' is unreachable.\n" % s)
-
-def mark_reachable_from(s, Reachable):
-    '''
-    Mark all symbols that are reachable from symbol s.
-    '''
-    if Reachable[s]:
-        # We've already reached symbol s.
-        return
-    Reachable[s] = 1
-    for p in Prodnames.get(s,[]):
-        for r in p.prod:
-            mark_reachable_from(r, Reachable)
-
-# -----------------------------------------------------------------------------
-# compute_terminates()
-#
-# 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 compute_terminates():
-    '''
-    Raise an error for any symbols that don't terminate.
-    '''
-    Terminates = {}
-
-    # Terminals:
-    for t in Terminals.keys():
-        Terminates[t] = 1
-
-    Terminates['$end'] = 1
-
-    # Nonterminals:
-
-    # Initialize to false:
-    for n in Nonterminals.keys():
-        Terminates[n] = 0
-
-    # Then propagate termination until no change:
-    while 1:
-        some_change = 0
-        for (n,pl) in 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
-
-    some_error = 0
-    for (s,terminates) in Terminates.items():
-        if not terminates:
-            if not Prodnames.has_key(s) and not Terminals.has_key(s) 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:
-                sys.stderr.write("yacc: Infinite recursion detected for symbol '%s'.\n" % s)
-                some_error = 1
+        self.Follow       = { }     # A dictionary of precomputed FOLLOW(x) symbols
 
-    return some_error
+        self.Precedence   = { }     # Precedence rules for each terminal. Contains tuples of the
+                                    # form ('right',level) or ('nonassoc', level) or ('left',level)
 
-# -----------------------------------------------------------------------------
-# verify_productions()
-#
-# This function examines all of the supplied rules to see if they seem valid.
-# -----------------------------------------------------------------------------
-def verify_productions(cycle_check=1):
-    error = 0
-    for p in Productions:
-        if not p: continue
+        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.
 
-        for s in p.prod:
-            if not Prodnames.has_key(s) and not Terminals.has_key(s) and s != 'error':
-                sys.stderr.write("%s:%d: Symbol '%s' used, but not defined as a token or a rule.\n" % (p.file,p.line,s))
-                error = 1
-                continue
+        self.Start = None           # Starting symbol for the grammar
 
-    unused_tok = 0
-    # Now verify all of the tokens
-    if yaccdebug:
-        _vf.write("Unused terminals:\n\n")
-    for s,v in Terminals.items():
-        if s != 'error' and not v:
-            sys.stderr.write("yacc: Warning. Token '%s' defined, but not used.\n" % s)
-            if yaccdebug: _vf.write("   %s\n"% s)
-            unused_tok += 1
-
-    # Print out all of the productions
-    if yaccdebug:
-        _vf.write("\nGrammar\n\n")
-        for i in range(1,len(Productions)):
-            _vf.write("Rule %-5d %s\n" % (i, Productions[i]))
-
-    unused_prod = 0
-    # Verify the use of all productions
-    for s,v in Nonterminals.items():
-        if not v:
-            p = Prodnames[s][0]
-            sys.stderr.write("%s:%d: Warning. Rule '%s' defined, but not used.\n" % (p.file,p.line, s))
-            unused_prod += 1
-
-
-    if unused_tok == 1:
-        sys.stderr.write("yacc: Warning. There is 1 unused token.\n")
-    if unused_tok > 1:
-        sys.stderr.write("yacc: Warning. There are %d unused tokens.\n" % unused_tok)
-
-    if unused_prod == 1:
-        sys.stderr.write("yacc: Warning. There is 1 unused rule.\n")
-    if unused_prod > 1:
-        sys.stderr.write("yacc: Warning. There are %d unused rules.\n" % unused_prod)
-
-    if yaccdebug:
-        _vf.write("\nTerminals, with rules where they appear\n\n")
-        ks = Terminals.keys()
-        ks.sort()
-        for k in ks:
-            _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Terminals[k]])))
-        _vf.write("\nNonterminals, with rules where they appear\n\n")
-        ks = Nonterminals.keys()
-        ks.sort()
-        for k in ks:
-            _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Nonterminals[k]])))
-
-    if (cycle_check):
-        compute_reachable()
-        error += compute_terminates()
-#        error += check_cycles()
-    return error
-
-# -----------------------------------------------------------------------------
-# 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():
-    for p in Productions:
-        lastlri = p
-        lri = p.lr_item(0)
-        i = 0
-        while 1:
-            lri = p.lr_item(i)
-            lastlri.lr_next = lri
-            if not lri: break
-            lri.lr_num = len(LRitems)
-            LRitems.append(lri)
-            lastlri = lri
-            i += 1
 
-    # In order for the rest of the parser generator to work, we need to
-    # guarantee that no more lritems are generated.  Therefore, we nuke
-    # the p.lr_item method.  (Only used in debugging)
-    # Production.lr_item = None
+    def __len__(self):
+        return len(self.Productions)
 
-# -----------------------------------------------------------------------------
-# add_precedence()
-#
-# Given a list of precedence rules, add to the precedence table.
-# -----------------------------------------------------------------------------
+    def __getitem__(self,index):
+        return self.Productions[index]
 
-def add_precedence(plist):
-    plevel = 0
-    error = 0
-    for p in plist:
-        plevel += 1
-        try:
-            prec = p[0]
-            terms = p[1:]
-            if prec != 'left' and prec != 'right' and prec != 'nonassoc':
-                sys.stderr.write("yacc: Invalid precedence '%s'\n" % prec)
-                return -1
-            for t in terms:
-                if Precedence.has_key(t):
-                    sys.stderr.write("yacc: Precedence already specified for terminal '%s'\n" % t)
-                    error += 1
-                    continue
-                Precedence[t] = (prec,plevel)
-        except:
-            sys.stderr.write("yacc: Invalid precedence table.\n")
-            error += 1
+    # -----------------------------------------------------------------------------
+    # set_precedence()
+    #
+    # Sets the precedence for a given terminal. assoc is the associativity such as
+    # 'left','right', or 'nonassoc'.  level is a numeric level.
+    #
+    # -----------------------------------------------------------------------------
 
-    return error
+    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" % (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
 
-# -----------------------------------------------------------------------------
-# check_precedence()
-#
-# Checks the use of the Precedence tables.  This makes sure all of the symbols
-# are terminals or were used with %prec
-# -----------------------------------------------------------------------------
+        # Add to the global productions list
+        try:
+            self.Prodnames[prodname].append(p)
+        except KeyError:
+            self.Prodnames[prodname] = [ p ]
+        return 0
 
-def check_precedence():
-    error = 0
-    for precname in Precedence.keys():
-        if not (Terminals.has_key(precname) or UsedPrecedence.has_key(precname)):
-            sys.stderr.write("yacc: Precedence rule '%s' defined for unknown symbol '%s'\n" % (Precedence[precname][0],precname))
-            error += 1
-    return error
+    # -----------------------------------------------------------------------------
+    # 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
 
-# -----------------------------------------------------------------------------
-# augment_grammar()
-#
-# Compute the augmented grammar.  This is just a rule S' -> start where start
-# is the starting symbol.
-# -----------------------------------------------------------------------------
+    # -----------------------------------------------------------------------------
+    # 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 augment_grammar(start=None):
-    if not start:
-        start = Productions[1].name
-    Productions[0] = Production(name="S'",prod=[start],number=0,len=1,prec=('right',0),func=None)
-    Productions[0].usyms = [ start ]
-    Nonterminals[start].append(0)
+    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] )
 
-# -------------------------------------------------------------------------
-# 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(beta):
+        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).
+    # -----------------------------------------------------------------------------
 
-    # We are computing First(x1,x2,x3,...,xn)
-    result = [ ]
-    for x in beta:
-        x_produces_empty = 0
+    def infinite_cycles(self):
+        terminates = {}
 
-        # Add all the non-<empty> symbols of First[x] to the result.
-        for f in First[x]:
-            if f == '<empty>':
-                x_produces_empty = 1
-            else:
-                if f not in result: result.append(f)
+        # Terminals:
+        for t in self.Terminals:
+            terminates[t] = 1
 
-        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>')
+        terminates['$end'] = 1
 
-    return result
+        # Nonterminals:
 
+        # Initialize to false:
+        for n in self.Nonterminals:
+            terminates[n] = 0
 
-# FOLLOW(x)
-# Given a non-terminal.  This function computes the set of all symbols
-# that might follow it.  Dragon book, p. 189.
-
-def compute_follow(start=None):
-    # Add '$end' to the follow list of the start symbol
-    for k in Nonterminals.keys():
-        Follow[k] = [ ]
-
-    if not start:
-        start = Productions[1].name
-
-    Follow[start] = [ '$end' ]
-
-    while 1:
-        didadd = 0
-        for p in Productions[1:]:
-            # Here is the production set
-            for i in range(len(p.prod)):
-                B = p.prod[i]
-                if Nonterminals.has_key(B):
-                    # Okay. We got a non-terminal in a production
-                    fst = first(p.prod[i+1:])
-                    hasempty = 0
-                    for f in fst:
-                        if f != '<empty>' and f not in Follow[B]:
-                            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 Follow[p.name]:
-                            if f not in Follow[B]:
-                                Follow[B].append(f)
-                                didadd = 1
-        if not didadd: break
-
-    if 0 and yaccdebug:
-        _vf.write('\nFollow:\n')
-        for k in Nonterminals.keys():
-            _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Follow[k]])))
-
-# -------------------------------------------------------------------------
-# compute_first1()
-#
-# Compute the value of FIRST1(X) for all symbols
-# -------------------------------------------------------------------------
-def compute_first1():
-
-    # Terminals:
-    for t in Terminals.keys():
-        First[t] = [t]
-
-    First['$end'] = ['$end']
-    First['#'] = ['#'] # what's this for?
-
-    # Nonterminals:
-
-    # Initialize to the empty set:
-    for n in Nonterminals.keys():
-        First[n] = []
-
-    # Then propagate symbols until no change:
-    while 1:
-        some_change = 0
-        for n in Nonterminals.keys():
-            for p in Prodnames[n]:
-                for f in first(p.prod):
-                    if f not in First[n]:
-                        First[n].append( f )
-                        some_change = 1
-        if not some_change:
-            break
-
-    if 0 and yaccdebug:
-        _vf.write('\nFirst:\n')
-        for k in Nonterminals.keys():
-            _vf.write("%-20s : %s\n" %
-                (k, " ".join([str(s) for s in First[k]])))
-
-# -----------------------------------------------------------------------------
-#                           === SLR Generation ===
-#
-# The following functions are used to construct SLR (Simple LR) parsing tables
-# as described on p.221-229 of the dragon book.
-# -----------------------------------------------------------------------------
-
-# Global variables for the LR parsing engine
-def lr_init_vars():
-    global _lr_action, _lr_goto, _lr_method
-    global _lr_goto_cache, _lr0_cidhash
-
-    _lr_action       = { }        # Action table
-    _lr_goto         = { }        # Goto table
-    _lr_method       = "Unknown"  # LR method used
-    _lr_goto_cache   = { }
-    _lr0_cidhash     = { }
-
-
-# Compute the LR(0) closure operation on I, where I is a set of LR(0) items.
-# prodlist is a list of productions.
-
-_add_count = 0       # Counter used to detect cycles
-
-def lr0_closure(I):
-    global _add_count
-
-    _add_count += 1
-    prodlist = Productions
-
-    # Add everything in I to J
-    J = I[:]
-    didadd = 1
-    while didadd:
-        didadd = 0
-        for j in J:
-            for x in j.lrafter:
-                if x.lr0_added == _add_count: continue
-                # Add B --> .G to J
-                J.append(x.lr_next)
-                x.lr0_added = _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(I,x):
-    # First we look for a previously cached entry
-    g = _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 = _lr_goto_cache.get(x,None)
-    if not s:
-        s = { }
-        _lr_goto_cache[x] = s
-
-    gs = [ ]
-    for p in I:
-        n = p.lr_next
-        if n and n.lrbefore == 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 = lr0_closure(gs)
-            s['$end'] = g
-        else:
-            s['$end'] = gs
-    _lr_goto_cache[(id(I),x)] = g
-    return g
-
-_lr0_cidhash = { }
+        # 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
 
-# Compute the LR(0) sets of item function
-def lr0_items():
+            if not some_change:
+                break
 
-    C = [ lr0_closure([Productions[0].lr_next]) ]
-    i = 0
-    for I in C:
-        _lr0_cidhash[id(I)] = i
-        i += 1
+        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)
 
-    # Loop over the items in C and each grammar symbols
-    i = 0
-    while i < len(C):
-        I = C[i]
-        i += 1
+        return infinite
 
-        # 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.keys():
-            g = lr0_goto(I,x)
-            if not g:  continue
-            if _lr0_cidhash.has_key(id(g)): continue
-            _lr0_cidhash[id(g)] = len(C)
-            C.append(g)
+    # -----------------------------------------------------------------------------
+    # 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
 
-    return C
+            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
 
-# -----------------------------------------------------------------------------
-#                       ==== 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).
-#
-# Note:  This implementation is a complete replacement of the LALR(1)
-#        implementation in PLY-1.x releases.   That version was based on
-#        a less efficient algorithm and it had bugs in its implementation.
-# -----------------------------------------------------------------------------
+    # -----------------------------------------------------------------------------
+    # 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)
 
-# -----------------------------------------------------------------------------
-# compute_nullable_nonterminals()
-#
-# Creates a dictionary containing all of the non-terminals that might produce
-# an empty production.
-# -----------------------------------------------------------------------------
+        return unused_tok
 
-def compute_nullable_nonterminals():
-    nullable = {}
-    num_nullable = 0
-    while 1:
-       for p in Productions[1:]:
-           if p.len == 0:
-                nullable[p.name] = 1
-                continue
-           for t in p.prod:
-                if not nullable.has_key(t): break
-           else:
-                nullable[p.name] = 1
-       if len(nullable) == num_nullable: break
-       num_nullable = len(nullable)
-    return nullable
+    # ------------------------------------------------------------------------------
+    # 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
 
-# -----------------------------------------------------------------------------
-# 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.
-# -----------------------------------------------------------------------------
+    # -----------------------------------------------------------------------------
+    # 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
 
-def find_nonterminal_transitions(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 Nonterminals.has_key(t[1]):
-                        if t not in trans: trans.append(t)
-         state = state + 1
-     return trans
+    # -------------------------------------------------------------------------
+    # _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)
 
-# -----------------------------------------------------------------------------
-# 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.
-# -----------------------------------------------------------------------------
+            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>')
 
-def dr_relation(C,trans,nullable):
-    dr_set = { }
-    state,N = trans
-    terms = []
+        return result
 
-    g = lr0_goto(C[state],N)
-    for p in g:
-       if p.lr_index < p.len - 1:
-           a = p.prod[p.lr_index+1]
-           if Terminals.has_key(a):
-               if a not in terms: terms.append(a)
+    # -------------------------------------------------------------------------
+    # compute_first()
+    #
+    # Compute the value of FIRST1(X) for all symbols
+    # -------------------------------------------------------------------------
+    def compute_first(self):
+        if self.First:
+            return self.First
 
-    # This extra bit is to handle the start state
-    if state == 0 and N == Productions[0].prod[0]:
-       terms.append('$end')
+        # Terminals:
+        for t in self.Terminals:
+            self.First[t] = [t]
 
-    return terms
+        self.First['$end'] = ['$end']
 
-# -----------------------------------------------------------------------------
-# reads_relation()
-#
-# Computes the READS() relation (p,A) READS (t,C).
-# -----------------------------------------------------------------------------
+        # Nonterminals:
 
-def reads_relation(C, trans, empty):
-    # Look for empty transitions
-    rel = []
-    state, N = trans
+        # Initialize to the empty set:
+        for n in self.Nonterminals:
+            self.First[n] = []
 
-    g = lr0_goto(C[state],N)
-    j = _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 empty.has_key(a):
-                  rel.append((j,a))
+        # 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
 
-    return rel
+    # ---------------------------------------------------------------------
+    # 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] = [ ]
 
-# -----------------------------------------------------------------------------
-# 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.
-#
-# -----------------------------------------------------------------------------
+        if not start:
+            start = self.Productions[1].name
 
-def compute_lookback_includes(C,trans,nullable):
+        self.Follow[start] = [ '$end' ]
 
-    lookdict = {}          # Dictionary of lookback relations
-    includedict = {}       # Dictionary of include relations
+        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
 
-    # 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
+    # -----------------------------------------------------------------------------
+    # 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 . ]
+    # -----------------------------------------------------------------------------
 
-            # 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
+    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
 
-            lr_index = p.lr_index
-            j = state
-            while lr_index < p.len - 1:
-                 lr_index = lr_index + 1
-                 t = p.prod[lr_index]
+    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']
 
-                 # Check to see if this symbol and state are a non-terminal transition
-                 if dtrans.has_key((j,t)):
-                       # 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
+        if parsetab._tabversion != __tabversion__:
+            raise VersionError("yacc table file version is out of date")
 
-                       li = lr_index + 1
-                       while li < p.len:
-                            if Terminals.has_key(p.prod[li]): break      # No forget it
-                            if not nullable.has_key(p.prod[li]): break
-                            li = li + 1
-                       else:
-                            # Appears to be a relation between (j,t) and (state,N)
-                            includes.append((j,t))
+        self.lr_action = parsetab._lr_action
+        self.lr_goto = parsetab._lr_goto
 
-                 g = lr0_goto(C[j],t)               # Go to next set
-                 j = _lr0_cidhash.get(id(g),-1)     # Go to next state
+        self.lr_productions = []
+        for p in parsetab._lr_productions:
+            self.lr_productions.append(MiniProduction(*p))
 
-            # 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 includedict.has_key(i): includedict[i] = []
-             includedict[i].append((state,N))
-        lookdict[(state,N)] = lookb
+        self.lr_method = parsetab._lr_method
+        return parsetab._lr_signature
 
-    return lookdict,includedict
+    def read_pickle(self,filename):
+        try:
+            import cPickle as pickle
+        except ImportError:
+            import pickle
+
+        in_f = open(filename,"rb")
+
+        tabversion = pickle.load(in_f)
+        if tabversion != __tabversion__:
+            raise VersionError("yacc table file version is out of date")
+        self.lr_method = pickle.load(in_f)
+        signature      = pickle.load(in_f)
+        self.lr_action = pickle.load(in_f)
+        self.lr_goto   = pickle.load(in_f)
+        productions    = pickle.load(in_f)
+
+        self.lr_productions = []
+        for p in productions:
+            self.lr_productions.append(MiniProduction(*p))
+
+        in_f.close()
+        return signature
+
+    # Bind all production function names to callable objects in pdict
+    def bind_callables(self,pdict):
+        for p in self.lr_productions:
+            p.bind(pdict)
+    
+# -----------------------------------------------------------------------------
+#                           === LR Generator ===
+#
+# The following classes and functions are used to generate LR parsing tables on 
+# a grammar.
+# -----------------------------------------------------------------------------
 
 # -----------------------------------------------------------------------------
 # digraph()
@@ -2181,715 +1919,1358 @@
         for a in F.get(y,[]):
             if a not in F[x]: F[x].append(a)
     if N[x] == d:
-       N[stack[-1]] = sys.maxint
+       N[stack[-1]] = MAXINT
        F[stack[-1]] = F[x]
        element = stack.pop()
        while element != x:
-           N[stack[-1]] = sys.maxint
+           N[stack[-1]] = MAXINT
            F[stack[-1]] = F[x]
            element = stack.pop()
 
+class LALRError(YaccError): pass
+
 # -----------------------------------------------------------------------------
-# compute_read_sets()
+#                             == LRGeneratedTable ==
 #
-# 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
+# This class implements the LR table generation algorithm.  There are no
+# public methods except for write()
 # -----------------------------------------------------------------------------
 
-def compute_read_sets(C, ntrans, nullable):
-    FP = lambda x: dr_relation(C,x,nullable)
-    R =  lambda x: reads_relation(C,x,nullable)
-    F = digraph(ntrans,R,FP)
-    return F
+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
 
-# -----------------------------------------------------------------------------
-# 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
-# -----------------------------------------------------------------------------
+        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
 
-def compute_follow_sets(ntrans,readsets,inclsets):
-     FP = lambda x: readsets[x]
-     R  = lambda x: inclsets.get(x,[])
-     F = digraph(ntrans,R,FP)
-     return F
+    # Compute the LR(0) sets of item function
+    def lr0_items(self):
 
-# -----------------------------------------------------------------------------
-# 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
-# -----------------------------------------------------------------------------
+        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
 
-def add_lookaheads(lookbacks,followset):
-    for trans,lb in lookbacks.items():
-        # Loop over productions in lookback
-        for state,p in lb:
-             if not p.lookaheads.has_key(state):
-                  p.lookaheads[state] = []
-             f = followset.get(trans,[])
-             for a in f:
-                  if a not in p.lookaheads[state]: p.lookaheads[state].append(a)
+            # 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)
 
-# -----------------------------------------------------------------------------
-# add_lalr_lookaheads()
-#
-# This function does all of the work of adding lookahead information for use
-# with LALR parsing
-# -----------------------------------------------------------------------------
+        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
 
-def add_lalr_lookaheads(C):
-    # Determine all of the nullable nonterminals
-    nullable = compute_nullable_nonterminals()
+    # -----------------------------------------------------------------------------
+    # 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.
+    # -----------------------------------------------------------------------------
 
-    # Find all non-terminal transitions
-    trans = find_nonterminal_transitions(C)
+    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
 
-    # Compute read sets
-    readsets = compute_read_sets(C,trans,nullable)
+    # -----------------------------------------------------------------------------
+    # 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.
+    # -----------------------------------------------------------------------------
 
-    # Compute lookback/includes relations
-    lookd, included = compute_lookback_includes(C,trans,nullable)
+    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')
 
-    # Compute LALR FOLLOW sets
-    followsets = compute_follow_sets(trans,readsets,included)
+        return terms
 
-    # Add all of the lookaheads
-    add_lookaheads(lookd,followsets)
+    # -----------------------------------------------------------------------------
+    # reads_relation()
+    #
+    # Computes the READS() relation (p,A) READS (t,C).
+    # -----------------------------------------------------------------------------
 
-# -----------------------------------------------------------------------------
-# lr_parse_table()
-#
-# This function constructs the parse tables for SLR or LALR
-# -----------------------------------------------------------------------------
-def lr_parse_table(method):
-    global _lr_method
-    goto = _lr_goto           # Goto array
-    action = _lr_action       # Action array
-    actionp = { }             # Action production array (temporary)
+    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))
 
-    _lr_method = method
+        return rel
 
-    n_srconflict = 0
-    n_rrconflict = 0
+    # -----------------------------------------------------------------------------
+    # 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.
+    #
+    # -----------------------------------------------------------------------------
 
-    if yaccdebug:
-        sys.stderr.write("yacc: Generating %s parsing table...\n" % method)
-        _vf.write("\n\nParsing method: %s\n\n" % method)
+    def compute_lookback_includes(self,C,trans,nullable):
 
-    # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items
-    # This determines the number of states
+        lookdict = {}          # Dictionary of lookback relations
+        includedict = {}       # Dictionary of include relations
 
-    C = lr0_items()
+        # 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
 
-    if method == 'LALR':
-        add_lalr_lookaheads(C)
+        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
 
-    # 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    = { }
-        if yaccdebug:
-            _vf.write("\nstate %d\n\n" % st)
+    # -----------------------------------------------------------------------------
+    # 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):
+        Productions = self.grammar.Productions
+        Precedence  = self.grammar.Precedence
+        goto   = self.lr_goto         # Goto array
+        action = self.lr_action       # Action array
+        log    = self.log             # Logger for output
+
+        actionp = { }                 # Action production array (temporary)
+        
+        log.info("Parsing method: %s", self.lr_method)
+
+        # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items
+        # This determines the number of states
+
+        C = self.lr0_items()
+
+        if self.lr_method == 'LALR':
+            self.add_lalr_lookaheads(C)
+
+        # Build the parser table, state by state
+        st = 0
+        for I in C:
+            # Loop over each production in I
+            actlist = [ ]              # List of actions
+            st_action  = { }
+            st_actionp = { }
+            st_goto    = { }
+            log.info("")
+            log.info("state %d", st)
+            log.info("")
             for p in I:
-                _vf.write("    (%d) %s\n" % (p.number, str(p)))
-            _vf.write("\n")
+                log.info("    (%d) %s", p.number, str(p))
+            log.info("")
 
-        for p in I:
-            try:
-                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 method == 'LALR':
-                            laheads = p.lookaheads[st]
+            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:
-                            laheads = 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 = Productions[st_actionp[a].number].prec
-                                    rprec,rlevel = 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:
-                                            _vfc.write("shift/reduce conflict in state %d resolved as reduce.\n" % st)
-                                            _vf.write("  ! shift/reduce conflict for %s resolved as reduce.\n" % a)
-                                            n_srconflict += 1
-                                    elif (slevel == rlevel) and (rprec == 'nonassoc'):
-                                        st_action[a] = None
-                                    else:
-                                        # Hmmm. Guess we'll keep the shift
-                                        if not rlevel:
-                                            _vfc.write("shift/reduce conflict in state %d resolved as shift.\n" % st)
-                                            _vf.write("  ! shift/reduce conflict for %s resolved as shift.\n" % a)
-                                            n_srconflict +=1
-                                elif r < 0:
-                                    # Reduce/reduce conflict.   In this case, we favor the rule
-                                    # that was defined first in the grammar file
-                                    oldp = Productions[-r]
-                                    pp = Productions[p.number]
-                                    if oldp.line > pp.line:
-                                        st_action[a] = -p.number
-                                        st_actionp[a] = p
-                                    # sys.stderr.write("Reduce/reduce conflict in state %d\n" % st)
-                                    n_rrconflict += 1
-                                    _vfc.write("reduce/reduce conflict in state %d resolved using rule %d (%s).\n" % (st, st_actionp[a].number, st_actionp[a]))
-                                    _vf.write("  ! reduce/reduce conflict for %s resolved using rule %d (%s).\n" % (a,st_actionp[a].number, st_actionp[a]))
-                                else:
-                                    sys.stderr.write("Unknown conflict in state %d\n" % st)
+                            # We are at the end of a production.  Reduce!
+                            if self.lr_method == 'LALR':
+                                laheads = p.lookaheads[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 Terminals.has_key(a):
-                        g = lr0_goto(I,a)
-                        j = _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:
-                                        sys.stderr.write("Shift/shift conflict in state %d\n" % 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 = Productions[st_actionp[a].number].prec
-                                    sprec,slevel = 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:
-                                            n_srconflict += 1
-                                            _vfc.write("shift/reduce conflict in state %d resolved as shift.\n" % st)
-                                            _vf.write("  ! shift/reduce conflict for %s resolved as shift.\n" % a)
-                                    elif (slevel == rlevel) and (rprec == 'nonassoc'):
-                                        st_action[a] = None
+                                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 = Productions[st_actionp[a].number].prec
+                                        rprec,rlevel = 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'))
+                                            Productions[p.number].reduced += 1
+                                        elif (slevel == rlevel) and (rprec == 'nonassoc'):
+                                            st_action[a] = None
+                                        else:
+                                            # Hmmm. Guess we'll keep the shift
+                                            if not rlevel:
+                                                log.info("  ! shift/reduce conflict for %s resolved as shift",a)
+                                                self.sr_conflicts.append((st,a,'shift'))
+                                    elif r < 0:
+                                        # Reduce/reduce conflict.   In this case, we favor the rule
+                                        # that was defined first in the grammar file
+                                        oldp = Productions[-r]
+                                        pp = Productions[p.number]
+                                        if oldp.line > pp.line:
+                                            st_action[a] = -p.number
+                                            st_actionp[a] = p
+                                            chosenp,rejectp = pp,oldp
+                                            Productions[p.number].reduced += 1
+                                            Productions[oldp.number].reduced -= 1
+                                        else:
+                                            chosenp,rejectp = oldp,pp
+                                        self.rr_conflicts.append((st,chosenp,rejectp))
+                                        log.info("  ! reduce/reduce conflict for %s resolved using rule %d (%s)", a,st_actionp[a].number, st_actionp[a])
                                     else:
-                                        # Hmmm. Guess we'll keep the reduce
-                                        if not slevel and not rlevel:
-                                            n_srconflict +=1
-                                            _vfc.write("shift/reduce conflict in state %d resolved as reduce.\n" % st)
-                                            _vf.write("  ! shift/reduce conflict for %s resolved as reduce.\n" % a)
+                                        raise LALRError("Unknown conflict in state %d" % st)
+                                else:
+                                    st_action[a] = -p.number
+                                    st_actionp[a] = p
+                                    Productions[p.number].reduced += 1
+                    else:
+                        i = p.lr_index
+                        a = p.prod[i+1]       # Get symbol right after the "."
+                        if a in self.grammar.Terminals:
+                            g = self.lr0_goto(I,a)
+                            j = self.lr0_cidhash.get(id(g),-1)
+                            if j >= 0:
+                                # We are in a shift state
+                                actlist.append((a,p,"shift and go to state %d" % j))
+                                r = st_action.get(a,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 = Productions[st_actionp[a].number].prec
+                                        sprec,slevel = Precedence.get(a,('right',0))
+                                        if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')):
+                                            # We decide to shift here... highest precedence to shift
+                                            Productions[st_actionp[a].number].reduced -= 1
+                                            st_action[a] = j
+                                            st_actionp[a] = p
+                                            if not rlevel:
+                                                log.info("  ! shift/reduce conflict for %s resolved as shift",a)
+                                                self.sr_conflicts.append((st,a,'shift'))
+                                        elif (slevel == rlevel) and (rprec == 'nonassoc'):
+                                            st_action[a] = None
+                                        else:
+                                            # Hmmm. Guess we'll keep the reduce
+                                            if not slevel and not rlevel:
+                                                log.info("  ! shift/reduce conflict for %s resolved as reduce",a)
+                                                self.sr_conflicts.append((st,a,'reduce'))
 
+                                    else:
+                                        raise LALRError("Unknown conflict in state %d" % st)
                                 else:
-                                    sys.stderr.write("Unknown conflict in state %d\n" % st)
-                            else:
-                                st_action[a] = j
-                                st_actionp[a] = p
+                                    st_action[a] = j
+                                    st_actionp[a] = p
 
-            except StandardError,e:
-               print sys.exc_info()
-               raise YaccError, "Hosed in lr_parse_table"
-
-        # Print the actions associated with each terminal
-        if yaccdebug:
-          _actprint = { }
-          for a,p,m in actlist:
-            if st_action.has_key(a):
-                if p is st_actionp[a]:
-                    _vf.write("    %-15s %s\n" % (a,m))
-                    _actprint[(a,m)] = 1
-          _vf.write("\n")
-          for a,p,m in actlist:
-            if st_action.has_key(a):
-                if p is not st_actionp[a]:
-                    if not _actprint.has_key((a,m)):
-                        _vf.write("  ! %-15s [ %s ]\n" % (a,m))
+            # 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
 
-        # Construct the goto table for this state
-        if yaccdebug:
-            _vf.write("\n")
-        nkeys = { }
-        for ii in I:
-            for s in ii.usyms:
-                if Nonterminals.has_key(s):
-                    nkeys[s] = None
-        for n in nkeys.keys():
-            g = lr0_goto(I,n)
-            j = _lr0_cidhash.get(id(g),-1)
-            if j >= 0:
-                st_goto[n] = j
-                if yaccdebug:
-                    _vf.write("    %-30s shift and go to state %d\n" % (n,j))
-
-        action[st] = st_action
-        actionp[st] = st_actionp
-        goto[st] = st_goto
-
-        st += 1
-
-    if yaccdebug:
-        if n_srconflict == 1:
-            sys.stderr.write("yacc: %d shift/reduce conflict\n" % n_srconflict)
-        if n_srconflict > 1:
-            sys.stderr.write("yacc: %d shift/reduce conflicts\n" % n_srconflict)
-        if n_rrconflict == 1:
-            sys.stderr.write("yacc: %d reduce/reduce conflict\n" % n_rrconflict)
-        if n_rrconflict > 1:
-            sys.stderr.write("yacc: %d reduce/reduce conflicts\n" % n_rrconflict)
-
-# -----------------------------------------------------------------------------
-#                          ==== LR Utility functions ====
-# -----------------------------------------------------------------------------
 
-# -----------------------------------------------------------------------------
-# _lr_write_tables()
-#
-# This function writes the LR parsing tables to a file
-# -----------------------------------------------------------------------------
-
-def lr_write_tables(modulename=tab_module,outputdir=''):
-    if isinstance(modulename, types.ModuleType):
-        print >>sys.stderr, "Warning module %s is inconsistent with the grammar (ignored)" % modulename
-        return
+    # -----------------------------------------------------------------------------
+    # write()
+    #
+    # This function writes the LR parsing tables to a file
+    # -----------------------------------------------------------------------------
 
-    basemodulename = modulename.split(".")[-1]
-    filename = os.path.join(outputdir,basemodulename) + ".py"
-    try:
-        f = open(filename,"w")
+    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("""
+            f.write("""
 # %s
 # This file is automatically generated. Do not edit.
+_tabversion = %r
 
-_lr_method = %s
+_lr_method = %r
 
-_lr_signature = %s
-""" % (filename, repr(_lr_method), repr(Signature.digest())))
+_lr_signature = %r
+    """ % (filename, __tabversion__, self.lr_method, signature))
 
-        # Change smaller to 0 to go back to original tables
-        smaller = 1
+            # 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 _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)
+            # Factor out names to try and make smaller
+            if smaller:
+                items = { }
 
-                f.write("]),")
-            f.write("}\n")
+                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("]),")
+                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 _lr_action.has_key(_x):  _lr_action[_x] = { }
+      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 _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 _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)
+            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("]),")
+                f.write("}\n")
 
-            f.write("""
+                f.write("""
 _lr_goto = { }
 for _k, _v in _lr_goto_items.items():
    for _x,_y in zip(_v[0],_v[1]):
-       if not _lr_goto.has_key(_x): _lr_goto[_x] = { }
+       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 _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 Productions:
-            if p:
-                if (p.func):
-                    f.write("  (%r,%d,%r,%r,%d),\n" % (p.name, p.len, p.func.__name__,p.file,p.line))
-                else:
-                    f.write("  (%r,%d,None,None,None),\n" % (p.name, p.len))
             else:
-                f.write("  None,\n")
-        f.write("]\n")
-
-        f.close()
-
-    except IOError,e:
-        print >>sys.stderr, "Unable to create '%s'" % filename
-        print >>sys.stderr, e
-        return
-
-def lr_read_tables(module=tab_module,optimize=0):
-    global _lr_action, _lr_goto, _lr_productions, _lr_method
-    try:
-        if isinstance(module,types.ModuleType):
-            parsetab = module
-        else:
-            exec "import %s as parsetab" % module
+                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
 
-        if (optimize) or (Signature.digest() == parsetab._lr_signature):
-            _lr_action = parsetab._lr_action
-            _lr_goto   = parsetab._lr_goto
-            _lr_productions = parsetab._lr_productions
-            _lr_method = parsetab._lr_method
-            return 1
-        else:
-            return 0
 
-    except (ImportError,AttributeError):
-        return 0
+    # -----------------------------------------------------------------------------
+    # pickle_table()
+    #
+    # This function pickles the LR parsing tables to a supplied file object
+    # -----------------------------------------------------------------------------
 
+    def pickle_table(self,filename,signature=""):
+        try:
+            import cPickle as pickle
+        except ImportError:
+            import pickle
+        outf = open(filename,"wb")
+        pickle.dump(__tabversion__,outf,pickle_protocol)
+        pickle.dump(self.lr_method,outf,pickle_protocol)
+        pickle.dump(signature,outf,pickle_protocol)
+        pickle.dump(self.lr_action,outf,pickle_protocol)
+        pickle.dump(self.lr_goto,outf,pickle_protocol)
+
+        outp = []
+        for p in self.lr_productions:
+            if p.func:
+                outp.append((p.str,p.name, p.len, p.func,p.file,p.line))
+            else:
+                outp.append((str(p),p.name,p.len,None,None,None))
+        pickle.dump(outp,outf,pickle_protocol)
+        outf.close()
 
 # -----------------------------------------------------------------------------
-# yacc(module)
+#                            === INTROSPECTION ===
 #
-# Build the parser module
+# The following functions and classes are used to implement the PLY
+# introspection features followed by the yacc() function itself.
 # -----------------------------------------------------------------------------
 
-def yacc(method=default_lr, debug=yaccdebug, module=None, tabmodule=tab_module, start=None, check_recursion=1, optimize=0,write_tables=1,debugfile=debug_file,outputdir=''):
-    global yaccdebug
-    yaccdebug = debug
+# -----------------------------------------------------------------------------
+# 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.
+# -----------------------------------------------------------------------------
 
-    initialize_vars()
-    files = { }
-    error = 0
+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
 
-    # Add parsing method to signature
-    Signature.update(method)
+    # 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
 
-    # If a "module" parameter was supplied, extract its dictionary.
-    # Note: a module may in fact be an instance as well.
+    # Compute a signature over the grammar
+    def signature(self):
+        try:
+            from hashlib import md5
+        except ImportError:
+            from md5 import md5
+        try:
+            sig = md5()
+            if self.start:
+                sig.update(self.start.encode('latin-1'))
+            if self.prec:
+                sig.update("".join(["".join(p) for p in self.prec]).encode('latin-1'))
+            if self.tokens:
+                sig.update(" ".join(self.tokens).encode('latin-1'))
+            for f in self.pfuncs:
+                if f[3]:
+                    sig.update(f[3].encode('latin-1'))
+        except (TypeError,ValueError):
+            pass
+        return sig.digest()
 
-    if module:
-        # User supplied a module object.
-        if isinstance(module, types.ModuleType):
-            ldict = module.__dict__
-        elif isinstance(module, _INSTANCETYPE):
-            _items = [(k,getattr(module,k)) for k in dir(module)]
-            ldict = { }
-            for i in _items:
-                ldict[i[0]] = i[1]
-        else:
-            raise ValueError,"Expected a module"
+    # -----------------------------------------------------------------------------
+    # 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.
 
-    else:
-        # No module given.  We might be able to get information from the caller.
-        # Throw an exception and unwind the traceback to get the globals
+            try:
+                f = open(filename)
+                lines = f.readlines()
+                f.close()
+            except IOError:
+                continue
 
-        try:
-            raise RuntimeError
-        except RuntimeError:
-            e,b,t = sys.exc_info()
-            f = t.tb_frame
-            f = f.f_back           # Walk out to our calling function
-            if f.f_globals is f.f_locals:   # Collect global and local variations from caller
-               ldict = f.f_globals
-            else:
-               ldict = f.f_globals.copy()
-               ldict.update(f.f_locals)
+            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)
 
-    # Add starting symbol to signature
-    if not start:
-        start = ldict.get("start",None)
-    if start:
-        Signature.update(start)
+    # 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
-    ef = ldict.get('p_error',None)
-    if ef:
-        if isinstance(ef,types.FunctionType):
-            ismethod = 0
-        elif isinstance(ef, types.MethodType):
-            ismethod = 1
-        else:
-            raise YaccError,"'p_error' defined, but is not a function or method."
-        eline = ef.func_code.co_firstlineno
-        efile = ef.func_code.co_filename
-        files[efile] = None
-
-        if (ef.func_code.co_argcount != 1+ismethod):
-            raise YaccError,"%s:%d: p_error() requires 1 argument." % (efile,eline)
-        global Errorfunc
-        Errorfunc = ef
-    else:
-        print >>sys.stderr, "yacc: Warning. no p_error() function is defined."
+    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 running in optimized mode.  We're going to read tables instead
+                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
 
-    if (optimize and lr_read_tables(tabmodule,1)):
-        # Read parse table
-        del Productions[:]
-        for p in _lr_productions:
-            if not p:
-                Productions.append(None)
+    # 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:
-                m = MiniProduction()
-                m.name = p[0]
-                m.len  = p[1]
-                m.file = p[3]
-                m.line = p[4]
-                if p[2]:
-                    m.func = ldict[p[2]]
-                Productions.append(m)
+                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
 
-    else:
-        # Get the tokens map
-        if (module and isinstance(module,_INSTANCETYPE)):
-            tokens = getattr(module,"tokens",None)
-        else:
-            tokens = ldict.get("tokens",None)
+        self.grammar = grammar
 
-        if not tokens:
-            raise YaccError,"module does not define a list 'tokens'"
-        if not (isinstance(tokens,types.ListType) or isinstance(tokens,types.TupleType)):
-            raise YaccError,"tokens must be a list or tuple."
-
-        # Check to see if a requires dictionary is defined.
-        requires = ldict.get("require",None)
-        if requires:
-            if not (isinstance(requires,types.DictType)):
-                raise YaccError,"require must be a dictionary."
+# -----------------------------------------------------------------------------
+# yacc(module)
+#
+# Build a parser
+# -----------------------------------------------------------------------------
 
-            for r,v in requires.items():
-                try:
-                    if not (isinstance(v,types.ListType)):
-                        raise TypeError
-                    v1 = [x.split(".") for x in v]
-                    Requires[r] = v1
-                except StandardError:
-                    print >>sys.stderr, "Invalid specification for rule '%s' in require. Expected a list of strings" % r
-
-
-        # Build the dictionary of terminals.  We a record a 0 in the
-        # dictionary to track whether or not a terminal is actually
-        # used in the grammar
-
-        if 'error' in tokens:
-            print >>sys.stderr, "yacc: Illegal token 'error'.  Is a reserved word."
-            raise YaccError,"Illegal token name"
-
-        for n in tokens:
-            if Terminals.has_key(n):
-                print >>sys.stderr, "yacc: Warning. Token '%s' multiply defined." % n
-            Terminals[n] = [ ]
-
-        Terminals['error'] = [ ]
-
-        # Get the precedence map (if any)
-        prec = ldict.get("precedence",None)
-        if prec:
-            if not (isinstance(prec,types.ListType) or isinstance(prec,types.TupleType)):
-                raise YaccError,"precedence must be a list or tuple."
-            add_precedence(prec)
-            Signature.update(repr(prec))
-
-        for n in tokens:
-            if not Precedence.has_key(n):
-                Precedence[n] = ('right',0)         # Default, right associative, 0 precedence
-
-        # Get the list of built-in functions with p_ prefix
-        symbols = [ldict[f] for f in ldict.keys()
-               if (type(ldict[f]) in (types.FunctionType, types.MethodType) and ldict[f].__name__[:2] == 'p_'
-                   and ldict[f].__name__ != 'p_error')]
+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, picklefile=None):
 
-        # Check for non-empty symbols
-        if len(symbols) == 0:
-            raise YaccError,"no rules of the form p_rulename are defined."
+    global parse                 # Reference to the parsing method of the last built parser
 
-        # Sort the symbols by line number
-        symbols.sort(lambda x,y: cmp(x.func_code.co_firstlineno,y.func_code.co_firstlineno))
+    # If pickling is enabled, table files are not created
 
-        # Add all of the symbols to the grammar
-        for f in symbols:
-            if (add_function(f)) < 0:
-                error += 1
-            else:
-                files[f.func_code.co_filename] = None
+    if picklefile:
+        write_tables = 0
 
-        # Make a signature of the docstrings
-        for f in symbols:
-            if f.__doc__:
-                Signature.update(f.__doc__)
+    if errorlog is None:
+        errorlog = PlyLogger(sys.stderr)
 
-        lr_init_vars()
+    # 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)
 
-        if error:
-            raise YaccError,"Unable to construct parser."
+    # Collect parser information from the dictionary
+    pinfo = ParserReflect(pdict,log=errorlog)
+    pinfo.get_all()
 
-        if not lr_read_tables(tabmodule):
+    if pinfo.error:
+        raise YaccError("Unable to build parser")
 
-            # Validate files
-            for filename in files.keys():
-                if not validate_file(filename):
-                    error = 1
+    # Check signature against table files (if any)
+    signature = pinfo.signature()
 
-            # Validate dictionary
-            validate_dict(ldict)
+    # Read the tables
+    try:
+        lr = LRTable()
+        if picklefile:
+            read_signature = lr.read_pickle(picklefile)
+        else:
+            read_signature = lr.read_table(tabmodule)
+        if optimize or (read_signature == signature):
+            try:
+                lr.bind_callables(pinfo.pdict)
+                parser = LRParser(lr,pinfo.error_func)
+                parse = parser.parse
+                return parser
+            except Exception:
+                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()
 
-            if start and not Prodnames.has_key(start):
-                raise YaccError,"Bad starting symbol '%s'" % start
+    debuglog.info("Created by PLY version %s (http://www.dabeaz.com/ply)", __version__)
 
-            augment_grammar(start)
-            error = verify_productions(cycle_check=check_recursion)
-            otherfunc = [ldict[f] for f in ldict.keys()
-               if (type(f) in (types.FunctionType,types.MethodType) and ldict[f].__name__[:2] != 'p_')]
 
-            # Check precedence rules
-            if check_precedence():
-                error = 1
+    errors = 0
 
-            if error:
-                raise YaccError,"Unable to construct parser."
+    # 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")
 
-            build_lritems()
-            compute_first1()
-            compute_follow(start)
+    # Create a grammar object
+    grammar = Grammar(pinfo.tokens)
 
-            if method in ['SLR','LALR']:
-                lr_parse_table(method)
-            else:
-                raise YaccError, "Unknown parsing method '%s'" % method
+    # 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
 
-            if write_tables:
-                lr_write_tables(tabmodule,outputdir)
+    # 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, 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 yaccdebug:
-                try:
-                    f = open(os.path.join(outputdir,debugfile),"w")
-                    f.write(_vfc.getvalue())
-                    f.write("\n\n")
-                    f.write(_vf.getvalue())
-                    f.close()
-                except IOError,e:
-                    print >>sys.stderr, "yacc: can't create '%s'" % debugfile,e
-
-    # Made it here.   Create a parser object and set up its internal state.
-    # Set global parse() method to bound method of parser object.
-
-    p = Parser("xyzzy")
-    p.productions = Productions
-    p.errorfunc = Errorfunc
-    p.action = _lr_action
-    p.goto   = _lr_goto
-    p.method = _lr_method
-    p.require = Requires
-
-    global parse
-    parse = p.parse
-
-    global parser
-    parser = p
-
-    # Clean up all of the globals we created
-    if (not optimize):
-        yacc_cleanup()
-    return p
-
-# yacc_cleanup function.  Delete all of the global variables
-# used during table construction
-
-def yacc_cleanup():
-    global _lr_action, _lr_goto, _lr_method, _lr_goto_cache
-    del _lr_action, _lr_goto, _lr_method, _lr_goto_cache
-
-    global Productions, Prodnames, Prodmap, Terminals
-    global Nonterminals, First, Follow, Precedence, UsedPrecedence, LRitems
-    global Errorfunc, Signature, Requires
-
-    del Productions, Prodnames, Prodmap, Terminals
-    del Nonterminals, First, Follow, Precedence, UsedPrecedence, LRitems
-    del Errorfunc, Signature, Requires
-
-    global _vf, _vfc
-    del _vf, _vfc
-
-
-# Stub that raises an error if parsing is attempted without first calling yacc()
-def parse(*args,**kwargs):
-    raise YaccError, "yacc: No parser built with yacc()"
+    if errors:
+        raise YaccError("Unable to build parser")
+    
+    # Run the LRGeneratedTable on the grammar
+    if debug:
+        errorlog.debug("Generating %s tables", method)
+            
+    lr = LRGeneratedTable(grammar,method,debuglog)
+
+    if debug:
+        num_sr = len(lr.sr_conflicts)
+
+        # Report shift/reduce and reduce/reduce conflicts
+        if num_sr == 1:
+            errorlog.warning("1 shift/reduce conflict")
+        elif num_sr > 1:
+            errorlog.warning("%d shift/reduce conflicts", num_sr)
+
+        num_rr = len(lr.rr_conflicts)
+        if num_rr == 1:
+            errorlog.warning("1 reduce/reduce conflict")
+        elif num_rr > 1:
+            errorlog.warning("%d reduce/reduce conflicts", num_rr)
+
+    # Write out conflicts to the output file
+    if debug and (lr.sr_conflicts or lr.rr_conflicts):
+        debuglog.warning("")
+        debuglog.warning("Conflicts:")
+        debuglog.warning("")
+
+        for state, tok, resolution in lr.sr_conflicts:
+            debuglog.warning("shift/reduce conflict for %s in state %d resolved as %s",  tok, state, resolution)
+        
+        already_reported = {}
+        for state, rule, rejected in lr.rr_conflicts:
+            if (state,id(rule),id(rejected)) in already_reported:
+                continue
+            debuglog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule)
+            debuglog.warning("rejected rule (%s) in state %d", rejected,state)
+            errorlog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule)
+            errorlog.warning("rejected rule (%s) in state %d", rejected, state)
+            already_reported[state,id(rule),id(rejected)] = 1
+        
+        warned_never = []
+        for state, rule, rejected in lr.rr_conflicts:
+            if not rejected.reduced and (rejected not in warned_never):
+                debuglog.warning("Rule (%s) is never reduced", rejected)
+                errorlog.warning("Rule (%s) is never reduced", rejected)
+                warned_never.append(rejected)
+
+    # Write the table file if requested
+    if write_tables:
+        lr.write_table(tabmodule,outputdir,signature)
+
+    # Write a pickled version of the tables
+    if picklefile:
+        lr.pickle_table(picklefile,signature)
+
+    # Build the parser
+    lr.bind_callables(pinfo.pdict)
+    parser = LRParser(lr,pinfo.error_func)
+
+    parse = parser.parse
+    return parser
-------------- next part --------------
--- js/src/xpconnect/src/qsgen.py.orig	2010-04-02 19:02:30.000000000 +0300
+++ js/src/xpconnect/src/qsgen.py	2010-04-07 11:05:56.201435457 +0300
@@ -1,4 +1,4 @@
-#!/usr/bin/env/python
+#!/usr/local/bin/python
 # qsgen.py - Generate XPConnect quick stubs.
 #
 # ***** BEGIN LICENSE BLOCK *****
-------------- next part --------------
--- xpcom/idl-parser/xpidl.py.orig	2010-04-02 19:03:13.000000000 +0300
+++ xpcom/idl-parser/xpidl.py	2010-04-07 11:23:53.544027464 +0300
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/local/bin/python
 # xpidl.py - A parser for cross-platform IDL (XPIDL) files.
 #
 # ***** BEGIN LICENSE BLOCK *****


More information about the freebsd-gecko mailing list