First commit, aggregated repos
This commit is contained in:
349
Desk-Calculator/Evaluator.java
Normal file
349
Desk-Calculator/Evaluator.java
Normal file
@@ -0,0 +1,349 @@
|
||||
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
|
||||
140
Desk-Calculator/EvaluatorTest.java
Normal file
140
Desk-Calculator/EvaluatorTest.java
Normal file
@@ -0,0 +1,140 @@
|
||||
package evaluator;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import evaluator.Evaluator.Lexer;
|
||||
|
||||
class EvaluatorTest {
|
||||
|
||||
@Test
|
||||
void lexerGoodInputTest() {
|
||||
InputStream input = new ByteArrayInputStream("x1x = 10.34 10 +\n".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Lexer lexer = new Lexer(input);
|
||||
|
||||
assertEquals(Lexer.VARIABLE, lexer.nextToken());
|
||||
assertEquals("x1x", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.ASSIGN_OP, lexer.nextToken());
|
||||
assertEquals("=", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("10.34", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("10", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.ADD_OP, lexer.nextToken());
|
||||
assertEquals("+", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.EOL, lexer.nextToken());
|
||||
assertEquals("\n", lexer.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lexerBadInputTest() {
|
||||
InputStream input = new ByteArrayInputStream("y,!g != 34.67 16! ".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Lexer lexer = new Lexer(input);
|
||||
|
||||
assertEquals(Lexer.VARIABLE, lexer.nextToken());
|
||||
assertEquals("y", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.BAD_TOKEN, lexer.nextToken());
|
||||
assertEquals(",", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.BAD_TOKEN, lexer.nextToken());
|
||||
assertEquals("!", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.VARIABLE, lexer.nextToken());
|
||||
assertEquals("g", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.BAD_TOKEN, lexer.nextToken());
|
||||
assertEquals("!", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.ASSIGN_OP, lexer.nextToken());
|
||||
assertEquals("=", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("34.67", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("16", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.BAD_TOKEN, lexer.nextToken());
|
||||
assertEquals("!", lexer.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lexerExtraWhiteSpaceTest() {
|
||||
InputStream input = new ByteArrayInputStream(" y = 17.5 + 34 * ".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Lexer lexer = new Lexer(input);
|
||||
|
||||
assertEquals(Lexer.VARIABLE, lexer.nextToken());
|
||||
assertEquals("y", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.ASSIGN_OP, lexer.nextToken());
|
||||
assertEquals("=", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("17.5", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.ADD_OP, lexer.nextToken());
|
||||
assertEquals("+", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("34", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.MULTIPLY_OP, lexer.nextToken());
|
||||
assertEquals("*", lexer.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lexerNoWhiteSpaceTest() {
|
||||
InputStream input = new ByteArrayInputStream("10+17=!13x78y*\n".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Lexer lexer = new Lexer(input);
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("10", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.ADD_OP, lexer.nextToken());
|
||||
assertEquals("+", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("17", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.ASSIGN_OP, lexer.nextToken());
|
||||
assertEquals("=", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.BAD_TOKEN, lexer.nextToken());
|
||||
assertEquals("!", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.NUMBER, lexer.nextToken());
|
||||
assertEquals("13", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.VARIABLE, lexer.nextToken());
|
||||
assertEquals("x78y", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.MULTIPLY_OP, lexer.nextToken());
|
||||
assertEquals("*", lexer.getText());
|
||||
|
||||
assertEquals(Lexer.EOL, lexer.nextToken());
|
||||
assertEquals("\n", lexer.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
void evaluatorSimpleExpressionTest() {
|
||||
InputStream input = new ByteArrayInputStream("10 10 *\n".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Evaluator eval = new Evaluator(new Lexer(input));
|
||||
|
||||
assertEquals(100, eval.evaluate());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user