Skip to content

Instantly share code, notes, and snippets.

@RedHatter
Last active September 3, 2016 17:45
Show Gist options
  • Save RedHatter/bec21ec6faca464f87f973292ce3dc34 to your computer and use it in GitHub Desktop.
Save RedHatter/bec21ec6faca464f87f973292ce3dc34 to your computer and use it in GitHub Desktop.
A postfix calculator.
import java.util.Queue;
/**
* Base class for all postfix elements.
*/
public interface Element {
/**
* Resolves the element to a number pushing the result onto the stack.
*/
public void resolve (Queue<Double> stack);
public String toString ();
}
import java.util.Queue;
/**
* The most basic postfix element--a literal number.
*/
public class Number implements Element {
private double value;
public static Number fromToken (String token) {
return new Number(Double.parseDouble(token));
}
public static boolean isValid (String token) {
// First char could be a negitive sign, the rest are digits or a decimal point
char[] chars = token.toCharArray();
for (int i = chars[0] == '-' && chars.length > 1 ? 1 : 0; i < chars.length; i++)
// TODO: Only one decimal point is valid
if (!Character.isDigit(chars[i]) && chars[i] != '.')
return false;
return true;
}
public Number (double value) {
this.value = value;
}
/**
* Simply pushes the stored value onto the stack.
*/
@Override
public void resolve (Queue<Double> stack) {
stack.add(value);
}
@Override
public String toString () {
return "Number " + value;
}
}
import java.util.Queue;
/**
* Represents a basic mathematical operation.
*/
public class Operation implements Element {
private char opt;
public static Operation fromToken (String token) {
return new Operation(token.charAt(0));
}
public static boolean isValid (String token) {
if (token.length() > 1)
return false;
switch (token.charAt(0)) {
case '+':
case '-':
case '*':
case '/':
case '%':
return true;
default:
return false;
}
}
public Operation (char opt) {
this.opt = opt;
}
/**
* Pop the last two elements off the stack, proform the signified
* opperation on them, and push the result back onto the stack.
*/
@Override
public void resolve (Queue<Double> stack) throws IllegalStateException {
if (stack.size() < 2)
throw new IllegalStateException("Operation requires at least two values on the stack.");
double right = stack.remove();
double left = stack.remove();
double result = -1;
switch (opt) {
case '+':
result = left + right;
break;
case '-':
result = left - right;
break;
case '*':
result = left * right;
break;
case '/':
result = left / right;
break;
case '%':
result = left % right;
break;
default:
throw new IllegalStateException("Unsupported operation '"+opt+"'.");
}
stack.add(result);
}
@Override
public String toString () {
return "Operation " + opt;
}
}
import java.io.InputStreamReader;
import java.io.IOException;
import java.text.ParseException;
import java.util.Scanner;
import java.util.List;
import java.util.Queue;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* Built to delegate to a series of 'postfix elements'. Each element has three
* important methods.
*
* 1. isValid used to determine whether a token can be converted
* into an element of that type.
* 2. fromToken that takes a token as a string and parses it returning an
* Element. Note: This can fail horribly if isValid is not called first.
* 3. resolve is where most of the work is done. It converts the Element into
* a double and pushes that result onto the stack for future Elements to use.
* This could result in a smaller stack as some Elements (such as Operation)
* pop Elements off the stack while resolving.
*
* Postfix first builds a list of Elements by spliting a string into
* tokens and converting each one through use of isValid and fromToken. It
* then loops through the resulting list resolving each Element onto the stack.
* At the end of this process if the expression was correctly formatted then
* the stack will contain a single number that is the result of the postfix
* expression.
*
* Throughout the code you will find a couple expressions like '\033[1m'
* included in the output, these provide simple color and formatting.
*/
public class PostfixParser {
public static void main (String[] args) throws ParseException, IOException {
// Run postfix on ether the arguments or stdin
Scanner s;
if (args.length > 0) {
StringBuilder builder = new StringBuilder();
for (String arg : args) builder.append(arg);
System.out.println("Result: \033[1;31m" + PostfixParser.run(new Scanner(builder.toString())) + "\033[0m");
} else {
// Interactive mode
do {
System.out.print("Enter a postfix expression: ");
System.out.println("Result: \033[1;31m" + PostfixParser.run(new Scanner(System.in)) + "\033[0m");
} while (ask());
}
}
public static double run (Scanner is) throws ParseException {
List<Element> elements = new ArrayList<>();
// Build a list of all postfix elements delegating
// to each Element type for parsing
int offset = 0;
while (is.hasNext()) {
String token = is.next();
if (token.equals("$"))
break;
else if (Number.isValid(token))
elements.add(Number.fromToken(token));
else if (Operation.isValid(token))
elements.add(Operation.fromToken(token));
else if (Variable.isValid(token))
elements.add(Variable.fromToken(token));
else
throw new ParseException("Unable to identify element '"+token+"'.", offset);
offset += token.length();
}
// Process each element passing a shared stack
// to store parial calculations
Queue<Double> stack = new LinkedList<>();
for (Element e : elements)
e.resolve(stack);
// Expect a single value on the stack
if (stack.size() > 1)
throw new IllegalStateException("Resulted in multiple values on the stack.");
else if (stack.size() < 1)
throw new IllegalStateException("Resulted in no values on the stack.");
return stack.remove();
}
public static boolean ask () throws IOException {
System.out.print("\nContinue? (y/n) ");
int response = (new InputStreamReader(System.in)).read();
return response == 'y' || response == 'Y';
}
}
import java.util.Queue;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.InputMismatchException;
/**
* A placeholder for a number to be provided by the user.
*/
public class Variable implements Element {
private static Map<String, Double> values = new HashMap<String, Double> ();
private String name;
public static Variable fromToken (String token) {
return new Variable(token);
}
public static boolean isValid (String token) {
char c = token.charAt(0);
return Character.isLetter(c) || c == '_';
}
public Variable (String name) {
this.name = name;
}
/**
* Pushes the value reposented onto the stack. Attempts to retrieve the
* value from the cache otherwise prompting the user to provide the value.
*/
@Override
public void resolve (Queue<Double> stack) {
if (values.containsKey(name))
stack.add(values.get(name));
else {
while (true) {
try {
System.out.print("Enter a value for \033[1m" + name + "\033[0m: ");
Scanner is = new Scanner(System.in);
double n = is.nextDouble();
values.put(name, n);
stack.add(n);
break;
} catch (InputMismatchException e) {
System.out.println("Not a number.");
}
}
}
}
@Override
public String toString () {
return "Variable " + name;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment