350 lines
7.9 KiB
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
|