/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain other free and open source software ("FOSS") code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.parsing.experimental;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import com.aptana.ide.lexer.Lexeme;
import com.aptana.ide.parsing.bnf.Action;
import com.aptana.ide.parsing.bnf.IReductionContext;
import com.aptana.ide.parsing.bnf.IReductionHandler;
import com.aptana.ide.parsing.bnf.Item;
import com.aptana.ide.parsing.bnf.ReductionContext;
import com.aptana.ide.parsing.bnf.State;
import com.aptana.ide.parsing.bnf.TerminalList;
import com.aptana.ide.parsing.bnf.nodes.GrammarNode;
import com.aptana.ide.parsing.bnf.nodes.GrammarNodeTypes;
import com.aptana.ide.parsing.bnf.nodes.IGrammarNode;
import com.aptana.ide.parsing.bnf.nodes.ProductionNode;
import com.aptana.ide.parsing.bnf.nodes.SequenceNode;
/**
* @author Kevin Lindsey
*/
public class Parser
{
private State[] _states;
private GrammarNode _grammar;
private String _message;
private List<IReductionHandler> _handlers;
/**
* addHandler
*
* @param handler
*/
public void addHandler(IReductionHandler handler)
{
if (this._handlers == null)
{
this._handlers = new ArrayList<IReductionHandler>();
}
this._handlers.add(handler);
}
/**
* generateTable
*
* @param grammar
*/
public void generateTable(GrammarNode grammar)
{
GrammarNode expandedGrammar = grammar.getExpandedGrammar();
this._grammar = expandedGrammar;
Item startItem = new Item((ProductionNode) expandedGrammar.getChild(0));
// state cache used to prevent the creation of duplicate states
Map<String,State> stateMap = new HashMap<String,State>();
List<State> states = new ArrayList<State>();
states.add(new State(expandedGrammar, startItem));
int index = 0;
while (index < states.size())
{
State currentState = states.get(index);
List<IGrammarNode> transitions = currentState.getTransitionInputs();
for (int i = 0; i < transitions.size(); i++)
{
IGrammarNode transition = transitions.get(i);
String name = transition.getName();
State newState = currentState.getTransitionState(transition, stateMap);
if (newState.getIndex() == -1)
{
// state is new
newState.setIndex(states.size());
states.add(newState);
}
if (transition.getTypeIndex() == GrammarNodeTypes.NONTERMINAL)
{
currentState.addGoto(name, newState.getIndex());
}
else
{
currentState.addShift(name, newState.getIndex());
}
}
index++;
}
for (int i = 0; i < states.size(); i++)
{
State state = states.get(i);
Item[] items = state.getItems();
for (int j = 0; j < items.length; j++)
{
Item item = items[j];
if (item.isCompletedItem())
{
String name = item.getName();
if (expandedGrammar.getStartingName().equals(name))
{
state.addAccept("$");
}
else
{
int newState = item.getIndex();
TerminalList terminals = expandedGrammar.getFollow(name);
for (int k = 0; k < terminals.size(); k++)
{
String terminalName = terminals.get(k).getName();
state.addReduce(terminalName, newState);
}
}
}
}
}
this._states = states.toArray(new State[states.size()]);
}
/**
* getGrammar
*
* @return
*/
public GrammarNode getGrammar()
{
return this._grammar;
}
/**
* getMessage
*
* @return
*/
public String getMessage()
{
return this._message;
}
/**
* getStates
*
* @return
*/
public State[] getStates()
{
return this._states;
}
/**
* parse
*
* @param lexemes
* @return boolean
*/
public boolean parse(Lexeme[] lexemes)
{
boolean accept = false;
boolean error = false;
int lexemeIndex = 0;
Stack<State> states = new Stack<State>();
State currentState = this._states[0];
states.push(currentState);
Lexeme currentLexeme = lexemes[lexemeIndex];
String currentSymbol = currentLexeme.getType();
this._message = "";
this.resetHandlers();
while (accept == false && error == false && lexemeIndex < lexemes.length)
{
Action action = currentState.getAction(currentSymbol);
switch (action.type)
{
case SHIFT:
this.pushCurrentLexeme(currentLexeme);
lexemeIndex++;
if (lexemeIndex < lexemes.length)
{
currentLexeme = lexemes[lexemeIndex];
currentSymbol = currentLexeme.getType();
}
else
{
currentLexeme = null;
currentSymbol = null;
}
currentState = this._states[action.newState];
states.push(currentState);
break;
case REDUCE:
ProductionNode production = (ProductionNode) this._grammar.getChild(action.newState);
String name = production.getName();
int symbolCount = production.getSymbolCount();
for (int i = 0; i < symbolCount; i++)
{
states.pop();
}
callHandlers(production, symbolCount);
Action newAction = states.peek().getAction(name);
currentState = this._states[newAction.newState];
states.push(currentState);
break;
case ACCEPT:
accept = true;
break;
case GOTO:
this._message = "Unexpected goto at " + currentLexeme + " in state " + currentState.getIndex();
error = true;
break;
case ERROR:
this._message = "No transition for " + currentLexeme + " in state " + currentState.getIndex();
error = true;
break;
default:
break;
}
}
return accept;
}
/**
* callHandlers
*
* @param production
* @param symbolCount
*/
private void callHandlers(ProductionNode production, int symbolCount)
{
if (this._handlers != null)
{
SequenceNode rule = (SequenceNode) production.getChild(0);
IReductionContext context = new ReductionContext(production.getName(), rule);
Iterator<IReductionHandler> iter = this._handlers.iterator();
while (iter.hasNext())
{
iter.next().reduce(context);
}
}
}
/**
* pushCurrentLexeme
*
* @param currentLexeme
*/
private void pushCurrentLexeme(Lexeme currentLexeme)
{
if (this._handlers != null)
{
Iterator<IReductionHandler> iter = this._handlers.iterator();
while (iter.hasNext())
{
iter.next().push(currentLexeme);
}
}
}
/**
* removeHandler
*
* @param handler
*/
public void removeHandler(IReductionHandler handler)
{
if (this._handlers != null)
{
this._handlers.remove(handler);
}
}
/**
* resetHandlers
*/
public void resetHandlers()
{
if (this._handlers != null)
{
Iterator<IReductionHandler> iter = this._handlers.iterator();
while (iter.hasNext())
{
iter.next().beforeParse(null, null);
}
}
}
}