/*
   This Console applet simulates the stand-alone program
   SimpleParser5.java.
*/

import java.util.HashMap;

public class SimpleParser5Console extends ConsoleApplet {


    static class ParseError extends Exception {
           // Represents a syntax error found in the user's input.
       ParseError(String message) {
           super(message);
       }
    } // end nested class ParseError
    
    
    HashMap symbolTable = new HashMap();
       // The symbolTable contains information about the
       // values of variables.  When a variable is assigned
       // a value, it is recorded in the symbol table.
       // The key is the name of the variable, and the 
       // value is an object of type Double that contains
       // the value of the variable.  (The wrapper class
       // Double is used, since a HashMap cannot contain
       // objects belonging to the primitive type double.)
    
    
    protected String getTitle() {
       return "Symbol Table Demo";
    }


    protected void program() {
    
        // To start, add variables named "pi" and "e" to the
        // symbol tables.  Their values are the usual 
        // mathematical constants.
    
        symbolTable.put("pi", new Double(Math.PI));
        symbolTable.put("e", new Double(Math.E));
    
        console.putln("\n\nEnter commands; press return to end.");
        console.putln("Commands must have the form:\n");
        console.putln("      print <expression>");
        console.putln("  or");
        console.putln("      let <variable> = <expression>");

        while (true) {
           console.put("\n?  ");
           skipBlanks();
           if ( console.peek() == '\n' )
              break;
           try {
              String command = console.getWord();
              if (command.equalsIgnoreCase("print"))
                 doPrintCommand();
              else if (command.equalsIgnoreCase("let"))
                 doLetCommand();
              else
                 throw new ParseError("Command must begin with 'print' or 'let'.");
              console.getln();
           }
           catch (ParseError e) {
              console.putln("\n*** Error in input:    " + e.getMessage());
              console.putln("*** Discarding input:  " + console.getln());
           }
        }
        
        console.putln("\n\nDone.");
    
    } // end main()
    
    
    void skipBlanks() {
          // Skip past any spaces and tabs on the current line of input.
          // Stop at a non-blank character or end-of-line.
       while ( console.peek() == ' ' || console.peek() == '\t' )
          console.getAnyChar();
    }

    
    void doLetCommand() throws ParseError {
          // Process a command of the form  let <variable> = <expression>.
          // When this method is called, the word "let" has already
          // been read.  Read the variable name and the expression, and
          // store the value of the variable in the symbol table.
        skipBlanks();
        if ( ! Character.isLetter(console.peek()) )
          throw new ParseError("Expected variable name after 'let'.");
        String name = readWord();  // The name of the variable.
        skipBlanks();
        if ( console.peek() != '=' )
           throw new ParseError("Expected '=' operator for 'let' command.");
        console.getChar();
        double val = expressionValue();  // The value of the variable.
        skipBlanks();
        if ( console.peek() != '\n' )
           throw new ParseError("Extra data after end of expression.");
        symbolTable.put(name, new Double(val));  // Add to symbol table.
        console.putln("ok");
    }
    
    
    void doPrintCommand() throws ParseError {
          // Process a command of the form  print <expression>.
          // When this method is called, the word "print" has already
          // been read.  Evaluate the expression and print the value.
        double val = expressionValue();
        skipBlanks();
        if ( console.peek() != '\n' )
           throw new ParseError("Extra data after end of expression.");
        console.putln("Value is " + val);
    }
    
    
    double expressionValue() throws ParseError {
           // Read an expression from the current line of input and
           // return its value.
       skipBlanks();
       boolean negative;  // True if there is a leading minus sign.
       negative = false;
       if (console.peek() == '-') {
          console.getAnyChar();
          negative = true;
       }
       double val;  // Value of the expression.
       val = termValue();  // An expression must start with a term.
       if (negative)
          val = -val; // Apply the leading minus sign
       skipBlanks();
       while ( console.peek() == '+' || console.peek() == '-' ) {
                // Read the next term and add it to or subtract it from
                // the value of previous terms in the expression.
           char op = console.getAnyChar();
           double nextVal = termValue();
           if (op == '+')
              val += nextVal;
           else
              val -= nextVal;
           skipBlanks();
       }
       return val;
    } // end expressionValue()


    double termValue() throws ParseError {
           // Read a term from the current line of input and
           // return its value.
       skipBlanks();
       double val;  // The value of the term.
       val = factorValue();  // A term must start with a factor.
       skipBlanks();
       while ( console.peek() == '*' || console.peek() == '/' ) {
                // Read the next factor, and multiply or divide
                // the value-so-far by the value of this factor.
           char op = console.getAnyChar();
           double nextVal = factorValue();
           if (op == '*')
              val *= nextVal;
           else
              val /= nextVal;
           skipBlanks();
       }
       return val;
    } // end termValue()
    
    
    double factorValue() throws ParseError {
           // Read a factor from the current line of input and
           // return its value.
       skipBlanks();
       double val;  // Value of the factor.
       val = primaryValue();  // A factor must start with a primary.
       skipBlanks();
       while ( console.peek() == '^' ) {
                // Read the next primary, and exponentiate
                // the value-so-far by the value of this primary.
           console.getChar();
           double nextVal = primaryValue();
           val = Math.pow(val,nextVal);
           if (Double.isNaN(val))
              throw new ParseError("Illegal values for ^ operator.");
           skipBlanks();
       }
       return val;
    } // end termValue()
    
    
    double primaryValue() throws ParseError {
          // Read a primary from the current line of input and
          // return its value.  A primary must be a number,
          // a variable, or an expression enclosed in parentheses.
       skipBlanks();
       char ch = console.peek();
       if ( Character.isDigit(ch) ) {
              // The factor is a number.  Read it and
              // return its value.
          return console.getDouble();
       }
       else if ( Character.isLetter(ch) ) {
              // The factor is a variable.  Read its name and
              // look up its value in the symbol table.  If the
              // variable is not in the symbol table, an error
              // occurs.  (Note that the values in the symbol
              // table are objects of type Double.)
          String name = readWord();
          Object symbolTableEntry = symbolTable.get(name);
          if (symbolTableEntry == null)
             throw new ParseError("Unknown variable \"" + name + "\"");
          Double val = (Double)symbolTableEntry;
          return val.doubleValue();
       }
       else if ( ch == '(' ) {
             // The factor is an expression in parentheses.
             // Return the value of the expression.
          console.getAnyChar();  // Read the "("
          double val = expressionValue();
          skipBlanks();
          if ( console.peek() != ')' )
             throw new ParseError("Missing right parenthesis.");
          console.getAnyChar();  // Read the ")"
          return val;
       }
       else if ( ch == '\n' )
          throw new ParseError("End-of-line encountered in the middle of an expression.");
       else if ( ch == ')' )
          throw new ParseError("Extra right parenthesis.");
       else if ( ch == '+' || ch == '-' || ch == '*' || ch == '/')
          throw new ParseError("Misplaced operator.");
       else
          throw new ParseError("Unexpected character \"" + ch + "\" encountered.");
    }
    
    
    String readWord() {
          // Reads a word from input.  A word is any sequence of
          // letters and digits, starting with a letter.  When 
          // this subroutine is called, it should already be
          // known that the next character in the input is
          // a letter.
       String word = "";  // The word.
       char ch = console.peek();
       while (Character.isLetter(ch) || Character.isDigit(ch)) {
          word += console.getChar(); // Add the character to the word.
          ch = console.peek();
       }
       return word;
    }


} // end class SimpleParser5