parent
ff465b514e
commit
cfc55b658f
@ -0,0 +1,26 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
/** |
||||
* Interface for classes with optional debug mode |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public interface IDebugable { |
||||
|
||||
/** |
||||
* Get if debug mode is enabled |
||||
* |
||||
* @return debug enabled |
||||
*/ |
||||
public boolean isDebug(); |
||||
|
||||
|
||||
/** |
||||
* Enable / disable debug mode |
||||
* |
||||
* @param debug enable debug |
||||
*/ |
||||
public void setDebug(boolean debug); |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Interface of an object that can be turned into a fractional value |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public interface IEvaluable { |
||||
|
||||
/** |
||||
* Get fractional value |
||||
* |
||||
* @return fractional value |
||||
*/ |
||||
public Fraction evaluate(); |
||||
} |
@ -0,0 +1,11 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
/** |
||||
* Evaluable token interface
|
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public interface IEvaluableToken extends IEvaluable, IToken { |
||||
|
||||
} |
@ -0,0 +1,11 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
/** |
||||
* Token interface
|
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public interface IToken { |
||||
|
||||
} |
@ -0,0 +1,32 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
/** |
||||
* Error thrown when a syntax error is encountered while parsing an expression |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class ParseError extends RuntimeException { |
||||
|
||||
/** |
||||
* Create new parse error |
||||
* |
||||
* @param message error message |
||||
*/ |
||||
public ParseError(String message) { |
||||
|
||||
super(message); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Wrapper another exception in a parse error |
||||
* |
||||
* @param e other exception |
||||
*/ |
||||
public ParseError(Exception e) { |
||||
|
||||
super(e); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,74 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Rational calculator, expression parser returning fractional results instead |
||||
* of imprecise decimal values. |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class RCalc implements IDebugable { |
||||
|
||||
/** Debug mode flag */ |
||||
private boolean debug = false; |
||||
/** Tokenizer instance */ |
||||
private Tokenizer tokenizer = new Tokenizer(); |
||||
|
||||
|
||||
/** |
||||
* Create new rational calculator |
||||
*/ |
||||
public RCalc() { |
||||
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean isDebug() { |
||||
|
||||
return debug; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setDebug(boolean debug) { |
||||
|
||||
tokenizer.setDebug(debug); |
||||
this.debug = debug; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Calculate a result of an expression. |
||||
* |
||||
* @param expression input expression |
||||
* @return computed result |
||||
*/ |
||||
public Fraction evaluate(String expression) { |
||||
|
||||
IEvaluable ev; |
||||
|
||||
|
||||
try { |
||||
TokenList tl = tokenizer.tokenize(expression); |
||||
|
||||
if (debug) System.out.println("\nBuilding syntax tree..."); |
||||
|
||||
ev = tl.parse(); |
||||
} catch (ParseError e) { |
||||
throw e; |
||||
} catch (NumberFormatException e) { |
||||
throw new ParseError("Invalid number format."); |
||||
} catch (Exception e) { |
||||
throw new ParseError(e); |
||||
} |
||||
|
||||
if (debug) System.out.println("\nSyntax tree:\n" + ev + "\n"); |
||||
|
||||
Fraction out = ev.evaluate(); |
||||
return out; |
||||
} |
||||
} |
@ -0,0 +1,172 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map.Entry; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Rational Calculator Session - interactive RCalc with possibility to set and |
||||
* use variables. |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class RCalcSession implements IDebugable { |
||||
|
||||
/** Debug mode flag */ |
||||
private boolean debug = false; |
||||
/** Calculator instance */ |
||||
private RCalc rcalc = new RCalc(); |
||||
/** Table of user defined variables */ |
||||
private HashMap<String, Fraction> symbolTable = new HashMap<String, Fraction>(); |
||||
|
||||
|
||||
@Override |
||||
public boolean isDebug() { |
||||
|
||||
return debug; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setDebug(boolean debug) { |
||||
|
||||
rcalc.setDebug(debug); |
||||
this.debug = debug; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get variable value |
||||
* |
||||
* @param name variable name |
||||
* @return the value |
||||
*/ |
||||
public Fraction getVariable(String name) { |
||||
|
||||
return symbolTable.get(name.toLowerCase()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set a variable value |
||||
* |
||||
* @param name variable name |
||||
* @param value value to set |
||||
*/ |
||||
public void setVariable(String name, Fraction value) { |
||||
|
||||
symbolTable.put(name.trim(), value); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Evaluate an expression |
||||
* |
||||
* @param expression expression |
||||
* @return result |
||||
*/ |
||||
public Fraction evaluate(String expression) { |
||||
|
||||
expression = expression.trim().toLowerCase(); |
||||
|
||||
if (expression.length() == 0) return null; // no operation
|
||||
|
||||
expression.replace(":=", "="); // normalize assignment sign
|
||||
|
||||
String assignedVariable = null; |
||||
String assignmentOperation = ""; |
||||
|
||||
// parse name=value syntax for variables
|
||||
if (expression.matches("^[a-z]+\\s*[+\\-*/%^]?=\\s*(.+)$")) { |
||||
Pattern pattern = Pattern.compile("^([a-z]+)\\s*([+\\-*/%^]?=)\\s*(.+)$"); |
||||
Matcher matcher = pattern.matcher(expression); |
||||
|
||||
if (!matcher.find() || matcher.groupCount() != 3) { |
||||
throw new ParseError("Invalid expression format, check your syntax."); |
||||
} |
||||
|
||||
assignedVariable = matcher.group(1).trim(); |
||||
assignmentOperation = matcher.group(2).trim(); |
||||
expression = matcher.group(3).trim(); |
||||
} |
||||
|
||||
// replace variable names with their values
|
||||
for (Entry<String, Fraction> entry : symbolTable.entrySet()) { |
||||
String regex = "(?:(?<![a-z])|^)" + entry.getKey() + "(?:(?![a-z])|$)"; |
||||
String replacement = "(" + entry.getValue() + ")"; |
||||
expression = expression.replaceAll(regex, replacement); |
||||
} |
||||
|
||||
// calculate result
|
||||
Fraction result = rcalc.evaluate(expression); |
||||
|
||||
// assign variable value if requested
|
||||
if (assignedVariable != null) { |
||||
|
||||
Fraction oldValue = symbolTable.get(assignedVariable); |
||||
|
||||
if (oldValue == null) oldValue = Fraction.ZERO; |
||||
|
||||
if (assignmentOperation.equals("=")) { |
||||
// no-op
|
||||
|
||||
} else if (assignmentOperation.equals("+=")) { |
||||
|
||||
result = oldValue.add(result); |
||||
|
||||
} else if (assignmentOperation.equals("-=")) { |
||||
|
||||
result = oldValue.subtract(result); |
||||
|
||||
} else if (assignmentOperation.equals("*=")) { |
||||
|
||||
result = oldValue.multiply(result); |
||||
|
||||
} else if (assignmentOperation.equals("/=")) { |
||||
|
||||
result = oldValue.divide(result); |
||||
|
||||
} else if (assignmentOperation.equals("%=")) { |
||||
|
||||
result = oldValue.modulo(result); |
||||
|
||||
} else if (assignmentOperation.equals("^=")) { |
||||
|
||||
result = oldValue.power(result); |
||||
} |
||||
|
||||
symbolTable.put(assignedVariable, result); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set symbol table (can be used to share it among multiple instances) |
||||
* |
||||
* @param table the symbol table |
||||
*/ |
||||
public void setSymbolTable(HashMap<String, Fraction> table) { |
||||
|
||||
this.symbolTable = table; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get the current symbol table |
||||
* |
||||
* @return symbol table |
||||
*/ |
||||
public HashMap<String, Fraction> getSymbolTable() { |
||||
|
||||
return symbolTable; |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,268 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Stack; |
||||
|
||||
import net.mightypork.rcalc.operations.Operation; |
||||
import net.mightypork.rcalc.tokens.*; |
||||
|
||||
|
||||
/** |
||||
* A list of tokens which can be parsed into a single IEvaluable |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenList extends ArrayList<IToken> implements IToken { |
||||
|
||||
/** |
||||
* Create a TokenList with defined initial size |
||||
* |
||||
* @param i initial size |
||||
*/ |
||||
public TokenList(int i) { |
||||
|
||||
super(i); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create a blank TokenList |
||||
*/ |
||||
public TokenList() { |
||||
|
||||
super(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Parse the token list.<br> |
||||
* After running this method, the list is typically reduced to a single |
||||
* token |
||||
* |
||||
* @return the remaining token |
||||
*/ |
||||
public IEvaluableToken parse() { |
||||
|
||||
try { |
||||
// 1. Extract all parentheses into TokenLists
|
||||
extractParentheses(); |
||||
|
||||
// 2. Convert operator tokens to operators, including their arguments
|
||||
// in the correct order ^ * / + -
|
||||
extractOperator(TokenOperatorFactorial.class); |
||||
extractOperator(TokenOperatorPower.class); |
||||
extractOperator(TokenOperatorMultiply.class); |
||||
extractOperator(TokenOperatorDivide.class); |
||||
extractOperator(TokenOperatorModulo.class); |
||||
extractOperator(TokenOperatorAdd.class); |
||||
extractOperator(TokenOperatorSubtract.class); |
||||
|
||||
// 3. Check for leftovers, throw error if any
|
||||
if (this.size() > 1) { |
||||
throw new ParseError("TokenList didn't parse entirely, probably a syntax error: " + this); |
||||
} |
||||
|
||||
// 4. If last token is a TokenList, use its content instead
|
||||
if (this.get(0) instanceof TokenList) { |
||||
TokenList theList = (TokenList) this.get(0); |
||||
if (theList.size() == 1 && (theList.get(0) instanceof IEvaluableToken)) { |
||||
IEvaluableToken eval = (IEvaluableToken) theList.get(0); |
||||
this.clear(); |
||||
this.add(eval); |
||||
} |
||||
} |
||||
|
||||
// 5. Throw error if last token remaining is not evaluable
|
||||
if (!(this.get(0) instanceof IEvaluableToken)) { |
||||
throw new ParseError("Last token in a TokenList is not evaluable: " + this.get(0)); |
||||
} |
||||
|
||||
} catch (ParseError e) { |
||||
throw e; |
||||
} catch (IndexOutOfBoundsException e) { |
||||
throw new ParseError("Missing operand(s)."); |
||||
} catch (Exception e) { |
||||
throw new ParseError(e); |
||||
} |
||||
|
||||
return (IEvaluableToken) this.get(0); // checked earlier
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Find tokens of given operator class, and convert them to the operations |
||||
* including their operands. |
||||
* |
||||
* @param operatorClass class of the operator to find |
||||
*/ |
||||
private void extractOperator(Class<? extends IOperatorToken> operatorClass) { |
||||
|
||||
Stack<Integer> positions = new Stack<Integer>(); |
||||
|
||||
// find operator positions
|
||||
for (int i = size() - 1; i >= 0; i--) { |
||||
if (operatorClass.isInstance(get(i))) { |
||||
positions.push(i); |
||||
} |
||||
} |
||||
|
||||
// offset, used when positions are updated while replacing tokens
|
||||
int offset = 0; |
||||
|
||||
while (!positions.isEmpty()) { |
||||
|
||||
// get position of the next token
|
||||
int pos = positions.pop() + offset; |
||||
|
||||
IToken operatorToken = get(pos); |
||||
|
||||
if (operatorToken instanceof TokenBinaryOperator) { |
||||
|
||||
TokenBinaryOperator opToken = (TokenBinaryOperator) operatorToken; |
||||
|
||||
// variables for tokenLists of the operands
|
||||
TokenList leftTL, rightTL; |
||||
|
||||
// get left operand
|
||||
IToken leftArg = get(pos - 1); |
||||
if (leftArg instanceof TokenList) { |
||||
leftTL = (TokenList) leftArg; |
||||
} else { |
||||
leftTL = new TokenList(1); |
||||
leftTL.add(leftArg); |
||||
} |
||||
|
||||
// get right operand
|
||||
IToken rightArg = get(pos + 1); |
||||
if (rightArg instanceof TokenList) { |
||||
rightTL = (TokenList) rightArg; |
||||
} else { |
||||
rightTL = new TokenList(1); |
||||
rightTL.add(rightArg); |
||||
} |
||||
|
||||
// build an operation
|
||||
Operation op = opToken.toOperation(leftTL, rightTL); |
||||
|
||||
// discard used tokens
|
||||
subList(pos - 1, pos + 2).clear(); |
||||
// put back the operation
|
||||
add(pos - 1, op); |
||||
// shift offset
|
||||
offset -= 2; |
||||
|
||||
} else if (operatorToken instanceof TokenUnaryOperatorLeft) { |
||||
|
||||
TokenUnaryOperatorLeft opToken = (TokenUnaryOperatorLeft) operatorToken; |
||||
|
||||
// variable for left operand
|
||||
TokenList leftTL; |
||||
|
||||
// get left operand (the only one)
|
||||
IToken leftArg = get(pos - 1); |
||||
if (leftArg instanceof TokenList) { |
||||
leftTL = (TokenList) leftArg; |
||||
} else { |
||||
leftTL = new TokenList(1); |
||||
leftTL.add(leftArg); |
||||
} |
||||
|
||||
// build an operation
|
||||
Operation op = opToken.toOperation(leftTL); |
||||
|
||||
// discard used tokens
|
||||
subList(pos - 1, pos + 1).clear(); |
||||
// put back the operation
|
||||
add(pos - 1, op); |
||||
// shift offset
|
||||
offset -= 1; |
||||
|
||||
} else if (operatorToken instanceof TokenUnaryOperatorRight) { |
||||
|
||||
TokenUnaryOperatorRight opToken = (TokenUnaryOperatorRight) operatorToken; |
||||
|
||||
// variable for left operand
|
||||
TokenList rightTL; |
||||
|
||||
// get right operand (the only one)
|
||||
IToken rightArg = get(pos + 1); |
||||
if (rightArg instanceof TokenList) { |
||||
rightTL = (TokenList) rightArg; |
||||
} else { |
||||
rightTL = new TokenList(1); |
||||
rightTL.add(rightArg); |
||||
} |
||||
|
||||
// build an operation
|
||||
Operation op = opToken.toOperation(rightTL); |
||||
|
||||
// discard used tokens
|
||||
subList(pos, pos + 2).clear(); |
||||
// put back the operation
|
||||
add(pos, op); |
||||
// shift offset
|
||||
offset -= 1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Convert outer parenthesized blocks to individual tokens |
||||
*/ |
||||
private void extractParentheses() { |
||||
|
||||
int open = 0; // level of outer parentheses
|
||||
int openIgnored = 0; // level of inner parentheses, to be skipped
|
||||
|
||||
Stack<Integer> lefts = new Stack<Integer>(); |
||||
Stack<Integer> rights = new Stack<Integer>(); |
||||
|
||||
for (int i = 0; i < size(); i++) { |
||||
|
||||
if (get(i) instanceof TokenParenthesisLeft) { |
||||
|
||||
if (open == 0) { |
||||
open++; |
||||
lefts.push(i); |
||||
} else { |
||||
openIgnored++; |
||||
} |
||||
} |
||||
|
||||
if (get(i) instanceof TokenParenthesisRight) { |
||||
|
||||
if (openIgnored > 0) { |
||||
openIgnored--; |
||||
} else { |
||||
open--; |
||||
if (open == 0) rights.push(i); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (open > 0) throw new ParseError("Unbalanced parentheses."); |
||||
|
||||
// extract token lists inside the outer parentheses, put them back as single tokens
|
||||
while (!lefts.isEmpty()) { |
||||
int left = lefts.pop(); |
||||
int right = rights.pop(); |
||||
|
||||
int innerLeft = left + 1; |
||||
int innerRight = right - 1; |
||||
|
||||
// prepare tokenList for the inner tokens
|
||||
TokenList insides = new TokenList(); |
||||
// add the inner tokens
|
||||
insides.addAll(subList(innerLeft, innerRight + 1)); |
||||
// destroy the whole range including parentheses
|
||||
subList(left, right + 1).clear(); |
||||
// put replacement token back
|
||||
add(left, insides.parse()); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,185 @@ |
||||
package net.mightypork.rcalc; |
||||
|
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
import net.mightypork.rcalc.tokens.*; |
||||
|
||||
|
||||
/** |
||||
* Utility for converting an expression from String to a list of tokens |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class Tokenizer implements IDebugable { |
||||
|
||||
/** Debug mode flag */ |
||||
private boolean debug = false; |
||||
|
||||
/** Deep debug mode flag */ |
||||
private boolean deepDebug = false; |
||||
|
||||
|
||||
@Override |
||||
public boolean isDebug() { |
||||
|
||||
return debug; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setDebug(boolean debug) { |
||||
|
||||
this.debug = debug; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get if deep debug mode is enabled |
||||
* |
||||
* @return is in deep debug mode |
||||
*/ |
||||
public boolean isDeepDebug() { |
||||
|
||||
return deepDebug && debug; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Enable / disable deep debug mode |
||||
* |
||||
* @param debug |
||||
*/ |
||||
public void setDeepDebug(boolean debug) { |
||||
|
||||
this.deepDebug = this.debug = debug; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Parse a string to a list of expression tokens |
||||
* |
||||
* @param input string with expression to parse |
||||
* @return token list |
||||
*/ |
||||
public TokenList tokenize(String input) { |
||||
|
||||
// discard whitespace
|
||||
input = input.replaceAll("\\s", ""); |
||||
|
||||
// check parentheses
|
||||
int level = 0; |
||||
for (char c : input.toCharArray()) { |
||||
if (c == '(') level++; |
||||
if (c == ')') level--; |
||||
|
||||
if (level < 0) throw new ParseError("Unbalanced parentheses."); |
||||
} |
||||
if (level != 0) throw new ParseError("Unbalanced parentheses."); |
||||
|
||||
// translate ** to ^
|
||||
input = input.replaceAll("[*]{2}", "^"); |
||||
if (debug && deepDebug) System.out.println("\nTranslate '**' to '^'\n" + input); |
||||
|
||||
// enclose ^ left operand in parentheses
|
||||
input = input.replaceAll("([0-9.]+[!]?)\\^", "($1)^"); |
||||
if (debug && deepDebug) System.out.println("\nWrap left numeric operand of '^' with parentheses\n" + input); |
||||
|
||||
// add multiplication operator for implicit multiplication
|
||||
input = input.replaceAll("(?<=[0-9)!])(?=[(])", "*"); // )( -> )*(
|
||||
input = input.replaceAll("(?<=[)!])(?=[0-9(])", "*"); |
||||
if (debug && deepDebug) System.out.println("\nApply implicit multiplication\n" + input); |
||||
|
||||
// normalize series of '+' and '-'
|
||||
StringBuffer outputBuffer = new StringBuffer(); |
||||
Matcher matcher = Pattern.compile("([+\\-]{2,})").matcher(input); |
||||
while (matcher.find()) { |
||||
int minus = 0; |
||||
for (char c : matcher.group().toCharArray()) { |
||||
if (c == '-') minus++; |
||||
} |
||||
minus = minus % 2; |
||||
matcher.appendReplacement(outputBuffer, minus == 0 ? "+" : "-"); |
||||
} |
||||
matcher.appendTail(outputBuffer); |
||||
input = outputBuffer.toString(); |
||||
if (debug && deepDebug) System.out.println("\nNormalize sequences of '+' and '-'\n" + input); |
||||
|
||||
// turn '-' to '+-' between numbers
|
||||
input = input.replaceAll("(?<=[0-9)!])-(?=[0-9])", "+-"); |
||||
if (debug && deepDebug) System.out.println("\n'-' to '+-' between numbers\n" + input); |
||||
|
||||
// convert '-' to '+-1*' in front of non-numbers
|
||||
input = input.replaceAll("-(?=[^0-9])", "+-1*"); |
||||
if (debug && deepDebug) System.out.println("\n'-' to '+-1*' in front of non-numbers\n" + input); |
||||
|
||||
// discard useless + signs
|
||||
input = input.replaceAll("(?<=[^)0-9!]|^)\\+", ""); |
||||
if (debug && deepDebug) System.out.println("\nDiscard '+' at beginning of scope\n" + input); |
||||
|
||||
if (debug) System.out.println("\nParsing tokens..."); |
||||
|
||||
TokenList list = new TokenList(); |
||||
|
||||
String buffer = ""; |
||||
|
||||
boolean parsingNumber = false; |
||||
|
||||
for (Character c : input.toCharArray()) { |
||||
if (c.toString().matches("[\\-0-9.]")) { |
||||
buffer += c; |
||||
parsingNumber = true; |
||||
} else { |
||||
|
||||
if (parsingNumber) { |
||||
list.add(new Fraction(buffer)); |
||||
buffer = ""; |
||||
parsingNumber = false; |
||||
} |
||||
|
||||
switch (c) { |
||||
case '(': |
||||
list.add(new TokenParenthesisLeft()); |
||||
break; |
||||
case ')': |
||||
list.add(new TokenParenthesisRight()); |
||||
break; |
||||
case '*': |
||||
list.add(new TokenOperatorMultiply()); |
||||
break; |
||||
case '/': |
||||
list.add(new TokenOperatorDivide()); |
||||
break; |
||||
case '+': |
||||
list.add(new TokenOperatorAdd()); |
||||
break; |
||||
case '!': |
||||
list.add(new TokenOperatorFactorial()); |
||||
break; |
||||
case '%': |
||||
list.add(new TokenOperatorModulo()); |
||||
break; |
||||
case '^': |
||||
list.add(new TokenOperatorPower()); |
||||
break; |
||||
default: |
||||
throw new ParseError("Unknown token " + c); |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
// include possible last number
|
||||
if (parsingNumber) { |
||||
list.add(new Fraction(buffer)); |
||||
} |
||||
|
||||
if (debug) System.out.println("\nToken list:\n" + list); |
||||
|
||||
|
||||
return list; |
||||
} |
||||
} |
@ -0,0 +1,451 @@ |
||||
package net.mightypork.rcalc.numbers; |
||||
|
||||
|
||||
import java.math.BigInteger; |
||||
|
||||
import net.mightypork.rcalc.IEvaluableToken; |
||||
import net.mightypork.rcalc.ParseError; |
||||
|
||||
|
||||
/** |
||||
* A fractional number with methods for basic arithmetics |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class Fraction implements IEvaluableToken { |
||||
|
||||
/** Zero fraction (0/1) */ |
||||
public static final Fraction ZERO = new Fraction(BigInteger.ZERO); |
||||
/** One fraction (1/1) */ |
||||
public static final Fraction ONE = new Fraction(BigInteger.ZERO); |
||||
|
||||
|
||||
private BigInteger numerator = BigInteger.ZERO; |
||||
private BigInteger denominator = BigInteger.ONE; |
||||
|
||||
|
||||
/** |
||||
* Create fraction from numerator and 1 as denominator |
||||
* |
||||
* @param numerator numerator |
||||
*/ |
||||
public Fraction(long numerator) { |
||||
|
||||
this.numerator = BigInteger.valueOf(numerator); |
||||
this.denominator = BigInteger.ONE; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create fraction from numerator and 1 as denominator |
||||
* |
||||
* @param numerator numerator |
||||
*/ |
||||
public Fraction(BigInteger numerator) { |
||||
|
||||
this.numerator = numerator; |
||||
this.denominator = BigInteger.ONE; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create fraction from numerator and denominator |
||||
* |
||||
* @param numerator numerator |
||||
* @param denominator denominator |
||||
*/ |
||||
public Fraction(long numerator, long denominator) { |
||||
|
||||
if (denominator == 0) { |
||||
throw new ArithmeticException("Division by zero."); |
||||
} |
||||
|
||||
this.numerator = BigInteger.valueOf(numerator); |
||||
this.denominator = BigInteger.valueOf(denominator); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create fraction from numerator and denominator |
||||
* |
||||
* @param numerator numerator |
||||
* @param denominator denominator |
||||
*/ |
||||
public Fraction(BigInteger numerator, BigInteger denominator) { |
||||
|
||||
if (denominator.equals(BigInteger.ZERO)) { |
||||
throw new ArithmeticException("Division by zero."); |
||||
} |
||||
|
||||
this.numerator = numerator; |
||||
this.denominator = denominator; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create a fraction as a copy of another |
||||
* |
||||
* @param other other fraction to copy |
||||
*/ |
||||
public Fraction(Fraction other) { |
||||
|
||||
this.numerator = other.numerator; |
||||
this.denominator = other.denominator; |
||||
} |
||||
|
||||
|
||||
public Fraction(String number) { |
||||
if(number.contains(".")) { |
||||
String[] parts = number.split("[.]"); |
||||
try{ |
||||
if(parts.length != 2) throw new ParseError(""); // for the catch
|
||||
this.numerator = new BigInteger(parts[0]+parts[1]); |
||||
this.denominator = BigInteger.valueOf(10).pow(parts[1].length()); |
||||
this.simplify_ip(); |
||||
}catch(Exception e) { |
||||
e.printStackTrace(); |
||||
throw new ParseError("Invalid number format."); |
||||
} |
||||
} else { |
||||
this.numerator = new BigInteger(number); |
||||
this.denominator = BigInteger.ONE; |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get numerator number |
||||
* |
||||
* @return numerator |
||||
*/ |
||||
public BigInteger getNumerator() { |
||||
|
||||
return numerator; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get denominator number |
||||
* |
||||
* @return numerator |
||||
*/ |
||||
public BigInteger getDenominator() { |
||||
|
||||
return numerator; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Add a number |
||||
* |
||||
* @param operand addend |
||||
* @return this + addend |
||||
*/ |
||||
public Fraction add(Fraction operand) { |
||||
|
||||
Fraction a = this.getCopy(); |
||||
Fraction b = operand.getCopy(); |
||||
|
||||
a.numerator = a.numerator.multiply(b.denominator); |
||||
b.numerator = b.numerator.multiply(a.denominator); |
||||
a.denominator = a.denominator.multiply(b.denominator); |
||||
|
||||
a.numerator = a.numerator.add(b.numerator); |
||||
|
||||
return a.simplify(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Subtract a number |
||||
* |
||||
* @param operand subtrahend |
||||
* @return this - subtrahend |
||||
*/ |
||||
public Fraction subtract(Fraction operand) { |
||||
|
||||
Fraction negated = new Fraction(operand.numerator.negate(), operand.denominator); |
||||
return this.add(negated); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Multiply by a number |
||||
* |
||||
* @param operand multiplier |
||||
* @return this * multiplier |
||||
*/ |
||||
public Fraction multiply(Fraction operand) { |
||||
|
||||
BigInteger n = numerator.multiply(operand.numerator); |
||||
BigInteger d = denominator.multiply(operand.denominator); |
||||
return (new Fraction(n, d)).simplify(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Divide by a number |
||||
* |
||||
* @param operand divisor |
||||
* @return this / divisor |
||||
*/ |
||||
public Fraction divide(Fraction operand) { |
||||
|
||||
Fraction flipped = new Fraction(operand.denominator, operand.numerator); |
||||
return this.multiply(flipped); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Modulo with a number |
||||
* |
||||
* @param operand divisor |
||||
* @return this % divisor |
||||
*/ |
||||
public Fraction modulo(Fraction operand) { |
||||
|
||||
if (isFractional() || operand.isFractional()) { |
||||
throw new ArithmeticException("Modulo is not defined for fractional operands."); |
||||
} |
||||
|
||||
BigInteger i = this.getBigIntegerValue(); |
||||
BigInteger o = operand.getBigIntegerValue(); |
||||
|
||||
return new Fraction(i.mod(o)); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get an n-th power of the number |
||||
* |
||||
* @param operand power |
||||
* @return this ^ power |
||||
*/ |
||||
public Fraction power(Fraction operand) { |
||||
|
||||
if (operand.isFractional()) { |
||||
throw new ArithmeticException("Can't calculate fractional power."); |
||||
} |
||||
|
||||
int power = operand.getIntegerValue(); |
||||
|
||||
boolean swap = power < 0; |
||||
|
||||
power = Math.abs(power); |
||||
|
||||
BigInteger n = numerator.pow(power); |
||||
BigInteger d = denominator.pow(power); |
||||
|
||||
Fraction out = (new Fraction(n, d)).simplify(); |
||||
|
||||
if (swap) out = out.invert(); |
||||
|
||||
return out; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get a fraction with swapped numerator and denominator. |
||||
* |
||||
* @return inverted fraction |
||||
*/ |
||||
public Fraction invert() { |
||||
|
||||
if (numerator.equals(BigInteger.ZERO)) { |
||||
throw new ArithmeticException("Division by zero."); |
||||
} |
||||
|
||||
return new Fraction(denominator, numerator); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Simplify the fraction |
||||
* |
||||
* @return simplified fraction |
||||
*/ |
||||
public Fraction simplify() { |
||||
|
||||
BigInteger gcd = numerator.gcd(denominator); |
||||
|
||||
return new Fraction(numerator.divide(gcd), denominator.divide(gcd)); |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
* Simplify the fraction (in place) |
||||
*/ |
||||
private void simplify_ip() { |
||||
BigInteger gcd = numerator.gcd(denominator); |
||||
|
||||
numerator = numerator.divide(gcd); |
||||
denominator = denominator.divide(gcd); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Check if the number is fractional |
||||
* |
||||
* @return is fractional |
||||
*/ |
||||
public boolean isFractional() { |
||||
|
||||
return !(numerator.mod(denominator)).equals(BigInteger.ZERO); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get an integer numeric value |
||||
* |
||||
* @return value |
||||
* @throws UnsupportedOperationException for fractional numbers |
||||
*/ |
||||
public int getIntegerValue() { |
||||
|
||||
if (isFractional()) { |
||||
throw new ArithmeticException("Can't take integer value of a fractional number."); |
||||
} |
||||
|
||||
BigInteger a = numerator.divide(denominator); |
||||
if (a.longValue() > Integer.MAX_VALUE) { |
||||
throw new ArithmeticException("Can't convert to integer, number is too big."); |
||||
} |
||||
|
||||
return a.intValue(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get a BigInteger numeric value |
||||
* |
||||
* @return value |
||||
* @throws UnsupportedOperationException for fractional numbers |
||||
*/ |
||||
public BigInteger getBigIntegerValue() { |
||||
|
||||
if (this.isFractional()) { |
||||
throw new ArithmeticException("Can't take BigInteger value of a fractional number."); |
||||
} |
||||
|
||||
return numerator.divide(denominator); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get a double-precision numeric value |
||||
* |
||||
* @return value |
||||
*/ |
||||
public double getDoubleValue() { |
||||
|
||||
double a = numerator.doubleValue(); |
||||
double b = denominator.doubleValue(); |
||||
|
||||
if (Double.isInfinite(a) || Double.isInfinite(b)) { |
||||
throw new ArithmeticException("Can't convert BigInteger to double, number is too big."); |
||||
} |
||||
|
||||
return a / b; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get a identical copy of this fraction |
||||
* |
||||
* @return copy |
||||
*/ |
||||
private Fraction getCopy() { |
||||
|
||||
return new Fraction(this); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
|
||||
final int prime = 31; |
||||
int result = 1; |
||||
result = prime * result + ((denominator == null) ? 0 : denominator.hashCode()); |
||||
result = prime * result + ((numerator == null) ? 0 : numerator.hashCode()); |
||||
return result; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
|
||||
if (this == obj) return true; |
||||
if (obj == null) return false; |
||||
if (getClass() != obj.getClass()) return false; |
||||
Fraction other = (Fraction) obj; |
||||
|
||||
if (denominator == null || other.denominator == null) return false; |
||||
if (numerator == null || other.numerator == null) return false; |
||||
|
||||
Fraction thisSimplified = this.simplify(); |
||||
Fraction otherSimplified = other.simplify(); |
||||
|
||||
return thisSimplified.numerator.equals(otherSimplified.numerator) && thisSimplified.denominator.equals(otherSimplified.denominator); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Check if the toString() of this fraction equals to the given string |
||||
* |
||||
* @param str compared string |
||||
* @return equals |
||||
*/ |
||||
public boolean equalsToString(String str) { |
||||
|
||||
return this.toString().equals(str); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get a string representation of this fraction in the format |
||||
* "numerator/denominator", alternatively "numerator" if denominator is |
||||
* equal to 1. |
||||
*/ |
||||
@Override |
||||
public String toString() { |
||||
|
||||
if (!isFractional()) return numerator.divide(denominator).toString(); |
||||
|
||||
return numerator + "/" + denominator; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Fraction evaluate() { |
||||
|
||||
return this; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Take a factorial of this number, provided it is not fractional. |
||||
* |
||||
* @return the factorial |
||||
*/ |
||||
public Fraction factorial() { |
||||
|
||||
if (this.isFractional()) { |
||||
throw new ArithmeticException("Can't get factorial of a fractional number."); |
||||
} |
||||
|
||||
BigInteger val = getBigIntegerValue(); |
||||
|
||||
if (val.compareTo(BigInteger.ZERO) < 0) { |
||||
throw new ArithmeticException("Can't get factorial of a negative number."); |
||||
} |
||||
|
||||
BigInteger result = BigInteger.ONE; |
||||
for (BigInteger cnt = BigInteger.ONE; cnt.compareTo(val) <= 0; cnt = cnt.add(BigInteger.ONE)) { |
||||
result = result.multiply(cnt); |
||||
} |
||||
|
||||
return new Fraction(result, BigInteger.ONE); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,37 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Abstract operation with two operands |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public abstract class BinaryOperation extends Operation { |
||||
|
||||
/** Left operand */ |
||||
protected IEvaluable left = null; |
||||
/** Right operand */ |
||||
protected IEvaluable right = null; |
||||
|
||||
|
||||
/** |
||||
* Create a binary operation |
||||
* |
||||
* @param left left operand |
||||
* @param right right operand |
||||
*/ |
||||
public BinaryOperation(IEvaluable left, IEvaluable right) { |
||||
|
||||
this.left = left; |
||||
this.right = right; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public abstract Fraction evaluate(); |
||||
|
||||
} |
@ -0,0 +1,17 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluableToken; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Abstract operation |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public abstract class Operation implements IEvaluableToken { |
||||
|
||||
@Override |
||||
public abstract Fraction evaluate(); |
||||
} |
@ -0,0 +1,39 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Addition |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class OperationAdd extends BinaryOperation { |
||||
|
||||
/** |
||||
* Create addition |
||||
* |
||||
* @param left left operand |
||||
* @param right right operand |
||||
*/ |
||||
public OperationAdd(IEvaluable left, IEvaluable right) { |
||||
|
||||
super(left, right); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Fraction evaluate() { |
||||
|
||||
return left.evaluate().add(right.evaluate()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "ADD{" + left + "," + right + "}"; |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Division |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class OperationDivide extends BinaryOperation { |
||||
|
||||
/** |
||||
* Create division |
||||
* |
||||
* @param left left operand |
||||
* @param right right operand |
||||
*/ |
||||
public OperationDivide(IEvaluable left, IEvaluable right) { |
||||
|
||||
super(left, right); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Fraction evaluate() { |
||||
|
||||
return left.evaluate().divide(right.evaluate()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "DIV{" + left + "," + right + "}"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,38 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Factorial |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class OperationFactorial extends UnaryOperation { |
||||
|
||||
/** |
||||
* Create factorial |
||||
* |
||||
* @param operand operand |
||||
*/ |
||||
public OperationFactorial(IEvaluable operand) { |
||||
|
||||
super(operand); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Fraction evaluate() { |
||||
|
||||
return operand.evaluate().factorial(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "FCT{" + operand + "}"; |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Modulo |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class OperationModulo extends BinaryOperation { |
||||
|
||||
/** |
||||
* Create modulo |
||||
* |
||||
* @param left left operand |
||||
* @param right right operand |
||||
*/ |
||||
public OperationModulo(IEvaluable left, IEvaluable right) { |
||||
|
||||
super(left, right); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Fraction evaluate() { |
||||
|
||||
return left.evaluate().modulo(right.evaluate()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "MOD{" + left + "," + right + "}"; |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Multiplication |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class OperationMultiply extends BinaryOperation { |
||||
|
||||
/** |
||||
* Create multiplication |
||||
* |
||||
* @param left left operand |
||||
* @param right right operand |
||||
*/ |
||||
public OperationMultiply(IEvaluable left, IEvaluable right) { |
||||
|
||||
super(left, right); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Fraction evaluate() { |
||||
|
||||
return left.evaluate().multiply(right.evaluate()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "MUL{" + left + "," + right + "}"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,40 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Exponentiation |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class OperationPower extends BinaryOperation { |
||||
|
||||
/** |
||||
* Create exponentiation |
||||
* |
||||
* @param left left operand |
||||
* @param right right operand |
||||
*/ |
||||
public OperationPower(IEvaluable left, IEvaluable right) { |
||||
|
||||
super(left, right); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Fraction evaluate() { |
||||
|
||||
return left.evaluate().power(right.evaluate()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "POW{" + left + "," + right + "}"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,40 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Subtraction |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class OperationSubtract extends BinaryOperation { |
||||
|
||||
/** |
||||
* Create subtraction |
||||
* |
||||
* @param left left operand |
||||
* @param right right operand |
||||
*/ |
||||
public OperationSubtract(IEvaluable left, IEvaluable right) { |
||||
|
||||
super(left, right); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Fraction evaluate() { |
||||
|
||||
return left.evaluate().subtract(right.evaluate()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "SUB{" + left + "," + right + "}"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,33 @@ |
||||
package net.mightypork.rcalc.operations; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IEvaluable; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Abstract unary operator |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public abstract class UnaryOperation extends Operation { |
||||
|
||||
/** Operand */ |
||||
protected IEvaluable operand = null; |
||||
|
||||
|
||||
/** |
||||
* Create unary operation |
||||
* |
||||
* @param operand operand |
||||
*/ |
||||
public UnaryOperation(IEvaluable operand) { |
||||
|
||||
this.operand = operand; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public abstract Fraction evaluate(); |
||||
|
||||
} |
@ -0,0 +1,14 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IToken; |
||||
|
||||
|
||||
/** |
||||
* Interface of an operator token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public interface IOperatorToken extends IToken { |
||||
|
||||
} |
@ -0,0 +1,23 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
|
||||
|
||||
/** |
||||
* Abstract operation token with two operands |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public abstract class TokenBinaryOperator implements IOperatorToken { |
||||
|
||||
/** |
||||
* Convert to operation |
||||
* |
||||
* @param left left operand |
||||
* @param right right operand |
||||
* @return operation |
||||
*/ |
||||
public abstract Operation toOperation(TokenList left, TokenList right); |
||||
} |
@ -0,0 +1,29 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
import net.mightypork.rcalc.operations.OperationAdd; |
||||
|
||||
|
||||
/** |
||||
* Addition token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenOperatorAdd extends TokenBinaryOperator { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "+"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Operation toOperation(TokenList left, TokenList right) { |
||||
|
||||
return new OperationAdd(left.parse(), right.parse()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
import net.mightypork.rcalc.operations.OperationDivide; |
||||
|
||||
|
||||
/** |
||||
* Division token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenOperatorDivide extends TokenBinaryOperator { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "/"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Operation toOperation(TokenList left, TokenList right) { |
||||
|
||||
return new OperationDivide(left.parse(), right.parse()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
import net.mightypork.rcalc.operations.OperationFactorial; |
||||
|
||||
|
||||
/** |
||||
* Factorial token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenOperatorFactorial extends TokenUnaryOperatorLeft { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "!"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Operation toOperation(TokenList argument) { |
||||
|
||||
return new OperationFactorial(argument.parse()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
import net.mightypork.rcalc.operations.OperationModulo; |
||||
|
||||
|
||||
/** |
||||
* Modulo token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenOperatorModulo extends TokenBinaryOperator { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "%"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Operation toOperation(TokenList left, TokenList right) { |
||||
|
||||
return new OperationModulo(left.parse(), right.parse()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
import net.mightypork.rcalc.operations.OperationMultiply; |
||||
|
||||
|
||||
/** |
||||
* Multiplication token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenOperatorMultiply extends TokenBinaryOperator { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "*"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Operation toOperation(TokenList left, TokenList right) { |
||||
|
||||
return new OperationMultiply(left.parse(), right.parse()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
import net.mightypork.rcalc.operations.OperationPower; |
||||
|
||||
|
||||
/** |
||||
* Exponentiation token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenOperatorPower extends TokenBinaryOperator { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "^"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Operation toOperation(TokenList left, TokenList right) { |
||||
|
||||
return new OperationPower(left.parse(), right.parse()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
import net.mightypork.rcalc.operations.OperationSubtract; |
||||
|
||||
|
||||
/** |
||||
* Subtraction token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenOperatorSubtract extends TokenBinaryOperator { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "-"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Operation toOperation(TokenList left, TokenList right) { |
||||
|
||||
return new OperationSubtract(left.parse(), right.parse()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IToken; |
||||
|
||||
|
||||
/** |
||||
* Left parenthesis token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenParenthesisLeft implements IToken { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return "("; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.IToken; |
||||
|
||||
|
||||
/** |
||||
* Right parenthesis token |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class TokenParenthesisRight implements IToken { |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
return ")"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,22 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
|
||||
|
||||
/** |
||||
* Abstract operator token with one operand on left |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public abstract class TokenUnaryOperatorLeft implements IOperatorToken { |
||||
|
||||
/** |
||||
* Convert to operation |
||||
* |
||||
* @param operand operand |
||||
* @return operation |
||||
*/ |
||||
public abstract Operation toOperation(TokenList operand); |
||||
} |
@ -0,0 +1,22 @@ |
||||
package net.mightypork.rcalc.tokens; |
||||
|
||||
|
||||
import net.mightypork.rcalc.TokenList; |
||||
import net.mightypork.rcalc.operations.Operation; |
||||
|
||||
|
||||
/** |
||||
* Abstract operator token with one operand on right |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public abstract class TokenUnaryOperatorRight implements IOperatorToken { |
||||
|
||||
/** |
||||
* Convert to operation |
||||
* |
||||
* @param operand operand |
||||
* @return operation |
||||
*/ |
||||
public abstract Operation toOperation(TokenList operand); |
||||
} |
@ -0,0 +1,131 @@ |
||||
package net.mightypork.semestralka; |
||||
|
||||
|
||||
import java.io.File; |
||||
import java.io.FileReader; |
||||
import java.io.IOException; |
||||
import java.util.HashMap; |
||||
import java.util.Scanner; |
||||
|
||||
import net.mightypork.rcalc.ParseError; |
||||
import net.mightypork.rcalc.RCalcSession; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Batch calculator (reads input from a file) |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class CalculatorBatch implements Runnable { |
||||
|
||||
/** Rcalc Session used by the calculator */ |
||||
private RCalcSession session = new RCalcSession(); |
||||
|
||||
/** File to load the expressions from */ |
||||
private File file; |
||||
|
||||
/** Show results as fractions */ |
||||
private boolean fractionalMode = true; |
||||
|
||||
/** Flag that this calculator has been called from interactive mode */ |
||||
private boolean fromInteractive; |
||||
|
||||
//@formatter:off
|
||||
private static final String introMessage = |
||||
"\nRational Calculator - file input mode\n" |
||||
+ "--------------------------------------\n" |
||||
+ "(c) Ondrej Hruska 2013\n" |
||||
+ "\n"; |
||||
//@formatter:on
|
||||
|
||||
/** |
||||
* Create a batch calculator |
||||
* |
||||
* @param fileToLoad file to be executed |
||||
*/ |
||||
public CalculatorBatch(File fileToLoad) { |
||||
|
||||
this.file = fileToLoad; |
||||
this.fromInteractive = false; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create a batch calculator |
||||
* |
||||
* @param fileToLoad file to be executed |
||||
* @param calledFromInteractive called from the interactive calculator |
||||
*/ |
||||
public CalculatorBatch(File fileToLoad, boolean calledFromInteractive) { |
||||
|
||||
this.file = fileToLoad; |
||||
this.fromInteractive = calledFromInteractive; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set symbol table (share variables with other session) |
||||
* |
||||
* @param symbolTable symbol table |
||||
*/ |
||||
public void setSymbolTable(HashMap<String, Fraction> symbolTable) { |
||||
|
||||
session.setSymbolTable(symbolTable); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void run() { |
||||
|
||||
if (!fromInteractive) { |
||||
System.out.println(introMessage); |
||||
} |
||||
|
||||
System.out.println("\nFILE = " + file.getAbsolutePath() + "\n"); |
||||
|
||||
// input scanner
|
||||
Scanner sc; |
||||
try { |
||||
sc = new Scanner(new FileReader(file)); |
||||
} catch (IOException e) { |
||||
System.out.println("ERROR:\n\tFile not found."); |
||||
return; |
||||
} |
||||
|
||||
System.out.println("--- File begin ---"); |
||||
|
||||
while (sc.hasNextLine()) { |
||||
|
||||
String expr = sc.nextLine().trim(); |
||||
System.out.println("\n> " + expr); |
||||
|
||||
if (expr.equalsIgnoreCase("fractional") || expr.equalsIgnoreCase("decimal")) { |
||||
|
||||
if (fractionalMode) { |
||||
System.out.println("[FRACTIONAL MODE OFF]"); |
||||
fractionalMode = false; |
||||
} else { |
||||
System.out.println("[FRACTIONAL MODE ON]"); |
||||
fractionalMode = true; |
||||
} |
||||
|
||||
} else { |
||||
try { |
||||
Fraction result = session.evaluate(expr); |
||||
System.out.println("= " + (fractionalMode ? result.toString() : result.getDoubleValue())); |
||||
} catch (ParseError pe) { |
||||
System.out.println("PARSE ERROR:\n\t" + pe.getMessage()); |
||||
System.out.println("\n--- Aborted ---"); |
||||
return; |
||||
} catch (ArithmeticException ae) { |
||||
System.out.println("MATH ERROR:\n\t" + ae.getMessage()); |
||||
System.out.println("\n--- Aborted ---"); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
System.out.println("\n--- File end ---"); |
||||
} |
||||
} |
@ -0,0 +1,192 @@ |
||||
package net.mightypork.semestralka; |
||||
|
||||
|
||||
import java.io.File; |
||||
import java.util.*; |
||||
import java.util.Map.Entry; |
||||
|
||||
import net.mightypork.rcalc.ParseError; |
||||
import net.mightypork.rcalc.RCalcSession; |
||||
import net.mightypork.rcalc.numbers.Fraction; |
||||
|
||||
|
||||
/** |
||||
* Interactive calculator |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class CalculatorInteractive implements Runnable { |
||||
|
||||
/** Rcalc Session used by the calculator */ |
||||
private RCalcSession session = new RCalcSession(); |
||||
|
||||
/** Show results as fractions */ |
||||
private boolean fractionalMode = true; |
||||
|
||||
/** Command history */ |
||||
private ArrayList<String> history = new ArrayList<String>(); |
||||
|
||||
|
||||
//@formatter:off
|
||||
private static final String introMessage = |
||||
"\nRational Calculator - interactive mode\n" |
||||
+ "--------------------------------------\n" |
||||
+ "(c) Ondrej Hruska 2013\n" |
||||
+ "\n" |
||||
+ "Type 'help' for more info.\n\n" |
||||
+ "Enter expression, 'exit' to quit."; |
||||
|
||||
|
||||
private static final String helpMessage = |
||||
"\n### HELP ###\n\n" |
||||
+ "Commands:\n" |
||||
+ "\thelp - show this help\n" |
||||
+ "\texit - quit the calculator\n" |
||||
+ "\tdebug - toggle debug mode (default off)\n" |
||||
+ "\tdecimal - toggle decimal/fractional output\n" |
||||
+ "\tvars - print a list of variables\n" |
||||
+ "\tload filename - execute commands from a file\n" |
||||
+ "\t\t(Expressions from the file can use/change variables)\n" |
||||
+ "\n" |
||||
+ "Mathematical operations, syntax\n" |
||||
+ "\tSupported: + - * / % ^ ! ( )\n" |
||||
+ "\tMultiplication sign can be omitted where it makes sense.\n" |
||||
+ "\tAlong with fractions, decimal-point format works too.\n" |
||||
+ "\tUse 'a/b' syntax to express a fraction.\n" |
||||
+ "\n" |
||||
+ "Variables\n" |
||||
+ "\tAssign a variable:\n" |
||||
+ "\t> name = expression\n\n" |
||||
+ "\tModify a variable:\n" |
||||
+ "\t> name += expression\n" |
||||
+ "\t> name -= expression\n" |
||||
+ "\t> name *= expression\n" |
||||
+ "\t> name /= expression\n" |
||||
+ "\t> name %= expression\n" |
||||
+ "\t> name ^= expression\n\n" |
||||
+ "\tYou can also use variable names in expressions."; |
||||
//@formatter:on
|
||||
|
||||
|
||||
/** |
||||
* Create an interactive calculator |
||||
*/ |
||||
public CalculatorInteractive() { |
||||
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void run() { |
||||
|
||||
System.out.println(introMessage); |
||||
|
||||
// input scanner
|
||||
Scanner sc = new Scanner(System.in); |
||||
|
||||
while (true) { |
||||
System.out.print("\n> "); |
||||
|
||||
// wait for input
|
||||
while (!sc.hasNextLine()) {} |
||||
|
||||
String expr = sc.nextLine().toLowerCase().trim(); |
||||
|
||||
if (expr.equals("%")) { // repeat last expression
|
||||
if (history.size() > 0) { |
||||
expr = history.get(history.size() - 1); |
||||
System.out.println("| " + expr); |
||||
} else { |
||||
System.out.println("No history available."); |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
if (expr.equals("exit")) { // quit
|
||||
System.out.println("Exit."); |
||||
return; |
||||
|
||||
} else if (expr.equals("help")) { // show help
|
||||
System.out.println(helpMessage); |
||||
|
||||
} else if (expr.equals("debug")) { // toggle debug mode
|
||||
|
||||
if (session.isDebug()) { |
||||
System.out.println("[DEBUG OFF]"); |
||||
session.setDebug(false); |
||||
} else { |
||||
System.out.println("[DEBUG ON]"); |
||||
session.setDebug(true); |
||||
} |
||||
|
||||
} else if (expr.equals("decimal")) { // toggle decimal/fractional output mode
|
||||
|
||||
if (fractionalMode) { |
||||
System.out.println("[FRACTIONAL MODE OFF]"); |
||||
fractionalMode = false; |
||||
} else { |
||||
System.out.println("[FRACTIONAL MODE ON]"); |
||||
fractionalMode = true; |
||||
} |
||||
|
||||
} else if (expr.startsWith("load ")) { // load and execute expressions from file
|
||||
expr = expr.replace("load ", ""); |
||||
|
||||
CalculatorBatch cb = new CalculatorBatch(new File(expr), true); |
||||
cb.setSymbolTable(session.getSymbolTable()); |
||||
|
||||
cb.run(); |
||||
|
||||
} else if (expr.equals("vars")) { // list variables
|
||||
|
||||
HashMap<String, Fraction> vars = (session.getSymbolTable()); |
||||
|
||||
Map<String, Fraction> varsSorted = sortByKeys(vars); |
||||
|
||||
System.out.println("\nVariables\n"); |
||||
|
||||
for (Entry<String, Fraction> entry : varsSorted.entrySet()) { |
||||
System.out.println("\t" + entry.getKey() + " = " + entry.getValue()); |
||||
} |
||||
|
||||
} else { // evaluate an expression
|
||||
|
||||
try { |
||||
Fraction result = session.evaluate(expr); |
||||
if (result == null) { |
||||
continue; |
||||
} |
||||
history.add(expr); |
||||
System.out.println("= " + (fractionalMode ? result.toString() : result.getDoubleValue())); |
||||
} catch (ParseError pe) { |
||||
System.out.println("PARSE ERROR:\n\t" + pe.getMessage()); |
||||
} catch (ArithmeticException ae) { |
||||
System.out.println("MATH ERROR:\n\t" + ae.getMessage()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Sort a map by keys, maintaining key-value pairs.<br> |
||||
* Source: StackOverflow.com |
||||
* |
||||
* @param map map to be sorted |
||||
* @return linked hash map with sorted entries |
||||
*/ |
||||
@SuppressWarnings({ "rawtypes", "unchecked" }) |
||||
private static <K extends Comparable, V> Map<K, V> sortByKeys(Map<K, V> map) { |
||||
|
||||
List<K> keys = new LinkedList<K>(map.keySet()); |
||||
Collections.sort(keys); |
||||
|
||||
Map<K, V> sortedMap = new LinkedHashMap<K, V>(); |
||||
for (K key : keys) { |
||||
sortedMap.put(key, map.get(key)); |
||||
} |
||||
|
||||
return sortedMap; |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
package net.mightypork.semestralka; |
||||
|
||||
|
||||
import java.io.File; |
||||
|
||||
|
||||
/** |
||||
* Application main class
|
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class Main { |
||||
|
||||
/** |
||||
* Program entry point |
||||
* |
||||
* @param args command line arguemnts |
||||
*/ |
||||
public static void main(String[] args) { |
||||
|
||||
if (args.length == 0 || (args.length == 1 && args[0].equals("-i"))) { |
||||
|
||||
Runnable task = new CalculatorInteractive(); |
||||
task.run(); |
||||
|
||||
} else if (args.length == 1) { |
||||
|
||||
Runnable task = new CalculatorBatch(new File(args[0])); |
||||
task.run(); |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,56 @@ |
||||
package net.mightypork.semestralka; |
||||
|
||||
|
||||
import net.mightypork.rcalc.RCalc; |
||||
|
||||
|
||||
/** |
||||
* RCalc unit tests |
||||
* |
||||
* @author Ondrej Hruska |
||||
*/ |
||||
public class Tests { |
||||
|
||||
/** |
||||
* Run the tests |
||||
*/ |
||||
public static void run() { |
||||
|
||||
RCalc rc = new RCalc(); |
||||
rc.setDebug(true); |
||||
|
||||
//@formatter:off
|
||||
String[] testCases = new String[] { |
||||
// Parser unit test
|
||||
"57**3 + -7^2 + (-7)^2", // power
|
||||
"(13*7)(99^2) * 4(12/7) + (17-8/2)13 + (4/3)*5", // implicit multiplication
|
||||
"(1-1)(1--1)(1---1)(1----1)(1--+-+-+++--+-+1)(1--+-+-+-+-----1)(5*-1)", // minus, plus
|
||||
"-(15/2)+(72*43-2)-(12+1)", // minus with parentheses
|
||||
"+34-(--8+2*+13)", // + at beginning of scope
|
||||
// misc
|
||||
"23%(6/5)", |
||||
"53/(5-4-1)", |
||||
"100!", |
||||
"24/4/3", |
||||
"(10+1)(15-3)", |
||||
"13(55-3/12)^2", |
||||
"(1/2)^-2", |
||||
"13^(1/2)", // should fail
|
||||
"(1/2)*(3/4)", |
||||
"(1/2)/(3/4)", // compound fraction
|
||||
}; |
||||
//@formatter:on
|
||||
|
||||
for (String expr : testCases) { |
||||
System.out.println("\n\n###### test case begin ######"); |
||||
|
||||
try { |
||||
System.out.println("IN: " + expr); |
||||
System.out.println("OUT: " + rc.evaluate(expr)); |
||||
} catch (Exception e) { |
||||
System.out.println("ERROR: " + e.getMessage()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
Loading…
Reference in new issue