// Evaluator clas interface: evaluate infix expression
// Type: Must have usual repertoire of arithmetic operators
//
// CONSTRUCTION: with a String
//
// ******************PUBLIC OPERATIONS***********************
// Type getValue( )      --> Return value of infix expression
// ******************ERRORS**********************************
// Some error checking is performed

import DataStructures.*;
import Exceptions.*;
import java.io.*;
import java.util.StringTokenizer;

class Precedence
{
    int inputSymbol;
    int topOfStack;

    Precedence( int inSymbol, int topSymbol )
    {
        inputSymbol = inSymbol;
        topOfStack  = topSymbol;
    }
}

public class Evaluator
{
    static final int EOL     = 0;
    static final int VALUE   = 1;
    static final int OPAREN  = 2;
    static final int CPAREN  = 3;
    static final int EXP     = 4;
    static final int MULT    = 5;
    static final int DIV     = 6;
    static final int PLUS    = 7;
    static final int MINUS   = 8;

        // PrecTable matches order of Token enumeration
    static Precedence [ ] precTable = new Precedence[ 9 ];
    static
    {
        precTable[ 0 ] = new Precedence(   0, -1 );  // EOL
        precTable[ 1 ] = new Precedence(   0,  0 );  // VALUE
        precTable[ 2 ] = new Precedence( 100,  0 );  // OPAREN
        precTable[ 3 ] = new Precedence(   0, 99 );  // CPAREN
        precTable[ 4 ] = new Precedence(   6,  5 );  // EXP
        precTable[ 5 ] = new Precedence(   3,  4 );  // MULT
        precTable[ 6 ] = new Precedence(   3,  4 );  // DIV
        precTable[ 7 ] = new Precedence(   1,  2 );  // PLUS
        precTable[ 8 ] = new Precedence(   1,  2 );  // MINUS
    }


    /**
     * Construct an evaluator object.
     * @param s the string containing the expression.
     */
    public Evaluator( String s )
    {
        opStack = new StackAr( );
        postfixStack = new StackAr( );

        str = new StringTokenizer( s, "+*-/^() ", true );

        opStack.push( new Integer( EOL ) );
    }

        // The only publicly visible routine

    /** 
     * Public routine that performs the evaluation.
     * Examine the  postfix machine to see if a single result is
     * left and if so, return it; otherwise print error.
     * @return the result.
     */
    public long getValue( )
    {
        long theResult = 0;

        do
        {
            lastToken = getToken( );
            processToken( );
        } while( lastToken != EOL );

        try
          { theResult = postFixTopAndPop( ); }
        catch( Underflow e )
        {
            System.err.println( "Missing operand!" );
            return 0;
        }

        if( !postfixStack.isEmpty( ) )
            System.err.println( "Warning: missing operators!" );

        return theResult;
    }


    private Stack opStack;      // Operator stack for conversion
    private Stack postfixStack; // Stack for postfix machine

    StringTokenizer str;        // The character stream
    long currentValue;          // Current operand
    int lastToken;              // Last token read
 
    /**
     * Internal method that hides type-casting.
     */
    private long postFixTopAndPop( ) throws Underflow
    {
        return ( (Long) ( postfixStack.topAndPop( ) ) ).longValue( );
    }

    /**
     * Another internal method that hides type-casting.
     */
    private int opStackTop( ) throws Underflow
    {
        return ( (Integer) ( opStack.top( ) ) ).intValue( );
    }

