added rcalc src
This commit is contained in:
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user