/*******************************************************************************
* Copyright © 2000, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.edt.ide.core.internal.errors;
import java.util.List;
import java.util.Stack;
/**
* @author winghong
*
* To change the template for this generated type comment go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
public class ParseStack implements Cloneable {
private ErrorGrammar grammar = ErrorGrammar.getInstance();
private Stack stack = new Stack();
public ParseStack(){
stack.push(new ParseStackEntry(grammar.getStartState(), new DummyParseNode()));
}
private boolean isTerminalOrConnector(ParseNode node) {
// If a node is not a terminal, it is a connector iff it is whitespace
return node.isTerminal() || node.isWhiteSpace();
}
/**
* @param subtreeRoot
* @return null if there is no terminal or whitespace connector in the subtree
*/
private NonTerminalNode findRMConnectorOrTerminalParent(NonTerminalNode subtreeRoot) {
ParseNode[] children = subtreeRoot.children;
// For an epsilon node, we return null
if (children == null) {
return null;
}
int position = children.length - 1;
while (position >= 0) {
ParseNode child = children[position];
if(isTerminalOrConnector(child)) {
return subtreeRoot;
}
else {
// We know that child must be a nonterminal at this point
// See whether there is a connector or whitespace node in the subtree rooted at child
NonTerminalNode result = findRMConnectorOrTerminalParent((NonTerminalNode) child);
if (result != null) {
return result;
}
}
position--;
}
return null;
}
private int findConnectorOrTerminalPos(NonTerminalNode parent) {
ParseNode[] children = parent.children;
for(int i = children.length - 1; i >= 0; i--) {
if(isTerminalOrConnector(children[i])) {
return i;
}
}
// We assume that there is a connector or terminal child
throw new IllegalArgumentException();
}
private void addToConnector(NonTerminalNode connector, ParseNode whitespaceNode) {
// Create a whitespace pair holding both the original whitespace and the added whitespace
NonTerminalNode wsPair = new NonTerminalNode(
ErrorNodeTypes.wsPair,
new ParseNode[] {connector.children[1], whitespaceNode}
);
connector.children[1] = wsPair;
}
// TODO implement these methods
public void connect(ParseNode whitespaceNode) {
// Do nothing if whitespaceNode is null
if(whitespaceNode == null) {
return;
}
for(int distanceFromTop = 0;;distanceFromTop++) {
ParseStackEntry stackEntry = (ParseStackEntry) stack.elementAt(stack.size() - 1 - distanceFromTop);
ParseNode node = stackEntry.node;
// Found terminal on stack
if(node.isTerminal()) {
// Replace terminal on stack with connector
stackEntry.node = createConnector((TerminalNode) node, whitespaceNode);
return;
}
// Found connector on stack
if(node.isWhiteSpace()) {
// Add new whitespace in addition to the original whitespaces
addToConnector((NonTerminalNode) node, whitespaceNode);
return;
}
// See if we will find a parent of a connector or whitespace in the subtree rooted at node
NonTerminalNode parent = findRMConnectorOrTerminalParent((NonTerminalNode) node);
if(parent != null) {
ParseNode terminalOrConnector = parent.children[findConnectorOrTerminalPos(parent)];
if(terminalOrConnector.isTerminal()) {
// Replace terminal node child with connector
parent.children[findConnectorOrTerminalPos(parent)] =
createConnector((TerminalNode) terminalOrConnector, whitespaceNode);
return;
}
else {
// Add new whitespace in addition to the original whitespaces
addToConnector((NonTerminalNode) terminalOrConnector, whitespaceNode);
return;
}
}
}
}
private NonTerminalNode createConnector(TerminalNode terminalNode, ParseNode whitespaceNode) {
return new NonTerminalNode(ErrorNodeTypes.connector, new ParseNode[] {terminalNode, whitespaceNode});
}
public void shift(ParseNode node) {
int shiftedState;
if(node.isTerminal()) {
TerminalNode terminalNode = (TerminalNode) node;
shiftedState = grammar.getTerminalAction(getCurrentState(), terminalNode) - 1;
}
else {
// Node must be nonterminal
NonTerminalNode nonTerminalNode = (NonTerminalNode) node;
shiftedState = grammar.getGotoState(getCurrentState(), nonTerminalNode);
}
if(shiftedState < 0) {
throw new IllegalArgumentException();
}
Reporter.getInstance().shift(node, shiftedState);
stack.push(new ParseStackEntry(shiftedState, node));
}
public void reduce(int ruleNumber) {
Reporter.getInstance().reduce(ruleNumber);
// Pop the nodes of the stack and put them in an array
int handleSize = grammar.getHandleSize(ruleNumber);
ParseNode[] children = handleSize == 0 ? null : new ParseNode[handleSize];
for (int i = handleSize - 1; i >= 0; i--) {
ParseStackEntry stackEntry = (ParseStackEntry) stack.pop();
children[i] = stackEntry.node;
}
// Create the parent nonterminal node
int nonTerminalType = grammar.getLHS(ruleNumber);
NonTerminalNode parent = new NonTerminalNode(nonTerminalType, children);
parent.ruleNumber = ruleNumber;
// Shift the parent back on the stack
int gotoState = grammar.getGotoState(getCurrentState(), parent);
shift(parent);
}
public int getCurrentState() {
ParseStackEntry stackEntry = (ParseStackEntry) stack.peek();
return stackEntry.state;
}
public ParseNode getTopOfStackNode() {
ParseStackEntry stackEntry = (ParseStackEntry) stack.peek();
return stackEntry.node;
}
public ParseStack copy() {
try {
ParseStack result = (ParseStack) super.clone();
result.stack = (Stack) stack.clone();
return result;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append('[');
buffer.append(stack.elementAt(0).toString());
for (int i = 1; i < stack.size(); i++) {
buffer.append(' ');
buffer.append(stack.elementAt(i).toString());
}
buffer.append(']');
return buffer.toString();
}
/**
* @param contextDeleted
* @return an array of length 0 if contextDeleted is 0
*/
public ParseNode[] deleteContext(int contextDeleted) {
List stackEntries = stack.subList(stack.size() - contextDeleted, stack.size());
ParseNode[] result = new ParseNode[contextDeleted];
for(int i = 0; i < contextDeleted; i++) {
ParseStackEntry stackEntry = (ParseStackEntry) stackEntries.get(i);
result[i] = stackEntry.node;
}
// Remember to actually "pop" the entries
stackEntries.clear();
return result;
}
public int availableContext() {
return stack.size() - 1;
}
public boolean isTerminalShiftable(int terminalType) {
// Optimization: Avoid copying the stack if the terminal is immediately shiftable or
// immediately of error
int action = grammar.getTerminalAction(getCurrentState(), terminalType);
if(action > 0) {
return true;
}
else if(action == 0) {
return false;
}
else {
// Make a copy of the stack, perform all the reductions and test again
ParseStack copiedStack = this.copy();
copiedStack.performAllReductions(terminalType);
return copiedStack.isTerminalShiftable(terminalType);
}
}
public void performAllReductions(int terminalType) {
while(true) {
int action = grammar.getTerminalAction(getCurrentState(), terminalType);
if(action < 0) {
// We shall never reduce by the start production because we are will not shift
// by EOF
int ruleNumber = -(action) - 1;
reduce(ruleNumber);
}
else {
return;
}
}
}
// TODO The following is experimental
public void performDefaultReductions() {
while(true) {
int defaultRule = grammar.getLRZeroReduceRule(getCurrentState());
if(defaultRule > 0) {
reduce(defaultRule);
}
else {
break;
}
}
}
/**
* @return
*/
public Stack getStack() {
return stack;
}
}