    /**
     * After a token is read, use operator precedence parsing
     * algorithm to process it; missing opening parentheses
     * are detected here.
     */
    private void processToken( )
    {
        int topOp;
      
        try
        {
            switch( lastToken )
            {
              case VALUE:
                postfixStack.push( new Long( currentValue ) );
                return;

              case CPAREN:
                while( ( topOp = opStackTop( ) ) != OPAREN && topOp != EOL )
                    binaryOp( topOp );
                if( topOp == OPAREN )
                    opStack.pop( );  // Get rid of opening parentheseis
                else
                    System.err.println( "Missing open parenthesis" );
                break;

              default:    // General operator case
                while( precTable[ lastToken ].inputSymbol <=
                       precTable[ topOp = opStackTop( ) ].topOfStack )
                    binaryOp( topOp );
                if( lastToken != EOL )
                    opStack.push( new Integer( lastToken ) );
                break;
            }
        }
        catch( Underflow e ) { }  // Cannot happen 
    }

    /*
     * topAndPop the postfix machine stack; return the result.
     * If the stack is empty, print an error message.
     */
    private long getTop( )
    {
        try
          { return postFixTopAndPop( ); }
        catch( Underflow e )
          { System.err.println( "Missing operand" ); }
        return 0;
    }

    /**
     * Internal routine to compute x^n.
     */
    private static long pow( long x, long n )
    {
        if( x == 0 )
        {
            if( n == 0 )
                System.err.println( "0^0 is undefined" );
            return 0;
        }
        if( n < 0 )
        {
            System.err.println( "Negative exponent" );
            return 0;
        }
        if( n == 0 )
            return 1;
        if( n % 2 == 0 )
            return pow( x * x, n / 2  );
        else
            return x * pow( x, n - 1 );
    }

    /**
     * Process an operator by taking two items off the postfix
     * stack, applying the operator, and pushing the result.
     * Print error if missing closing parenthesis or division by 0.
     */
    private void binaryOp( int topOp )
    {
        if( topOp == OPAREN )
        {
            System.err.println( "Unbalanced parentheses" );
            try
              { opStack.pop( ); }
            catch( Underflow e ) { }  // Cannot happen
            return;
        }
        long rhs = getTop( );
        long lhs = getTop( );

        if( topOp == EXP )
            postfixStack.push( new Long( pow( lhs, rhs ) ) );
        else if( topOp == PLUS )
            postfixStack.push( new Long( lhs + rhs ) );
        else if( topOp == MINUS )
            postfixStack.push( new Long( lhs - rhs ) );
        else if( topOp == MULT )
            postfixStack.push( new Long( lhs * rhs ) );
        else if( topOp == DIV )
            if( rhs != 0 )
                postfixStack.push( new Long( lhs / rhs ) );
            else
            {
                System.err.println( "Division by zero" );
                postfixStack.push( new Long( lhs ) );
            }
        try
          { opStack.pop( ); }
        catch( Underflow e ) { }
    }

    /**
     * Find the next token, skipping blanks, and return it.
     * For VALUE token, place the processed value in currentValue.
     * Print error message if input is unrecognized.
     */
    private int getToken( )
    {
        String s = "";

        try
          { s = str.nextToken( ); }
        catch( java.util.NoSuchElementException e )
          { return EOL; }

        if( s.equals( " " ) ) return getToken( );
        if( s.equals( "^" ) ) return EXP;
        if( s.equals( "/" ) ) return DIV;
        if( s.equals( "*" ) ) return MULT;
        if( s.equals( "(" ) ) return OPAREN;
        if( s.equals( ")" ) ) return CPAREN;
        if( s.equals( "+" ) ) return PLUS;
        if( s.equals( "-" ) ) return MINUS;

        try
          { currentValue = Long.parseLong( s ); }
        catch( NumberFormatException e )
        {
            System.err.println( "Parse error" );
            return EOL;
        }
        return VALUE;
    }

    /**
     * Quick and dirty main
     */
    public static void main( String [ ] args )
    {
        String str;
        BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) );

        try
        {
            System.out.println( "Enter expressions, one per line:" );
            while( ( str = in.readLine( ) ) != null )
            {
                System.out.println( "Read: " + str );
                Evaluator ev = new Evaluator( str );
                System.out.println( ev.getValue( ) );
                System.out.println( "Enter next expression:" );
            }
        }
        catch( IOException e ) { }
    }
}
