/***************************************************************************
    Copyright          : (C) 2002 by Neoworks Limited. All rights reserved
    URL                : http://www.neoworks.com
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

package com.neoworks.rdc;

import java.util.Vector;
import java.lang.reflect.*;

/**
 * The purpose of this abstract class is to provide the framework upon which
 * concrete versions of the class are instantiated. The parser will create handles
 * for lexed input, as well as for the output of target code. There will also be
 * a handle for Source code. Contains the basif functions for getting the next
 * token, matching the next token and generating pars error.
 */
public class Parser
{
	/* The following p s f ints are Lexer defined. They
	 * are declared here for consistency(and so it works)
	 */
	public static final int DUMMY  = Lexer.DUMMY;
	public static final int NUM    = Lexer.NUM;
	public static final int ID     = Lexer.ID;
	public static final int STRING = Lexer.STRING;
	public static final int NONE   = Lexer.NONE;

	/* The look ahead types */
	protected static final int LOOKAHEAD_TYPE = 10;
	protected static final int LOOKAHEAD_VALUE = 11;

	private String sourceCode;          // The input string to be lexed into tokens
	private TokenList lexedInput;       // The input lexed into tokens
	private int inputLength;            // The number of tokens
	private int currentToken;           // The current token being looked at
	
	/**
	 * Set to true to see verbose output
	 */
	protected boolean fVerbose = false;

	/**
	 * Protected constructor to make sure there can be instantiation of the class only by derived classes
	 *
	 * @param sourceCode The source code
	 * @param tokens List of tokens to parse
	 */
	protected Parser( String sourceCode , Vector tokens )
	{
		configure( sourceCode, tokens );
	}

	/**
	 * Configure the parser. This also runs the lexer over the source code.
	 *
	 * @param sourceCode Source code
	 * @param tokens List of tokens to match
	 */
	protected void configure( String sourceCode , Vector tokens )
	{
		this.sourceCode = sourceCode;
		this.lexedInput = new Lexer( sourceCode , tokens ).lexIt();
		this.inputLength = lexedInput.getSize();
		this.currentToken = -1;
	}

	protected boolean lookAhead( int type , int value )
	{
		if ( currentToken+1 < inputLength )
		{
			return ( 
				( ( lexedInput.getTokenAt( currentToken + 1 ) ).getTokenType() == type ) 
				&& 
				( ( lexedInput.getTokenAt( currentToken + 1 ) ).getTokenValue() == value ) 
			);
		}
		return false;
	}
	
	protected boolean lookAhead( int type )
	{
		if ( currentToken+1 < inputLength )
		{
			return ( (lexedInput.getTokenAt(currentToken+1)).getTokenType() == type );
		}
		return false;
	}
	
	protected int getLookAheadValue()
	{
		if (currentToken+1<inputLength)
		{
			return (lexedInput.getTokenAt(currentToken+1)).getTokenValue();
		}
		else
		{
			return -1;
		}
	}
	
	protected int getLookAheadType()
	{
		if (currentToken+1<inputLength)
		{
			return (lexedInput.getTokenAt(currentToken+1)).getTokenType();
		}
		else
		{
			return -1;
		}
	}
	
	protected String getLookAheadData()
	{
		if (currentToken+1<inputLength)
		{
			return (lexedInput.getTokenAt(currentToken+1)).getLexeme();
		}
		else
		{
			return null;
		}
	}

	/**
	 * Match any token from the input stream. Moves the current pointer.
	 */
	protected void match() throws ParserException
	{
		currentToken++;
	}	
	
	/**
	 * Match a token from the input stream with a given type. Only match a token
	 * of type tokenType. The value is not checked. Moves the current pointer.
	 * produces a parse error if not the right type of token.
	 *
	 * @param tokenType  The type of the token to match
	 */
	protected void match(int tokenType) throws ParserException
	{
		if ( lookAhead( tokenType ) )
		{
			currentToken++;
		}
		else
		{
			// Not the right type of token
			parseError("Expected token of type: "+lookupTokenID(tokenType));
		}
	}

