/*******************************************************************************
* 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.Stack;
import org.eclipse.edt.ide.core.internal.model.document.EGLNodeNameUtility;
/**
* @author winghong
*
* To change the template for this generated type comment go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
public abstract class AbstractRecoverer {
public static final int SUCCESS_DISTANCE = 3;
protected ErrorGrammar grammar = ErrorGrammar.getInstance();
public abstract int recoverDistance(ParseStack stack, TokenStream tokenStream);
public abstract void recover(ParseStack stack, TokenStream tokenStream);
public abstract void errorMessage(ParseStack stack, TokenStream tokenStream, TerminalNode errorTerminal);
// TODO Move grammatical information into Grammar file
protected static int[] IMPORTANT_TERMINALS = {
ErrorNodeTypes.RECORD,
ErrorNodeTypes.PROGRAM,
ErrorNodeTypes.FUNCTION,
ErrorNodeTypes.RPAREN,
ErrorNodeTypes.END
};
protected static int[] UNDELETABLE_TERMINALS = {
ErrorNodeTypes.RECORD,
ErrorNodeTypes.PROGRAM,
ErrorNodeTypes.FUNCTION,
ErrorNodeTypes.EOF
};
protected static int[] SCOPE_CLOSERS = {
ErrorNodeTypes.RPAREN,
ErrorNodeTypes.END
};
public static boolean isUndeletableTerminal(TerminalNode terminalNode) {
return isInList(terminalNode.terminalType, UNDELETABLE_TERMINALS);
}
private static boolean isInList(int number, int[] list) {
for (int i = 0; i < list.length; i++) {
if(list[i] == number) {
return true;
}
}
return false;
}
public static boolean isImportantTerminal(TerminalNode terminalNode) {
return isInList(terminalNode.terminalType, IMPORTANT_TERMINALS);
}
protected static boolean isScopeCloser(TerminalNode terminalNode) {
return isInList(terminalNode.terminalType, SCOPE_CLOSERS);
}
protected static boolean isScopeCloser(int terminalType) {
return isInList(terminalType, SCOPE_CLOSERS);
}
protected int tryParseAhead(ParseStack stack, TokenStream tokenStream) {
// See how far we can parse
int shifted = 0;
while(true) {
int action = grammar.getTerminalAction(stack.getCurrentState(), tokenStream.lookAhead());
if(action > 0) {
// If we are shifting an important terminal, we are done
if(isImportantTerminal(tokenStream.lookAhead())) {
return Integer.MAX_VALUE;
}
// Shift
stack.shift(tokenStream.lookAhead());
tokenStream.shift();
shifted++;
}
else if(action < 0) {
// Reduce
int ruleNumber = -(action) - 1;
// If we are reducing by the start rule, we return "infinity"
if(ruleNumber == 0) {
return Integer.MAX_VALUE;
}
stack.reduce(ruleNumber);
// // If we are creating a "significant" node, we return infinity
// int nonTerminal = grammar.getLHS(ruleNumber);
// if(Character.isUpperCase(ErrorNodeTypesConverter.getNonterminalTokenForInt(nonTerminal).charAt(0))) {
// return Integer.MAX_VALUE;
// }
}
else {
return shifted;
}
}
}
protected boolean processTerminal(ParseStack stack, TerminalNode terminalNode) {
// We should not try to process EOF
if(terminalNode.terminalType == ErrorNodeTypes.EOF) {
throw new IllegalArgumentException();
}
// First perform all reductions possible
performAllReductions(stack, terminalNode);
// Now see whether we can shift this terminal
int action = grammar.getTerminalAction(stack.getCurrentState(), terminalNode);
if(action > 0) {
// We can shift
stack.shift(terminalNode);
return true;
}
else {
// We cannot shift
return false;
}
}
protected void performAllReductions(ParseStack stack, TerminalNode terminalNode) {
while(true) {
int action = grammar.getTerminalAction(stack.getCurrentState(), terminalNode);
if(action < 0) {
// We shall never reduce by the start production because we are will not shift
// by EOF
int ruleNumber = -(action) - 1;
stack.reduce(ruleNumber);
}
else {
return;
}
}
}
/**
* EGL Normally, the highest symbol of a given terminal is the nonterminal closest to the root of
* the tree that chain-derives the terminal.
*
* This is not optimal for our use. For example, the rule of a primitive type is below:
*
* primitiveType -> PRIMITIVE precision (where prevision is nullable)
*
* If the user have a test case like "DataItem MyDataItem cha End", we would like to say that
* "cha" is not a valid primitive type.
*
* As a result, we are extending the meaning of the highest symbol of a given terminal to the
* nonterminal closest to the root that have the terminal as its sole yield.
*/
protected ParseNode getHighestSymbol(ParseStack stack, TerminalNode lookahead) {
int poppable = 1;
while(true) {
int action = grammar.getTerminalAction(stack.getCurrentState(), lookahead);
if(action > 0) {
// We are about to shift, so the highest node is on the top of the stack
break;
}
else if(action < 0) {
int ruleNumber = -(action) - 1;
int handleSize = grammar.getHandleSize(ruleNumber);
if(handleSize > poppable) break;
stack.reduce(ruleNumber);
poppable = poppable - handleSize + 1;
}
else {
// Error situations should not happen
break;
}
}
stack.deleteContext(poppable - 1);
return stack.getTopOfStackNode();
}
protected String getHighestSymbolName(ParseStack stack, TerminalNode lookahead) {
ParseNode highestNode = getHighestSymbol(stack, lookahead);
if(highestNode.isTerminal()) {
TerminalNode terminalNode = (TerminalNode) highestNode;
return EGLNodeNameUtility.getTerminalName(terminalNode.terminalType);
}
else {
NonTerminalNode nonTerminalNode = (NonTerminalNode) highestNode;
return EGLNodeNameUtility.getNonterminalName(nonTerminalNode.nonTerminalType);
}
}
protected TerminalNode findFirstNonWSTerminal(ParseNode[] nodes) {
Stack nodeStack = new Stack();
// Delete the contexts except for the last one
for (int i = nodes.length - 1; i >= 0; i--) {
nodeStack.push(nodes[i]);
}
// Find the first terminal among these entries
while(!nodeStack.isEmpty()) {
ParseNode parseNode = (ParseNode) nodeStack.pop();
if(parseNode.isTerminal()) {
if(parseNode.isWhiteSpace()) {
continue;
}
else {
return (TerminalNode) parseNode;
}
}
// We know now that parseNode is a non terminal
NonTerminalNode nonTerminalNode = (NonTerminalNode) parseNode;
if(nonTerminalNode.children != null) {
for (int i = nonTerminalNode.children.length - 1; i >= 0; i--) {
nodeStack.push(nonTerminalNode.children[i]);
}
}
}
return null;
}
/**
* This method chains up the nodes by reducing them into a binary tree of wsPairs
* TODO this method perhaps shouldn't be here
* @param nodes
*/
protected ParseNode chainNodes(ParseNode[] nodes) {
int size = nodes.length;
while(size > 1) {
// Pair up all the whitespaces node
for (int i = 0; i < size / 2; i++) {
nodes[i] = new NonTerminalNode(
ErrorNodeTypes.wsPair,
new ParseNode[] {nodes[i*2], nodes[i*2+1]}
);
}
// Deal with the odd one out and set up the new size
if(size % 2 > 0) {
nodes[size / 2] = nodes[size - 1];
size = size / 2 + 1;
}
else {
size = size / 2;
}
}
// Return null if the argument array is empty
return size > 0 ? nodes[0] : null;
}
}