package evaluator; import java.io.InputStream; import java.util.LinkedList; import java.util.HashMap; import java.util.HashSet; import java.util.Scanner; /** * Simulate a PDA to evaluate a series of postfix expressions provided by a * lexer. The constructor argument is the lexer of type Lexer. A single line is * evaluated and its value is printed. Expression values can also be assigned to * variables for later use. If no variable is explicitly assigned, then the * default variable "it" is assigned the value of the most recently evaluated * expression. * * @author YOU NAME HERE */ public class Evaluator { /** * Run the desk calculator. */ public static void main(String[] args) { Evaluator evaluator = new Evaluator(new Lexer(System.in)); evaluator.run(); } private Lexer lexer; // providing a stream of tokens private LinkedList stack; // operands private HashMap symbols; // symbol table for variables private String target; // variable assigned the latest expression value public Evaluator(Lexer lexer) { this.lexer = lexer; stack = new LinkedList<>(); symbols = new HashMap<>(); target = "it"; } /** * Evaluate a single line of input, which should be a complete expression * optionally assigned to a variable; if no variable is assigned to, then the * result is assigned to "it". In any case, return the value of the expression, * or "no value" if there was some sort of error. */ public Double evaluate() { stack.clear(); target = "it"; int q = 1; while (q != 4) { int token = lexer.nextToken(); if (lexer.getText().equals("exit")) { System.out.println("Bye"); System.exit(0); } switch (q) { case 1: switch (token) { case Lexer.NUMBER -> { stack.push(Double.parseDouble(lexer.getText())); q = 3; } case Lexer.VARIABLE -> { target = lexer.getText(); stack.push(symbols.getOrDefault(target, 0.0)); q = 2; } default -> error("Invald token encountered in state q1"); } break; case 2: switch (token) { case Lexer.ASSIGN_OP -> { stack.pop(); q = 3; } case Lexer.MINUS_OP -> { target = "it"; stack.push(-1 * stack.pop()); q = 3; } case Lexer.NUMBER -> { target = "it"; stack.push(Double.parseDouble(lexer.getText())); q = 3; } case Lexer.VARIABLE -> { target = "it"; stack.push(symbols.getOrDefault(lexer.getText(), 0.0)); q = 3; } case Lexer.EOL -> { target = "it"; symbols.put(target, stack.pop()); q = 4; } default -> error("Invald token encountered in state q2"); } break; case 3: switch (token) { case Lexer.MINUS_OP -> stack.push(-1 * stack.pop()); case Lexer.NUMBER -> stack.push(Double.parseDouble(lexer.getText())); case Lexer.VARIABLE -> stack.push(symbols.getOrDefault(lexer.getText(), 0.0)); case Lexer.ADD_OP -> { double a1 = stack.pop(); double a2 = stack.pop(); stack.push(a2 + a1); } case Lexer.SUBTRACT_OP -> { double s1 = stack.pop(); double s2 = stack.pop(); stack.push(s2 - s1); } case Lexer.MULTIPLY_OP -> { double m1 = stack.pop(); double m2 = stack.pop(); stack.push(m2 * m1); } case Lexer.DIVIDE_OP -> { double d1 = stack.pop(); double d2 = stack.pop(); stack.push(d2 / d1); } case Lexer.EOL -> { symbols.put(target, stack.pop()); q = 4; } default -> error("Invald token encountered in state q3"); } break; } } return symbols.get(target); } // evaluate /** * Run evaluate on each line of input and print the result forever. */ public void run() { while (true) { Double value = evaluate(); if (value == null) System.out.println("no value"); else System.out.println(value); } } /** * Print an error message, display the offending line with the current location * marked, and flush the lexer in preparation for the next line. * * @param msg what to print as an error indication */ private void error(String msg) { System.out.println(msg); String line = lexer.getCurrentLine(); int index = lexer.getCurrentChar(); System.out.print(line); for (int i = 1; i < index; i++) System.out.print(' '); System.out.println("^"); lexer.flush(); } //////////////////////////////// ///////// Lexer Class ////////// /** * Read terminal input and convert it to a token type, and also record the text * of each token. Whitespace is skipped. The input comes from stdin, and each * line is prompted for. */ public static class Lexer { // language token codes public static final int ADD_OP = 3; public static final int SUBTRACT_OP = 4; public static final int MULTIPLY_OP = 5; public static final int DIVIDE_OP = 6; public static final int MINUS_OP = 7; public static final int ASSIGN_OP = 8; public static final int EOL = 9; public static final int NUMBER = 11; public static final int VARIABLE = 12; public static final int BAD_TOKEN = 100; private Scanner input; // for reading lines from stdin private String line; // next input line private int index; // current character in this line private String text; // text of the current token public Lexer(InputStream in) { input = new Scanner(in); line = ""; index = 0; text = ""; } /** * Fetch the next character from the terminal. If the current line is exhausted, * then prompt the user and wait for input. If end-of-file occurs, then exit the * program. */ private char nextChar() { if (index == line.length()) { System.out.print(">> "); if (input.hasNextLine()) { line = input.nextLine() + "\n"; index = 0; } else { System.out.println("\nBye"); System.exit(0); } } char ch = line.charAt(index); index++; return ch; } /** * Put the last character back on the input line. */ private void unread() { index -= 1; } /** * Return the next token from the terminal. */ public int nextToken() { StringBuffer sb = new StringBuffer(); char t; int q = 0; while (true) { t = nextChar(); switch (q) { case 0 -> { if (Character.isWhitespace(t) && t != '\n') { continue; } sb.append(t); if (Character.isDigit(t)) { q = 1; continue; } if (Character.isLetter(t)) { q = 2; continue; } text = sb.toString(); return switch (t) { case '+' -> ADD_OP; case '-' -> SUBTRACT_OP; case '*' -> MULTIPLY_OP; case '/' -> DIVIDE_OP; case '~' -> MINUS_OP; case '=' -> ASSIGN_OP; case '\n' -> EOL; default -> BAD_TOKEN; }; } case 1 -> { if (Character.isDigit(t)) { sb.append(t); continue; } else if (t == '.') { sb.append(t); q = 10; continue; } else { unread(); text = sb.toString(); return NUMBER; } } case 2 -> { if (Character.isLetterOrDigit(t)) { sb.append(t); continue; } else { unread(); text = sb.toString(); return VARIABLE; } } case 10 -> { if (Character.isDigit(t)) { sb.append(t); continue; } else { unread(); text = sb.toString(); return NUMBER; } } } } } // nextToken /** * Return the current line for error messages. */ public String getCurrentLine() { return line; } /** * Return the current character index for error messages. */ public int getCurrentChar() { return index; } /** * /** Return the text of the current token. */ public String getText() { return text; } /** * Clear the current line after an error */ public void flush() { index = line.length(); } } // Lexer } // Evaluator