package de.fuberlin.projectci.test.parseTable; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.io.BufferedReader; import java.io.StringReader; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import org.junit.After; import org.junit.Before; import org.junit.Test; import de.fuberlin.commons.util.LogFactory; import de.fuberlin.projectci.grammar.BNFGrammarReader; import de.fuberlin.projectci.grammar.BNFParsingErrorException; import de.fuberlin.projectci.grammar.Grammar; import de.fuberlin.projectci.grammar.GrammarReader; import de.fuberlin.projectci.grammar.NonTerminalSymbol; import de.fuberlin.projectci.grammar.Symbol; import de.fuberlin.projectci.grammar.TerminalSymbol; import de.fuberlin.projectci.parseTable.AcceptAction; import de.fuberlin.projectci.parseTable.Goto; import de.fuberlin.projectci.parseTable.InvalidGrammarException; import de.fuberlin.projectci.parseTable.LR0Item; import de.fuberlin.projectci.parseTable.ParseTable; import de.fuberlin.projectci.parseTable.ReduceAction; import de.fuberlin.projectci.parseTable.SLRParseTableBuilder; import de.fuberlin.projectci.parseTable.ShiftAction; import de.fuberlin.projectci.parseTable.State; import de.fuberlin.projectci.test.driver.DriverTestDataProvider1; /** * Testfälle für die Algorithmen zum Austellen einer SLR-Parsetabelle. */ public class SLRParseTableBuilderTest { private static Logger logger = LogFactory.getLogger(SLRParseTableBuilderTest.class); private Grammar grammar=null; private SLRParseTableBuilder slrParseTableBuilder=null; private Set<LR0Item> i0=null; private Set<LR0Item> i1=null; private Set<LR0Item> i2=null; private Set<LR0Item> i3=null; private Set<LR0Item> i4=null; private Set<LR0Item> i5=null; private Set<LR0Item> i6=null; private Set<LR0Item> i7=null; private Set<LR0Item> i8=null; private Set<LR0Item> i9=null; private Set<LR0Item> i10=null; private Set<LR0Item> i11=null; @Before public void setUp() throws Exception { // Testdaten aus dem Drachenbuch Kapitel 4.6 / S.294ff String strGrammar="" + "<E0> ::= <E>\n"+ "<E> ::= <E> \"+\" <T> | <T>\n"+ "<T> ::= <T> \"*\" <F> | <F>\n"+ "<F> ::= \"(\" <E> \")\" | \"id\""; GrammarReader grammarReader=new BNFGrammarReader(); try { grammar=grammarReader.readGrammar(new StringReader(strGrammar)); grammar.setStartSymbol(grammar.getProductionAtIndex(0).getLhs()); // <E0> } catch (BNFParsingErrorException e) { fail(e.getClass()+": "+e.getMessage()); } slrParseTableBuilder=new SLRParseTableBuilder(grammar); i0=new HashSet<LR0Item>(); i0.add(new LR0Item(grammar.getProductionAtIndex(0), 0)); // E0 --> · E i0.add(new LR0Item(grammar.getProductionAtIndex(1), 0)); // E --> · E "+" T i0.add(new LR0Item(grammar.getProductionAtIndex(2), 0)); // E --> · T i0.add(new LR0Item(grammar.getProductionAtIndex(3), 0)); // T --> · T "*" F i0.add(new LR0Item(grammar.getProductionAtIndex(4), 0)); // T --> · F i0.add(new LR0Item(grammar.getProductionAtIndex(5), 0)); // F --> · "(" E ")" i0.add(new LR0Item(grammar.getProductionAtIndex(6), 0)); // F --> · "id" i1=new HashSet<LR0Item>(); i1.add(new LR0Item(grammar.getProductionAtIndex(0), 1)); // E0 --> E · i1.add(new LR0Item(grammar.getProductionAtIndex(1), 1)); // E --> E · "+" T i2=new HashSet<LR0Item>(); i2.add(new LR0Item(grammar.getProductionAtIndex(2), 1)); // E --> T · i2.add(new LR0Item(grammar.getProductionAtIndex(3), 1)); // T --> T · "*" F i3=new HashSet<LR0Item>(); i3.add(new LR0Item(grammar.getProductionAtIndex(4), 1)); // T --> F · i4=new HashSet<LR0Item>(); i4.add(new LR0Item(grammar.getProductionAtIndex(5), 1)); // F --> "(" · E ")" i4.add(new LR0Item(grammar.getProductionAtIndex(1), 0)); // E --> · E "+" T i4.add(new LR0Item(grammar.getProductionAtIndex(2), 0)); // E --> · T i4.add(new LR0Item(grammar.getProductionAtIndex(3), 0)); // T --> · T "*" F i4.add(new LR0Item(grammar.getProductionAtIndex(4), 0)); // T --> · F i4.add(new LR0Item(grammar.getProductionAtIndex(5), 0)); // F --> · "(" E ")" i4.add(new LR0Item(grammar.getProductionAtIndex(6), 0)); // F --> · "id" i5=new HashSet<LR0Item>(); i5.add(new LR0Item(grammar.getProductionAtIndex(6), 1)); // F --> "id" · i6=new HashSet<LR0Item>(); i6.add(new LR0Item(grammar.getProductionAtIndex(1), 2)); // E --> E "+" · T i6.add(new LR0Item(grammar.getProductionAtIndex(3), 0)); // T --> · T "*" F i6.add(new LR0Item(grammar.getProductionAtIndex(4), 0)); // T --> · F i6.add(new LR0Item(grammar.getProductionAtIndex(5), 0)); // F --> · "(" E ")" i6.add(new LR0Item(grammar.getProductionAtIndex(6), 0)); // F --> · "id" // System.out.println(prettyPrintLR0ItemSet(grammar, i6)); i7=new HashSet<LR0Item>(); i7.add(new LR0Item(grammar.getProductionAtIndex(3), 2)); // T --> T "*" · F i7.add(new LR0Item(grammar.getProductionAtIndex(5), 0)); // F --> · "(" E ")" i7.add(new LR0Item(grammar.getProductionAtIndex(6), 0)); // F --> · "id" i8=new HashSet<LR0Item>(); i8.add(new LR0Item(grammar.getProductionAtIndex(1), 1)); // E --> E · "+" T i8.add(new LR0Item(grammar.getProductionAtIndex(5), 2)); // F --> "(" E · ")" i9=new HashSet<LR0Item>(); i9.add(new LR0Item(grammar.getProductionAtIndex(1), 3)); // E --> E "+" T · i9.add(new LR0Item(grammar.getProductionAtIndex(3), 1)); // T --> T · "*" F i10=new HashSet<LR0Item>(); i10.add(new LR0Item(grammar.getProductionAtIndex(3), 3)); // T --> T "*" F · i11=new HashSet<LR0Item>(); i11.add(new LR0Item(grammar.getProductionAtIndex(5), 3)); // F --> "(" E ")" · } @After public void tearDown() throws Exception { } @Test public void testBuildParseTable() { ParseTable parseTable=null; // 1. Test gegen die Ausdrucksgrammatik aus dem Drachenbuch try { parseTable=slrParseTableBuilder.buildParseTable(); } catch (InvalidGrammarException e) { logger.log(Level.INFO, "InvalidGrammarException",e); fail("InvalidGrammarException with valid grammar"); } DriverTestDataProvider1 testDataProvider=new DriverTestDataProvider1(); ParseTable expectedParseTable=testDataProvider.getParseTable(); assertEquals(expectedParseTable, parseTable); // System.err.println(expectedParseTable); // System.err.println(); // System.err.println(parseTable); // 2. Test mit der Nicht-SLR(1) Grammatik aus dem Drachenbuch Grammar nonSLRGrammar=nonSLRGrammar(); SLRParseTableBuilder ptb=new SLRParseTableBuilder(nonSLRGrammar); try { parseTable=ptb.buildParseTable(); System.err.println(parseTable); fail("Invalid Grammar not recognized."); } catch (InvalidGrammarException e) { logger.info("Succed to recognize Non-SLR(1) Grammar"); } // 3. Test, ob die Dangling-Else-Grammatik aus dem Drachenbuch korrekt behandelt wird. Grammar danglingElseGrammar=danglingElseGrammar(); ptb=new SLRParseTableBuilder(danglingElseGrammar); try { parseTable=ptb.buildParseTable(); expectedParseTable=danglingElseParseTable(danglingElseGrammar); assertEquals(expectedParseTable, parseTable); logger.info("Dangline-Else-Test succeed."); // System.err.println(parseTable); } catch (InvalidGrammarException e) { logger.log(Level.INFO, "Dangline-Else-Test failed.",e); fail("Dangline-Else-Test failed"); } // 4. Test, ob die Quellgrammatik geparst werden kann. Grammar sourceGrammar=sourceGrammar(); ptb=new SLRParseTableBuilder(sourceGrammar); try { parseTable=ptb.buildParseTable(); logger.info("Valid source grammar."); // System.out.println(parseTable.toString()); } catch (InvalidGrammarException e) { logger.log(Level.INFO, "Invalid source grammar.",e); } } private Grammar sourceGrammar(){ GrammarReader grammarReader = new BNFGrammarReader(); Grammar g3 = null; try { g3 = grammarReader.readGrammar("./input/de/fuberlin/projectci/quellsprache_bnf.txt"); } catch (BNFParsingErrorException e) { fail(e.getClass()+": "+e.getMessage()); } return g3; } private Grammar nonSLRGrammar(){ // Grammatik (4.16) aus dem Beispiel 4.34 aus dem Drachenbuch String strGrammar="" + "<S0> ::= <S>\n"+ "<S> ::= <L> \"=\" <R> | <R>\n"+ "<L> ::= \"*\" <R> | \"id\"\n"+ "<R> ::= <L>\n"; GrammarReader grammarReader=new BNFGrammarReader(); Grammar grammar=null; try { grammar=grammarReader.readGrammar(new StringReader(strGrammar)); grammar.setStartSymbol(grammar.getProductionAtIndex(0).getLhs()); // <E0> } catch (BNFParsingErrorException e) { fail(e.getClass()+": "+e.getMessage()); } return grammar; } private Grammar danglingElseGrammar(){ // Grammatik (4.18) aus dem Drachenbuch String strGrammar="" + "<S0> ::= <S>\n"+ "<S> ::= \"if\" <S> \"else\" <S> | \"if\" <S> | \"a\"\n"; GrammarReader grammarReader=new BNFGrammarReader(); Grammar grammar=null; try { grammar=grammarReader.readGrammar(new StringReader(strGrammar)); grammar.setStartSymbol(grammar.getProductionAtIndex(0).getLhs()); // <E0> } catch (BNFParsingErrorException e) { fail(e.getClass()+": "+e.getMessage()); } return grammar; } private ParseTable danglingElseParseTable(Grammar danglingElseGrammar){ String strParseTable= " if else a $ S\n"+ "0 s2 s3 1\n"+ "1 acc \n"+ "2 s2 s3 4\n"+ "3 r3 r3 \n"+ "4 s5 r2 \n"+ "5 s2 s3 6\n"+ "6 r1 r1 "; return parseParseTable(danglingElseGrammar, strParseTable); } /** * Hilfsmethode zum Erstellen eines ParseTables aus einer Stringrepräsentation. * */ private ParseTable parseParseTable(Grammar grammar, String strParseTable){ try { BufferedReader bufReader=new BufferedReader(new StringReader(strParseTable)); String[] strSymbols=bufReader.readLine().split("\t"); Symbol[] symbols=new Symbol[strSymbols.length-1]; // erste Spalte ist leer! symbolLoop: for (int i = 1; i < strSymbols.length; i++) { String aSymbolString = strSymbols[i]; for (Symbol aSymbol : grammar.getAllSymbols()) { if (aSymbol.getName().equals(aSymbolString)){ symbols[i-1]=aSymbol; continue symbolLoop; } if (Grammar.EMPTY_STRING.equals(aSymbolString)){ symbols[i-1]=Grammar.EPSILON; continue symbolLoop; } if ("$".equals(aSymbolString)){ symbols[i-1]=Grammar.INPUT_ENDMARKER; continue symbolLoop; } } logger.warning("Failed to parse symbol: "+aSymbolString); return null; } List<State> states=new ArrayList<State>(); List<String[]> lines=new ArrayList<String[]>(); String strLine=bufReader.readLine(); while (strLine!=null){ String[] strLineArray=strLine.split("\t",-1); // limit=-1, damit auch leere Spalten am Ende nicht abgeschnitten werden. if (strLineArray.length!=strSymbols.length){ logger.warning("Failed to parse a line: "+strLine); return null; } states.add(new State(Integer.parseInt(strLineArray[0]))); lines.add(strLineArray); strLine=bufReader.readLine(); } ParseTable parseTable=new ParseTable(); for (int i = 0; i < lines.size(); i++) { State aState=states.get(i); String[] strLineArray =lines.get(i); for (int j = 0; j < symbols.length; j++) { Symbol aSymbol=symbols[j]; String strActionOrGoto=strLineArray[j+1]; if (strActionOrGoto.trim().length()==0){ continue; // leere Spalten ignorieren } if (grammar.getAllTerminalSymols().contains(aSymbol) || Grammar.INPUT_ENDMARKER.equals(aSymbol)){ // Action parsen if (strActionOrGoto.startsWith("s")){ // ShiftAction int targetStateIndex=Integer.parseInt(strActionOrGoto.substring(1)); parseTable.getActionTableForState(aState).setActionForTerminalSymbol(new ShiftAction(states.get(targetStateIndex)), (TerminalSymbol) aSymbol); } else if (strActionOrGoto.startsWith("r")){ // ReduceAction int productionIndex=Integer.parseInt(strActionOrGoto.substring(1)); parseTable.getActionTableForState(aState).setActionForTerminalSymbol(new ReduceAction(grammar.getProductionAtIndex(productionIndex)), (TerminalSymbol) aSymbol); } else if (strActionOrGoto.startsWith("acc")){ // AcceptAction parseTable.getActionTableForState(aState).setActionForTerminalSymbol(new AcceptAction(), (TerminalSymbol) aSymbol); } else{ logger.warning("Invalid action for state="+aState+" and symbol="+aSymbol+": "+strActionOrGoto); return null; } } else if (grammar.getAllNonTerminals().contains(aSymbol)){ // Goto parsen int targetStateIndex=Integer.parseInt(strActionOrGoto); parseTable.getGotoTableForState(aState).setGotoForNonTerminalSymbol(new Goto(states.get(targetStateIndex)), (NonTerminalSymbol) aSymbol); } else{ logger.warning("Found neither Action nor Goto for state="+aState+" and symbol="+aSymbol+": "+strActionOrGoto); return null; } } } parseTable.setInitialState(states.get(0)); return parseTable; } catch (Exception e) { logger.log(Level.WARNING, "Failed to parse ParseTable.",e); return null; } } @Test public void testClosure() { Set<LR0Item> testItemSet0=new HashSet<LR0Item>(); testItemSet0.add(new LR0Item(grammar.getProductionAtIndex(0), 0)); // E0 --> · E // System.out.println(prettyPrintLR0ItemSet(grammar, i0)); Set<LR0Item> result=slrParseTableBuilder.closure(testItemSet0); String failureMessage=null; if (!i0.equals(result)){ failureMessage="Expected:\n"+prettyPrintLR0ItemSet(grammar, i0); if (result!=null){ failureMessage+="Actual:\n"+prettyPrintLR0ItemSet(grammar, result); } else{ failureMessage+="Actual: null"; } } assertEquals(failureMessage, i0, result); } @Test public void testGotoSet() { Symbol symbol=new TerminalSymbol("+"); // Vgl. Drachenbuch Beispiel 4.27 Set<LR0Item> result=slrParseTableBuilder.gotoSet(i1, symbol); String failureMessage=null; if (!i6.equals(result)){ failureMessage="Expected:\n"+prettyPrintLR0ItemSet(grammar, i6); if (result!=null){ failureMessage+="Actual:\n"+prettyPrintLR0ItemSet(grammar, result); } else{ failureMessage+="Actual: null"; } } assertEquals(failureMessage, i6, result); } @Test public void testCannonicalCollectionOfLR0Items() { List<Set<LR0Item>> result = slrParseTableBuilder.cannonicalCollectionOfLR0Items(); // for (Set<LR0Item> anItemSet : result) { // System.err.println("---"); // System.err.println(prettyPrintLR0ItemSet(grammar, anItemSet)); // } List<Set<LR0Item>> cannonicalCollection= new ArrayList<Set<LR0Item>>(); cannonicalCollection.add(i0); cannonicalCollection.add(i1); cannonicalCollection.add(i2); cannonicalCollection.add(i3); cannonicalCollection.add(i4); cannonicalCollection.add(i5); cannonicalCollection.add(i6); cannonicalCollection.add(i7); cannonicalCollection.add(i8); cannonicalCollection.add(i9); cannonicalCollection.add(i10); cannonicalCollection.add(i11); assertEquals(cannonicalCollection, result); } public static String prettyPrintLR0ItemSet(final Grammar grammar, Set<LR0Item> itemSet){ SortedSet<LR0Item> sortedSet=new TreeSet<LR0Item>(new Comparator<LR0Item>() { @Override public int compare(LR0Item item1, LR0Item item2) { // Zunächst nach Index der Production sortieren int productionIndex1=grammar.getProductions().indexOf(item1.getProduction()); int productionIndex2=grammar.getProductions().indexOf(item2.getProduction()); int c=productionIndex1-productionIndex2; if (c!=0){ return c; } // Dann nach Index des LR0Item sortieren c=item1.getIndex()-item2.getIndex(); return c; } }); sortedSet.addAll(itemSet); StringBuffer strBuf= new StringBuffer(); for (LR0Item lr0Item : sortedSet) { strBuf.append(lr0Item); strBuf.append("\n"); } return strBuf.toString(); } }