# HG changeset patch # User Windel Bouwman # Date 1369603167 -7200 # Node ID 6cd6260789a1111574716fe5e1a9413b7f18e380 # Parent 6b2bec5653f1702d5c5bf7d71c77ec14fae71348 Added more tests for parser generator diff -r 6b2bec5653f1 -r 6cd6260789a1 python/pyyacc.py --- 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() + diff -r 6b2bec5653f1 -r 6cd6260789a1 python/testpyy.py --- 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):