diff python/pyyacc.py @ 194:b01429a5d695

Fixed test
author Windel Bouwman
date Wed, 29 May 2013 22:36:37 +0200
parents 6cd6260789a1
children 37ac6c016e0f
line wrap: on
line diff
--- a/python/pyyacc.py	Sun May 26 23:58:59 2013 +0200
+++ b/python/pyyacc.py	Wed May 29 22:36:37 2013 +0200
@@ -28,14 +28,15 @@
         self.productions = []
         self._first = None  # Cached first set
 
-    def add_production(self, name, symbols):
+    def add_production(self, name, symbols, f=None):
         """ Add a production rule to the grammar """
-        production = Production(name, symbols)
+        production = Production(name, symbols, f)
         self.productions.append(production)
         if name in self.terminals:
             raise ParserGenerationException("Cannot redefine terminal {0}".format(name))
         if not name in self.nonterminals:
             self.nonterminals.append(name)
+        self._first = None  # Invalidate cached version
 
     def productionsForName(self, name):
         """ Retrieve all productions for a non terminal """
@@ -156,7 +157,7 @@
         """ Checks no symbols are undefined """
         for production in self.productions:
             for symbol in production.symbols:
-                if symbol not in self.Symbols:
+                if symbol not in self.Symbols + [EPS]:
                     raise ParserGenerationException('Symbol {0} undefined'.format(symbol))
 
 
@@ -169,13 +170,31 @@
 
         # First generate all item sets by using the nextItemset function:
         states, transitions = self.genCanonicalSet(iis)
+
+        def action_str(act):
+            a, p = act
+            if a is SHIFT:
+                return 'Shift {0}'.format(0)
+            elif a is REDUCE:
+                return 'Reduce {0}'.format(p)
+            return 'Other'
         
         def setAction(state, t, action):
             key = (state, t)
             if key in action_table:
                 action2 = action_table[key]
                 if action != action2:
-                    raise ParserGenerationException('LR construction conflict')
+                    if (action2[0] == REDUCE) and (action[0] == SHIFT):
+                        # Automatically resolve and do the shift action!
+                        # Simple, but almost always what you want!!
+                        action_table[key] = action
+                    else:
+                        if (action2[0] == SHIFT) and (action[0] == REDUCE):
+                            pass
+                        else:
+                            a1 = action_str(action)
+                            a2 = action_str(action2)
+                            raise ParserGenerationException('LR construction conflict {0} vs {1}'.format(a1, a2))
             else:
                 action_table[key] = action
 
@@ -204,9 +223,10 @@
 
 class Production:
     """ Production rule for a grammar """
-    def __init__(self, name, symbols):
+    def __init__(self, name, symbols, f=None):
         self.name = name
         self.symbols = symbols
+        self.f = f
 
     def __repr__(self):
         return '{0} -> {1}'.format(self.name, self.symbols)
@@ -287,18 +307,26 @@
         """ Parse an iterable with tokens """
         assert hasattr(toks, '__iter__'), '{0} not iter type'.format(type(toks))
         stack = [0]
-        look_ahead = toks.__next__()
+        try:
+            look_ahead = toks.__next__()
+        except StopIteration:
+            look_ahead = Token(EOF, EOF)
         assert type(look_ahead) is Token
         while True:
             state = stack[-1]   # top of stack
             key = (state, look_ahead.typ)
             if not key in self.action_table:
-                raise Exception('Error parsing at character {0}'.format(look_ahead))
+                raise ParserException('Error parsing at character {0}'.format(look_ahead))
             action, param = self.action_table[key]
             if action == REDUCE:
+                #print('reduce', param)
+                f_args = []
                 for s in param.symbols:
                     stack.pop()
                     stack.pop()
+                    f_args.append(0)
+                if param.f:
+                    param.f(*f_args)
                 state = stack[-1]
                 stack.append(param.name)
                 stack.append(self.goto_table[(state, param.name)])
@@ -308,34 +336,8 @@
                 try:
                     look_ahead = toks.__next__()
                 except StopIteration:
-                    look_ahead = Token(EOF, EOF, 0)
+                    look_ahead = Token(EOF, EOF)
                 assert type(look_ahead) is Token
             elif action == ACCEPT:
                 break
 
-
-def testSimpleGrammar():
-    # 1. define a simple grammar:
-    g = Grammar(['EOF', 'identifier', '(', ')', '+', '*'])
-    g.add_production('input', ['expression'])
-    g.add_production('expression', ['term'])
-    g.add_production('expression', ['expression', '+', 'term'])
-    g.add_production('term', ['factor'])
-    g.add_production('term', ['term', '*', 'factor'])
-    g.add_production('factor', ['(', 'expression', ')'])
-    g.add_production('factor', ['identifier'])
-    g.start_symbol = 'input'
-    # 2. define input:
-    tokens = ['identifier', '+', 'identifier', '+', 'identifier']
-    # 3. build parser:
-    p = g.genParser()
-    # 4. feed input:
-    def genTokens(lst):
-        for t in lst:
-            yield Token(t, t, 0)
-    p.parse(genTokens(tokens))
-
-
-if __name__ == '__main__':
-    testSimpleGrammar()
-