	/**
	 * Match a token from the input stream with a given type and value. Only match a token
	 * of type tokenType and tokenValue. Moves the current pointer.
	 * produces a parse error if not the right type of token.
	 *
	 * @param tokenType  The type of the token to match
	 * @param tokenValue The value of the token to match
	 */
	protected void match(int tokenType, int tokenValue) throws ParserException
	{
		if ( lookAhead( tokenType , tokenValue ) )
		{
			currentToken++;
		}
		else
		{
			// Not the right type of token
			parseError("Expected token of type: "+lookupTokenID(tokenType)+" with value: "+lookupTokenID(tokenValue));
		}
	}

	/**
	 * Lookup a meaningful name for a token based on its number by
	 * introspecting the class object.
	 * 
	 * @param token_id ID of token look up.
	 */
	protected String lookupTokenID(int token_id)
	{
		Class c = this.getClass();
		
		Field[] fields = c.getFields();
		
		for(int i = 0; i < fields.length; i++)
		{
			Field f = fields[i];
			
			if(f.getType() == Integer.TYPE)
			{
				try
				{
					if(f.getInt(this) == token_id)
					{
						return f.getName();
					}
				}
				catch(IllegalAccessException ie)
				{
				}
			}
		}
		
		return "Unknown Token ID: " + token_id;
	}
	
	/**
	 * Simple parse error on the current token
	 */
	protected void parseError() throws ParserException
	{
		parseError( currentToken+1 , "" );
	}

	/**
	 * Simple parse error on the current token with additional error string
	 *
	 * @param errorString  The optional string to show in the error
	 */
	protected void parseError(String errorString) throws ParserException
	{
		parseError( currentToken+1 , errorString );
	}

	/**
	 * Generate a parss error for the given token index
	 *
	 * @param errorToken  The index of the token which is in error
	 */
	protected void parseError(int errorToken) throws ParserException
	{
		parseError(errorToken, "");
	}

	/**
	 * Generate a parss error for the given token index with additional error string
	 *
	 * @param errorToken  The index of the token which is in error
	 * @param errorString  The optional string to show in the error
	 */
	protected void parseError(int errorTokenIndex, String errorString) throws ParserException
	{
		StringBuffer errorReport = new StringBuffer();
		
		Token errorToken = lexedInput.getTokenAt( errorTokenIndex );
		
		errorReport.append( errorString ).append( '\n' );
		
		if ( ( errorTokenIndex < 0 ) || ( errorTokenIndex > this.inputLength ) )
		{
			errorReport.append( "Internal compiler error.  Token out of bounds" );
		}
		else
		{
			try
			{ 
				Object obj = errorToken.getLexeme(); 
				if ( errorToken.getTokenType() == Lexer.END )
				{
					errorReport.append( "Unexpected end of expression" );
				}
				else
				{
					errorReport.append( "Parse error at token \"" + (lexedInput.getTokenAt(errorTokenIndex)).getLexeme()+"\"" );
				}
			}
			catch ( ArrayIndexOutOfBoundsException e )
			{
				errorReport.append( "Unexpected end of expression" );
			}
		}
		
		errorReport.append( '\n' ).append( this.sourceCode ).append( '\n' );
		
		int startOfErrorIndex = errorToken.getStart();
		
		// Print the spaces to make the marker appear in the right place
		for ( int x = startOfErrorIndex ; --x >= 0 ; )
		{
			errorReport.append( ' ' );
		}
		// Print the acutes to highlight the error
		for ( int x = errorToken.getLexeme().length() ; --x >= 0 ; )
		{
			errorReport.append( '^' );
		}
		
		errorReport.append( '\n' );
		
		throw new ParserException( errorReport.toString() );
	}

	/**
	 * Set the verbose logging flag
	 *
	 * @param b Verbosity flag value
	 */
	public void setVerbose(boolean b)
	{
		fVerbose = b;
	}

	/**
	 * Get the current verbose logging flag
	 *
	 * @return The verbose logging flag
	 */
	public boolean getVerbose()
	{
		return fVerbose;
	}

	protected void displayMessageVerbose(String verboseStr, String shortStr)
	{
		if(fVerbose)
		{
			System.out.print(verboseStr);
		}
		else
		{
			System.out.print(shortStr);
		}
	}
}
