Last active
September 3, 2016 17:45
-
-
Save RedHatter/bec21ec6faca464f87f973292ce3dc34 to your computer and use it in GitHub Desktop.
A postfix calculator.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 (); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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