Files
CS327-Discrete-Structures-II/Desk-Calculator/Evaluator.java

350 lines
7.9 KiB
Java

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<Double> stack; // operands
private HashMap<String, Double> 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