/* This program can evaluate expressions that can include numbers, variables, parentheses, and the operators +, -, *, /, and ^ (where ^ indicates raising to a power). A variable name must consist of letters and digits, beginning with a letter. Names are case-sensitive. This program accepts commands of two types from the user. For a command of the form print , the expression is evaluated and the value is output. For a command of the form let = , the expression is evaluated and the value is assigned to the variable. If a variable is used in an expression before it has been assigned a value, an error occurs. A number must begin with a digit (i.e., not a decimal point). Commands are formally defined by the BNF rules: ::= "print" | "let" "=" ::= [ "-" ] [ [ "+" | "-" ] ]... ::= [ [ "*" | "/" ] ]... ::= [ "^" ]... ::= | | "(" ")" A line of input must contain exactly one such command. If extra data is found on a line after an expression has been read, it is considered an error. The variables "pi" and "e" are defined when the program starts to represent the usual mathematical constants. This program demonstrates the use of a HashMap as a symbol table. SimpleParser5.java is based on the program SimpleParser2.java. It requires a non-standard class, TextIO. */ import java.util.HashMap; public class SimpleParser5 { static class ParseError extends Exception { // Represents a syntax error found in the user's input. ParseError(String message) { super(message); } } // end nested class ParseError static 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.) public static void main(String[] args) { // 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)); TextIO.putln("\n\nEnter commands; press return to end."); TextIO.putln("Commands must have the form:\n"); TextIO.putln(" print "); TextIO.putln(" or"); TextIO.putln(" let = "); while (true) { TextIO.put("\n? "); skipBlanks(); if ( TextIO.peek() == '\n' ) break; try { String command = TextIO.getWord(); if (command.equalsIgnoreCase("print")) doPrintCommand(); else if (command.equalsIgnoreCase("let")) doLetCommand(); else throw new ParseError("Command must begin with 'print' or 'let'."); TextIO.getln(); } catch (ParseError e) { TextIO.putln("\n*** Error in input: " + e.getMessage()); TextIO.putln("*** Discarding input: " + TextIO.getln()); } } TextIO.putln("\n\nDone."); } // end main() static 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 ( TextIO.peek() == ' ' || TextIO.peek() == '\t' ) TextIO.getAnyChar(); } static void doLetCommand() throws ParseError { // Process a command of the form let = . // 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(TextIO.peek()) ) throw new ParseError("Expected variable name after 'let'."); String name = readWord(); // The name of the variable. skipBlanks(); if ( TextIO.peek() != '=' ) throw new ParseError("Expected '=' operator for 'let' command."); TextIO.getChar(); double val = expressionValue(); // The value of the variable. skipBlanks(); if ( TextIO.peek() != '\n' ) throw new ParseError("Extra data after end of expression."); symbolTable.put(name, new Double(val)); // Add to symbol table. TextIO.putln("ok"); } static void doPrintCommand() throws ParseError { // Process a command of the form print . // When this method is called, the word "print" has already // been read. Evaluate the expression and print the value. double val = expressionValue(); skipBlanks(); if ( TextIO.peek() != '\n' ) throw new ParseError("Extra data after end of expression."); TextIO.putln("Value is " + val); } static 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 (TextIO.peek() == '-') { TextIO.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 ( TextIO.peek() == '+' || TextIO.peek() == '-' ) { // Read the next term and add it to or subtract it from // the value of previous terms in the expression. char op = TextIO.getAnyChar(); double nextVal = termValue(); if (op == '+') val += nextVal; else val -= nextVal; skipBlanks(); } return val; } // end expressionValue() static 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 ( TextIO.peek() == '*' || TextIO.peek() == '/' ) { // Read the next factor, and multiply or divide // the value-so-far by the value of this factor. char op = TextIO.getAnyChar(); double nextVal = factorValue(); if (op == '*') val *= nextVal; else val /= nextVal; skipBlanks(); } return val; } // end termValue() static 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 ( TextIO.peek() == '^' ) { // Read the next primary, and exponentiate // the value-so-far by the value of this primary. TextIO.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() static 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 = TextIO.peek(); if ( Character.isDigit(ch) ) { // The factor is a number. Read it and // return its value. return TextIO.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. TextIO.getAnyChar(); // Read the "(" double val = expressionValue(); skipBlanks(); if ( TextIO.peek() != ')' ) throw new ParseError("Missing right parenthesis."); TextIO.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."); } static 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 = TextIO.peek(); while (Character.isLetter(ch) || Character.isDigit(ch)) { word += TextIO.getChar(); // Add the character to the word. ch = TextIO.peek(); } return word; } } // end class SimpleParser5