changeset 192:6cd6260789a1

Added more tests for parser generator
author Windel Bouwman
date Sun, 26 May 2013 23:19:27 +0200
parents 6b2bec5653f1
children f091e7d70996
files python/pyyacc.py python/testpyy.py
diffstat 2 files changed, 58 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/python/pyyacc.py	Sun May 26 15:28:07 2013 +0200
+++ b/python/pyyacc.py	Sun May 26 23:19:27 2013 +0200
@@ -32,7 +32,8 @@
         """ Add a production rule to the grammar """
         production = Production(name, symbols)
         self.productions.append(production)
-        assert not name in self.terminals, "Cannot redefine terminal"
+        if name in self.terminals:
+            raise ParserGenerationException("Cannot redefine terminal {0}".format(name))
         if not name in self.nonterminals:
             self.nonterminals.append(name)
 
@@ -150,9 +151,18 @@
                 addSt(nis)
                 transitions[(states.index(itemset), symbol)] = states.index(nis)
         return states, transitions
-        
+    
+    def checkSymbols(self):
+        """ Checks no symbols are undefined """
+        for production in self.productions:
+            for symbol in production.symbols:
+                if symbol not in self.Symbols:
+                    raise ParserGenerationException('Symbol {0} undefined'.format(symbol))
+
+
     def genParser(self):
         """ Generates a parser from the grammar """
+        self.checkSymbols()
         action_table = {}
         goto_table = {}
         iis = self.initialItemSet()
@@ -303,6 +313,7 @@
             elif action == ACCEPT:
                 break
 
+
 def testSimpleGrammar():
     # 1. define a simple grammar:
     g = Grammar(['EOF', 'identifier', '(', ')', '+', '*'])
@@ -315,12 +326,16 @@
     g.add_production('factor', ['identifier'])
     g.start_symbol = 'input'
     # 2. define input:
-    tokens = ['identifier', '+', 'identifier', '+', 'identifier', 'EOF']
+    tokens = ['identifier', '+', 'identifier', '+', 'identifier']
     # 3. build parser:
     p = g.genParser()
     # 4. feed input:
-    p.parse(tokens)
+    def genTokens(lst):
+        for t in lst:
+            yield Token(t, t, 0)
+    p.parse(genTokens(tokens))
 
 
 if __name__ == '__main__':
     testSimpleGrammar()
+
--- a/python/testpyy.py	Sun May 26 15:28:07 2013 +0200
+++ b/python/testpyy.py	Sun May 26 23:19:27 2013 +0200
@@ -1,5 +1,5 @@
 import unittest, pprint
-from pyyacc import Grammar, Item, EOF 
+from pyyacc import Grammar, Item, EOF, ParserGenerationException
 from ppci import Token
 
 def genTokens(lst):
@@ -27,6 +27,44 @@
         p = g.genParser()
         # 4. feed input:
         p.parse(tokens)
+    def testReduceReduceConflict(self):
+        """ Check if a reduce-reduce conflict is detected """
+        # Define a grammar with an obvious reduce-reduce conflict:
+        g = Grammar([EOF, 'id'])
+        g.add_production('goal', ['a'])
+        g.add_production('a', ['b'])
+        g.add_production('a', ['c'])
+        g.add_production('b', ['id'])
+        g.add_production('c', ['id'])
+        g.start_symbol = 'goal'
+        with self.assertRaises(ParserGenerationException):
+            p = g.genParser()
+    def testShiftReduceConflict(self):
+        g = Grammar([EOF, 'if', 'then', 'else'])
+        g.add_production('if_stmt', ['if', 'then'])
+        g.add_production('if_stmt', ['if', 'then', 'else'])
+        g.add_production('stmt', ['if_stmt', 'else'])
+        g.start_symbol = 'stmt'
+        with self.assertRaises(ParserGenerationException):
+            g.genParser()
+    def testUndefinedTerminal(self):
+        """ Test correct behavior when a terminal is undefined """
+        g = Grammar([EOF, 'b'])
+        g.add_production('goal', ['a'])
+        g.add_production('a', ['b'])
+        g.add_production('a', ['c'])
+        g.start_symbol = 'goal'
+        with self.assertRaises(ParserGenerationException):
+            g.genParser()
+    def testRedefineTerminal(self):
+        """ Test correct behavior when a terminal is redefined """
+        g = Grammar([EOF, 'b', 'c'])
+        g.add_production('goal', ['a'])
+        with self.assertRaises(ParserGenerationException):
+            g.add_production('b', ['c']) # Not allowed
+        g.add_production('a', ['c'])
+        g.start_symbol = 'goal'
+        g.genParser()
 
 class testExpressionGrammar(unittest.TestCase):
     def setUp(self):