/* file:     addMult.java
   author:   Robert Keller
   purpose:  Tutorial additive expression parser
   
   The program reads an additive expression and multiplicative expression
   (with * taking precedence over +) and shows its tree as an S expr.

   compile and run:  java -cs additive

   Your CLASSPATH must include a directory containing the Poly library.

    A -> M { '+' M }            sum

    M -> V { '*' V }            product

    V -> a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z

In the grammar { }, means 0 or more of what's inside.

*/

import Poly.*;
import java.io.*;

class addMult extends LineBuffer                // addMult parser class
  {
  static public void main(String arg[])         // USER INTERFACE
    {
    LineBufferInputStream in =                  // stream of lines
      new LineBufferInputStream(System.in);

    while( prompt() && !in.eof() )              // while more lines
      {
      String input = in.getLine();              // get a line
      addMult parser = new addMult(input);      // construct parser for line
      Object result = parser.parse();           // parse the line
      System.out.println("result: " + result);  // show the result
      System.out.println();
      }
    System.out.println();
    }


  Object A()                            // PARSE FUNCTION for A -> M { '+' M }
    {
    Object result;
    Object M1 = M();                    // get the first addend
    if( isFailure(M1) ) return failure;

    result = M1;

    while( peek() == '+' )                      // while more '+'
      {
      nextChar();                               // absorb the '+'
      Object M2 = M();                          // get the next addend
      if( isFailure(M2) ) return failure;
      result = Poly.List.list("+", result, M2); // create tree
      }
    return result;
    }


  Object M()                            // PARSE FUNCTION for M -> V { '*' V }
    {
    Object result;
    Object V1 = V();                            // get the first variable
    if( isFailure(V1) ) return failure;

    result = V1;

    while( peek() == '*' )                      // while more '*'
      {
      nextChar();                               // absorb the '*'
      Object V2 = V();                          // get the next variable
      if( isFailure(V2) ) return failure;
      result = Poly.List.list("*", result, V2); // create tree
      }
    return result;
    }


// PARSE FUNCTION for V -> a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z

  Object V()
    {
    if( isVar(peek()) )                         // if there is a variable
      {
      return (new StringBuffer(1).append(nextChar())).toString();
      }      
    return failure;
    }


  Object parse()        // TOP LEVEL: parse with check for residual input
    {
    Object result = A();                        // parse an A
    skipWhitespace();                           // ignore trailing whitespace
    if( position < lastPosition )               // see if any residual junk
      {
      System.out.print("*** Residual characters after input: ");
      while( !eof )                             // print residual characters
        {
        char c = nextChar();
        System.out.print(c);
        }
      System.out.println();
      }
    return result;                              // return result of parse 
    }


  // isVar indicates whether its argument is a variable

  boolean isVar(char c)
    {
    switch( c )
      {
      case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
      case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
      case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
      case 'v': case 'w': case 'x': case 'y': case 'z':
        return true;
      default:
        return false;
      }
    }
                                                        // SUPPORT CODE

  static String promptString = "input: ";               // prompt string

  static ParseFailure failure = new ParseFailure();     // failure

  addMult(String input)                                 // parser constructor
    {
    super(input);                                       // construct LineBuffer
    }

  boolean isFailure(Object ob)                          // test for failure
    {
    return ob instanceof ParseFailure;
    }

  static boolean prompt()                               // prompter
    {
    System.out.print(promptString);
    System.out.flush();
    return true;
    }
  }  // class addMult


// ParseFailure object is used to indicate a parse failure.

class ParseFailure
  {
  }


// LineBuffer provides a way of getting characters from a string
// Note that eof is a variable indicating end-of-line.  It should
// not be confused with the eof method of LineBufferInputStream.

class LineBuffer
  {
  String input;                 // string being parsed
  boolean eof;                  // end-of-file condition exists

  int position;                 // position of current character

  int lastPosition;             // last position in input string

  LineBuffer(String input)
    {
    this.input = input;         // initialize variables dealing with string
    position = -1;              // initialize "pointer"
    lastPosition = input.length()-1;
    eof = lastPosition == -1;
    }

  char nextChar()                       // get next character in input
    {
    if( position >= lastPosition )	// if no more characters
      {
      eof = true;			// set eof
      return ' ';			// and return a space
      }
    return input.charAt(++position);
    }

  char peek()                           // return, but retain, next character
    {
    if( position >= lastPosition )	
      {					// if no more characters
      return ' ';			// return a space
      }
    return input.charAt(position+1);
    }

  void skipWhitespace()                 // skip whitespace
    {
    while( !eof && peek() == ' ' )
      {
      nextChar();
      }
    }
  }  // LineBuffer


// LineBufferInputStream is an input stream capable of reading one line
// at a time and returning the line as a string, by calling method getLine().
// It also provides the method eof() for testing for end-of-file.
// It extends PushbackInputStream from package java.io

class LineBufferInputStream extends PushbackInputStream
  {
  /**
    * LineBufferInputStream constructs from an InputStream
   **/

  LineBufferInputStream(InputStream in)
    {
    super(in);			// call constructor of PushbackInputStream
    }

  /**
    *  getLine() gets the next line of input
   **/

  String getLine()
    {
    StringBuffer b = new StringBuffer();	// buffer for line
    try
      {
      int c;
      while( !eof() && ((c = read()) != '\n') )	// read to end-of-line or -file
        {
        b.append((char)c);
        }
      return b.toString();			// get string from buffer
      }
    catch( java.io.IOException e )
      {
      handleException("getLine", e);
      return "";
      }
    }

  /**
    *  eof() tells whether end-of-file has been reached.
   **/

  public boolean eof()
    {
    try
      {
      int c = read();
      if( c == -1 )
        {
        return true;
        }
      else
        {
        unread(c);
        return false;
        }      
      }
    catch( IOException e )
      {
      handleException("eof", e);
      }
    return false;
    }

  /**
    *  handleException is called when there is an IOException in any method.
   **/

  public void handleException(String method, IOException e)
    {
    System.out.println("IOException in LineBufferInputStream: " + e + 
                       " calling method " + method);
    }
  }

