/*******************************************************************************
 * Copyright (c) 2006, 2008 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.cdt.core.dom.lrparser.lpgextensions;

import lpg.lpgjavaruntime.BadParseException;
import lpg.lpgjavaruntime.BadParseSymFileException;
import lpg.lpgjavaruntime.ConfigurationElement;
import lpg.lpgjavaruntime.ConfigurationStack;
import lpg.lpgjavaruntime.IntTuple;
import lpg.lpgjavaruntime.Monitor;
import lpg.lpgjavaruntime.NotBacktrackParseTableException;
import lpg.lpgjavaruntime.ParseTable;
import lpg.lpgjavaruntime.PrsStream;
import lpg.lpgjavaruntime.RuleAction;
import lpg.lpgjavaruntime.Stacks;
import lpg.lpgjavaruntime.TokenStream;

public class FixedBacktrackingParser extends Stacks
{
    private Monitor monitor = null;
    private int START_STATE,
                NUM_RULES,
                LA_STATE_OFFSET,
                EOFT_SYMBOL,
                ERROR_SYMBOL,
                ACCEPT_ACTION,
                ERROR_ACTION;

    private int lastToken,
                currentAction;
    private TokenStream tokStream;
    private ParseTable prs;
    private RuleAction ra;
    private IntTuple action = new IntTuple(1 << 10),
                     tokens;
    private int actionStack[];
    private boolean skipTokens = false;	// true if error productions are used to skip tokens

    //
    // Override the getToken function in Stacks.
    //
    public final int getToken(int i)
    {
        return tokens.get(locationStack[stateStackTop + (i - 1)]);
    }

    public final int getCurrentRule()     { return currentAction; }
    public final int getFirstToken()      { return tokStream.getFirstErrorToken(getToken(1)); }
    public final int getFirstToken(int i) { return tokStream.getFirstErrorToken(getToken(i)); }
    public final int getLastToken()       { return tokStream.getLastErrorToken(lastToken); }
    public final int getLastToken(int i)  { int l = (i >= prs.rhs(currentAction)
                                                        ? lastToken
                                                        : tokens.get(locationStack[stateStackTop + i] - 1));
                                            return tokStream.getLastErrorToken(l);
                                          }

    public FixedBacktrackingParser(TokenStream tokStream, ParseTable prs, RuleAction ra) throws BadParseSymFileException,
                                                                                           NotBacktrackParseTableException
    {
        this.tokStream = (PrsStream) tokStream;
        this.prs = prs;
        this.ra = ra;

        START_STATE = prs.getStartState();
        NUM_RULES = prs.getNumRules();
        LA_STATE_OFFSET = prs.getLaStateOffset();
        EOFT_SYMBOL = prs.getEoftSymbol();
        ERROR_SYMBOL = prs.getErrorSymbol();
        ACCEPT_ACTION = prs.getAcceptAction();
        ERROR_ACTION = prs.getErrorAction();

        if (! prs.isValidForParser()) throw new BadParseSymFileException();
        if (! prs.getBacktrack()) throw new NotBacktrackParseTableException();
    }

    public FixedBacktrackingParser(Monitor monitor, TokenStream tokStream, ParseTable prs, RuleAction ra) throws BadParseSymFileException,
                                                                                                            NotBacktrackParseTableException
    {
        this(tokStream, prs, ra);
        this.monitor = monitor;
    }

    //
    // Allocate or reallocate all the stacks. Their sizes should always be the same.
    //
    public void reallocateOtherStacks(int start_token_index)
    {
        // assert(super.stateStack != null);
        if (this.actionStack == null)
        {
            this.actionStack = new int[super.stateStack.length];
            super.locationStack = new int[super.stateStack.length];
            super.parseStack = new Object[super.stateStack.length];

            actionStack[0] = 0;
            locationStack[0] = start_token_index;
        }
        else if (this.actionStack.length < super.stateStack.length)
        {
            int old_length = this.actionStack.length;

            System.arraycopy(this.actionStack, 0, this.actionStack = new int[super.stateStack.length], 0, old_length);
            System.arraycopy(super.locationStack, 0, super.locationStack = new int[super.stateStack.length], 0, old_length);
            System.arraycopy(super.parseStack, 0, super.parseStack = new Object[super.stateStack.length], 0, old_length);
        }
        return;
    }

    //
    // Parse without attempting any Error token recovery
    //
    public Object parse() throws BadParseException
    {
        // without an argument parse() will ignore error productions
    	return parse(0);
    }

    //
    // Parse input allowing up to max_error_count Error token recoveries.
    // When max_error_count is 0, no Error token recoveries occur.
    // When max_error is > 0, it limits the number of Error token recoveries.
    // When max_error is < 0, the number of error token recoveries is unlimited.
    // Also, such recoveries only require one token to be parsed beyond the recovery point.
    // (normally two tokens beyond the recovery point must be parsed)
    // Thus, a negative max_error_count should be used when error productions are used to 
    // skip tokens.
    //
    public Object parse(int max_error_count) throws BadParseException
    {
        action.reset();
        tokStream.reset(); // Position at first token.
        reallocateStateStack();
        stateStackTop = 0;
        stateStack[0] = START_STATE;
        skipTokens = max_error_count < 0;

        //
        // The tuple tokens will eventually contain the sequence 
        // of tokens that resulted in a successful parse. We leave
        // it up to the "Stream" implementer to define the predecessor
        // of the first token as he sees fit.
        //
        tokens = new IntTuple(tokStream.getStreamLength());
        tokens.add(tokStream.getPrevious(tokStream.peek()));

        int repair_token = 0,
            start_token_index = tokStream.peek(),
            start_action_index = action.size(), // obviously 0
            temp_stack[] = new int[1];
        temp_stack[0] = START_STATE;
        int initial_error_token = backtrackParse(repair_token);
        for (int error_token = initial_error_token, count = 0;
             error_token != 0;
             error_token = backtrackParse(repair_token), count++)
        {
            if (count == max_error_count)
                throw new BadParseException(initial_error_token);
            action.reset(start_action_index);
            tokStream.reset(start_token_index);
            stateStackTop = temp_stack.length - 1;
            System.arraycopy(temp_stack, 0, stateStack, 0, temp_stack.length);
            reallocateOtherStacks(start_token_index);

            backtrackParseUpToError(repair_token, error_token);

            for (stateStackTop = findRecoveryStateIndex(stateStackTop);
                 stateStackTop >= 0;
                 stateStackTop = findRecoveryStateIndex(stateStackTop - 1))
            {
                int recovery_token = tokens.get(locationStack[stateStackTop] - 1);
                repair_token = errorRepair((recovery_token >= start_token_index ? recovery_token : error_token), error_token);
                if (repair_token != 0)
                    break;
            }

            if (stateStackTop < 0)
                throw new BadParseException(initial_error_token);

            temp_stack = new int[stateStackTop + 1];
            System.arraycopy(stateStack, 0, temp_stack, 0, temp_stack.length);

            start_action_index = action.size();
            start_token_index = tokStream.peek();
        }

        if (repair_token != 0)
            tokens.add(repair_token);
        int t;
        for (t = start_token_index; tokStream.getKind(t) != EOFT_SYMBOL; t = tokStream.getNext(t))
            tokens.add(t);
        tokens.add(t);

        return parseActions();
    }

    //
    // Process reductions and continue...
    //
    private final void process_reductions()
    {
        do
        {
            stateStackTop -= (prs.rhs(currentAction) - 1);
            ra.ruleAction(currentAction);
            currentAction = prs.ntAction(stateStack[stateStackTop], prs.lhs(currentAction));
        } while(currentAction <= NUM_RULES);
        return;
    }

    //
    // Now do the final parse of the input based on the actions in
    // the list "action" and the sequence of tokens in list "tokens".
    //
    private Object parseActions() throws BadParseException
    {
        int ti = -1,
            curtok;
        lastToken = tokens.get(++ti);
        curtok = tokens.get(++ti);
        allocateOtherStacks();

        //
        // Reparse the input...
        //
        stateStackTop = -1;
        currentAction = START_STATE;
        for (int i = 0; i < action.size(); i++)
        {
            //
            // if the parser needs to stop processing,
            // it may do so here.
            //
            if (monitor != null && monitor.isCancelled())
                return null;

            stateStack[++stateStackTop] = currentAction;
            locationStack[stateStackTop] = ti;

            currentAction = action.get(i);
            if (currentAction <= NUM_RULES)  // a reduce action?
            {
                stateStackTop--; // make reduction look like shift-reduction
                process_reductions();
            }
            else                   // a shift or shift-reduce action
            {
                lastToken = curtok;
                curtok = tokens.get(++ti);
                if (currentAction > ERROR_ACTION) // a shift-reduce action?
                {
                    currentAction -= ERROR_ACTION;
                    process_reductions();
                }
            }
        }

        return parseStack[0];
    }

    //
    // Process reductions and continue...
    //
    private int process_backtrack_reductions(int act)
    {
        do
        {
            stateStackTop -= (prs.rhs(act) - 1);
            act = prs.ntAction(stateStack[stateStackTop], prs.lhs(act));
        } while(act <= NUM_RULES);

        return act;
    }

    //
    // Parse the input until either the parse completes successfully or
    // an error is encountered. This function returns an integer that
    // represents the last action that was executed by the parser. If
    // the parse was succesful, then the tuple "action" contains the
    // successful sequence of actions that was executed.
    //
    private int backtrackParse(int initial_token)
    {
        //
        // Allocate configuration stack.
        //
        ConfigurationStack configuration_stack = new ConfigurationStack(prs);

        //
        // Keep parsing until we successfully reach the end of file or
        // an error is encountered. The list of actions executed will
        // be stored in the "action" tuple.
        //
        int error_token = 0,
            maxStackTop = stateStackTop,
            start_token = tokStream.peek(),
            curtok = (initial_token > 0 ? initial_token : tokStream.getToken()),
            current_kind = tokStream.getKind(curtok),
            act = tAction(stateStack[stateStackTop], current_kind);

        //
        // The main driver loop
        //
        for (;;)
        {
            //
            // if the parser needs to stop processing,
            // it may do so here.
            //
            if (monitor != null && monitor.isCancelled())
                return 0;

            if (act <= NUM_RULES)
            {
                action.add(act); // save this reduce action
                stateStackTop--;
                act = process_backtrack_reductions(act);
            }
            else if (act > ERROR_ACTION)
            {
                action.add(act);     // save this shift-reduce action
                curtok = tokStream.getToken();
                current_kind = tokStream.getKind(curtok);
                act = process_backtrack_reductions(act - ERROR_ACTION);
            }
            else if (act < ACCEPT_ACTION)
            {
                action.add(act);    // save this shift action
                curtok = tokStream.getToken();
                current_kind = tokStream.getKind(curtok);
            }
            else if (act == ERROR_ACTION)
            {
                error_token = (error_token > curtok ? error_token : curtok);

                ConfigurationElement configuration = configuration_stack.pop();
                if (configuration == null)
                     act = ERROR_ACTION;
                else
                {
                    action.reset(configuration.action_length);
                    act = configuration.act;
                    curtok = configuration.curtok;
                    current_kind = tokStream.getKind(curtok);
                    tokStream.reset(curtok == initial_token
                                            ? start_token
                                            : tokStream.getNext(curtok));
                    stateStackTop = configuration.stack_top;
                    configuration.retrieveStack(stateStack);
                    continue;
                }
                break;
            }
            else if (act > ACCEPT_ACTION)
            {
                if (configuration_stack.findConfiguration(stateStack, stateStackTop, curtok))
                    act = ERROR_ACTION;
                else
                {
                    configuration_stack.push(stateStack, stateStackTop, act + 1, curtok, action.size());
                    act = prs.baseAction(act);
                    maxStackTop = stateStackTop > maxStackTop ? stateStackTop : maxStackTop;
                }
                continue;
            }
            else break; // assert(act == ACCEPT_ACTION);
            try
            {
                stateStack[++stateStackTop] = act;
            }
            catch(IndexOutOfBoundsException e)
            {
                reallocateStateStack();
                stateStack[stateStackTop] = act;
            }

            act = tAction(act, current_kind);
        }

        //System.out.println("****Number of configurations: " + configuration_stack.configurationSize());
        //System.out.println("****Number of elements in stack tree: " + configuration_stack.numStateElements());
        //System.out.println("****Number of elements in stacks: " + configuration_stack.stacksSize());
        //System.out.println("****Number of actions: " + action.size());
        //System.out.println("****Max Stack Size = " + maxStackTop);
        //System.out.flush();
        return (act == ERROR_ACTION ? error_token : 0);
    }

    private void backtrackParseUpToError(int initial_token, int error_token)
    {
        //
        // Allocate configuration stack.
        //
        ConfigurationStack configuration_stack = new ConfigurationStack(prs);

        //
        // Keep parsing until we successfully reach the end of file or
        // an error is encountered. The list of actions executed will
        // be stored in the "action" tuple.
        //
        int start_token = tokStream.peek(),
            curtok = (initial_token > 0 ? initial_token : tokStream.getToken()),
            current_kind = tokStream.getKind(curtok),
            act = tAction(stateStack[stateStackTop], current_kind);

        tokens.add(curtok);
        locationStack[stateStackTop] = tokens.size();
        actionStack[stateStackTop] = action.size();

        for (;;)
        {
            //
            // if the parser needs to stop processing,
            // it may do so here.
            //
            if (monitor != null && monitor.isCancelled())
                return;

            if (act <= NUM_RULES)
            {
                action.add(act); // save this reduce action
                stateStackTop--;
                act = process_backtrack_reductions(act);
            }
            else if (act > ERROR_ACTION)
            {
                action.add(act);     // save this shift-reduce action
                curtok = tokStream.getToken();
                current_kind = tokStream.getKind(curtok);
                tokens.add(curtok);
                act = process_backtrack_reductions(act - ERROR_ACTION);
            }
            else if (act < ACCEPT_ACTION)
            {
                action.add(act);     // save this shift action
                curtok = tokStream.getToken();
                current_kind = tokStream.getKind(curtok);
                tokens.add(curtok);
            }
            else if (act == ERROR_ACTION)
            {
                if (curtok != error_token)
                {
                    ConfigurationElement configuration = configuration_stack.pop();
                    if (configuration == null)
                        act = ERROR_ACTION;
                    else
                    {
                        action.reset(configuration.action_length);
                        act = configuration.act;
                        int next_token_index = configuration.curtok;
                        tokens.reset(next_token_index);
                        curtok = tokens.get(next_token_index - 1);
                        current_kind = tokStream.getKind(curtok);
                        tokStream.reset(curtok == initial_token
                                                ? start_token
                                                : tokStream.getNext(curtok));
                        stateStackTop = configuration.stack_top;
                        configuration.retrieveStack(stateStack);
                        locationStack[stateStackTop] = tokens.size();
                        actionStack[stateStackTop] = action.size();
                        continue;
                    }
                }
                break;
            }
            else if (act > ACCEPT_ACTION)
            {
                if (configuration_stack.findConfiguration(stateStack, stateStackTop, tokens.size()))
                    act = ERROR_ACTION;
                else
                {
                    configuration_stack.push(stateStack, stateStackTop, act + 1, tokens.size(), action.size());
                    act = prs.baseAction(act);
                }
                continue;
            }
            else break; // assert(act == ACCEPT_ACTION);

            stateStack[++stateStackTop] = act; // no need to check if out of bounds
            locationStack[stateStackTop] = tokens.size();
            actionStack[stateStackTop] = action.size();
            act = tAction(act, current_kind);
        }

        // assert(curtok == error_token);

        return;
    }

    private boolean repairable(int error_token)
    {
        //
        // Allocate configuration stack.
        //
        ConfigurationStack configuration_stack = new ConfigurationStack(prs);

        //
        // Keep parsing until we successfully reach the end of file or
        // an error is encountered. The list of actions executed will
        // be stored in the "action" tuple.
        //
        int start_token = tokStream.peek(),
            final_token = tokStream.getStreamLength(), // unreachable
            curtok = 0,
            current_kind = ERROR_SYMBOL,
            act = tAction(stateStack[stateStackTop], current_kind);

        for (;;)
        {
            if (act <= NUM_RULES)
            {
                stateStackTop--;
                act = process_backtrack_reductions(act);
            }
            else if (act > ERROR_ACTION)
            {
                curtok = tokStream.getToken();
                if (curtok > final_token) return true;
                current_kind = tokStream.getKind(curtok);
                act = process_backtrack_reductions(act - ERROR_ACTION);
            }
            else if (act < ACCEPT_ACTION)
            {
                curtok = tokStream.getToken();
                if (curtok > final_token) return true;
                current_kind = tokStream.getKind(curtok);
            }
            else if (act == ERROR_ACTION)
            {
                ConfigurationElement configuration = configuration_stack.pop();
                if (configuration == null)
                     act = ERROR_ACTION;
                else
                {
                    stateStackTop = configuration.stack_top;
                    configuration.retrieveStack(stateStack);
                    act = configuration.act;
                    curtok = configuration.curtok;
                    if (curtok == 0)
                    {
                        current_kind = ERROR_SYMBOL;
                        tokStream.reset(start_token);
                    }
                    else
                    {
                        current_kind = tokStream.getKind(curtok);
                        tokStream.reset(tokStream.getNext(curtok));
                    }
                    continue;
                }
                break;
            }
            else if (act > ACCEPT_ACTION)
            {
                if (configuration_stack.findConfiguration(stateStack, stateStackTop, curtok))
                    act = ERROR_ACTION;
                else
                {
                    configuration_stack.push(stateStack, stateStackTop, act + 1, curtok, 0);
                    act = prs.baseAction(act);
                }
                continue;
            }
            else break; // assert(act == ACCEPT_ACTION);
            try
            {
                //
                // We consider a configuration to be acceptable for recovery
                // if we are able to consume enough symbols in the remainining
                // tokens to reach another potential recovery point past the
                // original error token.
                //
                if ((curtok > error_token) && (final_token == tokStream.getStreamLength()))
                {
                    //
                    // If the ERROR_SYMBOL is a valid Action Adjunct in the state
                    // "act" then we set the terminating token as the successor of
                    // the current token. I.e., we have to be able to parse at least
                    // two tokens past the resynch point before we claim victory.
                    //
                    if (recoverableState(act))
                        final_token = skipTokens ? curtok : tokStream.getNext(curtok);
                }

                stateStack[++stateStackTop] = act;
            }
            catch(IndexOutOfBoundsException e)
            {
                reallocateStateStack();
                stateStack[stateStackTop] = act;
            }

            act = tAction(act, current_kind);
        }

        //
        // If we can reach the end of the input successfully, we claim victory.
        //
        return (act == ACCEPT_ACTION);
    }

    private boolean recoverableState(int state)
    {
        for (int k = prs.asi(state); prs.asr(k) != 0; k++)
        {
           if (prs.asr(k) == ERROR_SYMBOL)
                return true;
        }
        return false;
    }

    private int findRecoveryStateIndex(int start_index)
    {
        int i;
        for (i = start_index; i >= 0; i--)
        {
            //
            // If the ERROR_SYMBOL is an Action Adjunct in state stateStack[i]
            // then chose i as the index of the state to recover on.
            //
            if (recoverableState(stateStack[i]))
                break;
        }

        if (i >= 0) // if a recoverable state, remove null reductions, if any.
        {
            int k;
            for (k = i - 1; k >= 0; k--)
            {
                if (locationStack[k] != locationStack[i])
                    break;
            }
            i = k + 1;
        }

        return i;
    }

    private int errorRepair(int recovery_token, int error_token)
    {
        int temp_stack[] = new int[stateStackTop + 1];
        System.arraycopy(stateStack, 0, temp_stack, 0, temp_stack.length);
        for (;
             tokStream.getKind(recovery_token) != EOFT_SYMBOL;
             recovery_token = tokStream.getNext(recovery_token))
        {
            tokStream.reset(recovery_token);
            if (repairable(error_token))
                break;
            stateStackTop = temp_stack.length - 1;
            System.arraycopy(temp_stack, 0, stateStack, 0, temp_stack.length);
        }

        if (tokStream.getKind(recovery_token) == EOFT_SYMBOL)
        {
            tokStream.reset(recovery_token);
            if (! repairable(error_token))
            {
                stateStackTop = temp_stack.length - 1;
                System.arraycopy(temp_stack, 0, stateStack, 0, temp_stack.length);
                return 0;
            }
        }

        //
        //
        //
        stateStackTop = temp_stack.length - 1;
        System.arraycopy(temp_stack, 0, stateStack, 0, temp_stack.length);
        tokStream.reset(recovery_token);
        tokens.reset(locationStack[stateStackTop] - 1);
        action.reset(actionStack[stateStackTop]);

        return tokStream.makeErrorToken(tokens.get(locationStack[stateStackTop] - 1),
                                        tokStream.getPrevious(recovery_token),
                                        error_token,
                                        ERROR_SYMBOL);
    }

    private int tAction(int act, int sym)
    {
        act = prs.tAction(act, sym);
        if (act > LA_STATE_OFFSET)
        {
            int next_token = tokStream.peek();
            act = prs.lookAhead(act - LA_STATE_OFFSET, tokStream.getKind(next_token));
            while(act > LA_STATE_OFFSET)
            {
                next_token = tokStream.getNext(next_token);
                act = prs.lookAhead(act - LA_STATE_OFFSET, tokStream.getKind(next_token));
            }
        }
        return act;
    }
}
