package de.fuberlin.projectci.lrparser; import java.util.Stack; import java.util.logging.Logger; import de.fuberlin.commons.lexer.ILexer; import de.fuberlin.commons.lexer.IToken; import de.fuberlin.commons.parser.ISyntaxTree; import de.fuberlin.commons.util.LogFactory; import de.fuberlin.projectci.grammar.Grammar; import de.fuberlin.projectci.grammar.NonTerminalSymbol; import de.fuberlin.projectci.grammar.Production; import de.fuberlin.projectci.grammar.Symbol; import de.fuberlin.projectci.grammar.TerminalSymbol; import de.fuberlin.projectci.parseTable.AcceptAction; import de.fuberlin.projectci.parseTable.Action; import de.fuberlin.projectci.parseTable.ErrorAction; import de.fuberlin.projectci.parseTable.ParseTable; import de.fuberlin.projectci.parseTable.ReduceAction; import de.fuberlin.projectci.parseTable.ShiftAction; import de.fuberlin.projectci.parseTable.State; public class Driver { private static Logger logger=LogFactory.getLogger(Driver.class); /** Speichert das aktuelle Eingabe-Token, wenn ein ε-Übergang durchgeführt wird */ private IToken storedToken=null; /** * Implementiert den LR-Parse-Algorithmus aus dem Drachenbuch (Algorithmus 4.30/ S. 302 in der 2. deutschen Auflage). * Erweitert um die Erzeugung des (vollständigen) Parsebaums und dessen Reduzierung auf den Abstrakten Syntaxbaum. * Erweitert um die Behandlung von ε-Übergängen * Speichert die gelesenen Token um sie mit den Terminalknoten zu verknüpfen. * LR-Parse-Algorithmus aus dem Drachenbuch : <code> Eingabe: ein Eingabestring w und eine LR-Parsertabelle mit den Funtkionen ACTION und GOTO für eine Grammatik G Ausgabe: wenn w in L(G) ist, die Reduzierungsschritte einer Bottom-Up-Analyse für w, andernfalls eine Fehlermeldung Methode: Zunächst liegt s0, der Ausgangszustand, auf dem Stack und w$ befindet sich im Eingabepuffer. Dann führt der Parser das folgende Programm aus: Sei a das erste Symbol von w$; while(1){ Sei s der oberste Zustand auf dem Stack; if (ACTION[s,a] = shift t){ verschiebe t auf den Stack; Sei a das nächste Eingabesymbol; } else if (ACTION[s,a] = reduce A → β){ Entferne |β| Symbole vom Stack; Zustand t liegt jetzt oben auf dem Stack; verschiebe GOTO[t,A] auf den Stack; gib die Produktion A → β aus; } else if (ACTION[s,a] = accept){ break; // Analyse ist beendet } else{ Fehlerbehandlung aufrufen; } } </code> * @param lexer ILexer für die Eingabe * @param parseTable LR-Parsertabelle mit den Funtkionen ACTION und GOTO * @return den vollständigen (konkreten) Parsebaum * @throws LRParserException falls kein Parsebaum erzeugt werden konnte. */ public ISyntaxTree parse(ILexer lexer, ParseTable parseTable) { Stack<IToken> tokenStack= new Stack<IToken>(); Stack<SyntaxTreeNode> nodeStack=new Stack<SyntaxTreeNode>(); Stack<State> stateStack=new Stack<State>(); stateStack.push(parseTable.getInitialState()); IToken currentToken=readNextToken(lexer); // tokenStack.push(currentToken); TerminalSymbol currentTerminalSymbol=new TerminalSymbol(currentToken.getText()); while(true){ State currentState=stateStack.peek(); Action currentAction=parseTable.getAction(currentState, currentTerminalSymbol); // TODO printConfiguration():String und logConfiguration:boolean logger.finer(stateStack+" : " +currentToken+" : "+currentAction); if (currentAction instanceof ShiftAction){ State targetState=((ShiftAction)currentAction).getTargetState(); stateStack.push(targetState); tokenStack.push(currentToken); currentToken=readNextToken(lexer); currentTerminalSymbol=new TerminalSymbol(currentToken.getText()); } else if (currentAction instanceof ReduceAction){ Production p=((ReduceAction)currentAction).getProduction(); for (int i = 0; i < p.getRhs().size(); i++) { stateStack.pop(); } State s=stateStack.peek(); State targetState=parseTable.getGoto(s, p.getLhs()).getTargetState(); stateStack.push(targetState); // AST aufbauen // SyntaxTreeNode für das NonTerminalSymbol im Kopf der reduzierten Produktion anlegen SyntaxTreeNode currentNode=new SyntaxTreeNode(p.getLhs()); for (int i = p.getRhs().size()-1; i >=0; i--) { // Für jedes Symbol im Rumpf der reduzierten Produktion (von rechts nach links) Symbol aSymbol=p.getRhs().get(i); // Zu jedem Symbol im Rumpf der reduzierten Produktion liegt der zugehörige Knoten (in umgedrehter Reihenfolge - s.o.) auf dem Stack SyntaxTreeNode aChildNode=null; if (aSymbol instanceof TerminalSymbol){ // Für TerminalSymbol einen einfachen Knoten (Blatt) mit dem zugehörigen Token anlegen aChildNode=new SyntaxTreeNode(tokenStack.pop(), (TerminalSymbol) aSymbol); } else if (aSymbol instanceof NonTerminalSymbol){ // Knoten für NonTerminalSymbol vom Stack nehmen aChildNode=nodeStack.pop(); } currentNode.insertTree(aChildNode); } // Neuen Knoten auf den Stack legen nodeStack.push(currentNode); } else if (currentAction instanceof AcceptAction){ logger.finer("Done."); // Wurzel des Parsebaums vom Stack holen SyntaxTreeNode syntaxTree= nodeStack.pop(); return syntaxTree; } else if (currentAction instanceof ErrorAction){ // Zum aktuellen Eingabe-Token konnte keine gültige Action ermittelt werden // Daher wird jetzt erstmal versucht ob es eine Action für ε gibt. if (!(parseTable.getAction(currentState, Grammar.EPSILON) instanceof ErrorAction)){ // Es gibt eine Action für ε --> Aktuelles Eingabetoken zurückstellen // TODO Prüfen, ob dies die richtige Art und Weise ist um einen ε-Übergang herzustellen // (Der Syntaxbaum für ein einfaches Programm sah jedenfalls richtig aus...) storedToken=currentToken; currentToken=new EpsilonToken(currentToken.getLineNumber(), currentToken.getOffset()); currentTerminalSymbol=Grammar.EPSILON; // if (!tokenStack.isEmpty()){ // tokenStack.pop(); // } // tokenStack.push(currentToken); continue; } logger.warning("Parse Error: Unexpected Token "+currentToken.getText()+" in line "+currentToken.getLineNumber()+":"+currentToken.getOffset()); StringBuffer strBufPossibleTokens=new StringBuffer(); int i=0; for(TerminalSymbol t : parseTable.getActionTableForState(currentState).getAllLegalTerminals()) { if (i>0)strBufPossibleTokens.append(", "); strBufPossibleTokens.append(t); i++; } logger.warning("Parse Error: Expected Tokens are: "+strBufPossibleTokens); throw new LRParserException("Parse Error: Unexpected Token "+currentToken.getText()+" in line "+currentToken.getLineNumber()+":"+currentToken.getOffset()); } } } /** Liest das nächste Token */ private IToken readNextToken(ILexer lexer){ if (storedToken!=null){ IToken result=storedToken; storedToken=null; return result; } IToken nextToken=lexer.getNextToken(); return nextToken; } /** * IToken-Implementierung für Epsilon. * */ private static class EpsilonToken implements IToken{ private int lineNumber; private int offset; public EpsilonToken(int lineNumber, int offset) { this.lineNumber=lineNumber; this.offset=offset; } @Override public String getType() { // EpsilonToken wird nur intern verwendet und type brauchen wir nicht... return null; } @Override public String getText() { return Grammar.EMPTY_STRING; } @Override public Object getAttribute() { return null; } @Override public int getOffset() { return offset; } @Override public int getLineNumber() { return lineNumber; } public String toString(){ return "<" + getText() + ", >"; } } }