diff --git a/src b/src deleted file mode 120000 index 075a5ba..0000000 --- a/src +++ /dev/null @@ -1 +0,0 @@ -/home/ondra/Dokumenty/EclipseWorkspace/RCalc/src \ No newline at end of file diff --git a/src/net/mightypork/rcalc/IDebugable.java b/src/net/mightypork/rcalc/IDebugable.java new file mode 100644 index 0000000..2c737dc --- /dev/null +++ b/src/net/mightypork/rcalc/IDebugable.java @@ -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); + +} diff --git a/src/net/mightypork/rcalc/IEvaluable.java b/src/net/mightypork/rcalc/IEvaluable.java new file mode 100644 index 0000000..b8ed8bd --- /dev/null +++ b/src/net/mightypork/rcalc/IEvaluable.java @@ -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(); +} diff --git a/src/net/mightypork/rcalc/IEvaluableToken.java b/src/net/mightypork/rcalc/IEvaluableToken.java new file mode 100644 index 0000000..6f0f2d2 --- /dev/null +++ b/src/net/mightypork/rcalc/IEvaluableToken.java @@ -0,0 +1,11 @@ +package net.mightypork.rcalc; + + +/** + * Evaluable token interface + * + * @author Ondrej Hruska + */ +public interface IEvaluableToken extends IEvaluable, IToken { + +} diff --git a/src/net/mightypork/rcalc/IToken.java b/src/net/mightypork/rcalc/IToken.java new file mode 100644 index 0000000..54a28f1 --- /dev/null +++ b/src/net/mightypork/rcalc/IToken.java @@ -0,0 +1,11 @@ +package net.mightypork.rcalc; + + +/** + * Token interface + * + * @author Ondrej Hruska + */ +public interface IToken { + +} diff --git a/src/net/mightypork/rcalc/ParseError.java b/src/net/mightypork/rcalc/ParseError.java new file mode 100644 index 0000000..b35874c --- /dev/null +++ b/src/net/mightypork/rcalc/ParseError.java @@ -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); + } + +} diff --git a/src/net/mightypork/rcalc/RCalc.java b/src/net/mightypork/rcalc/RCalc.java new file mode 100644 index 0000000..c160ca7 --- /dev/null +++ b/src/net/mightypork/rcalc/RCalc.java @@ -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; + } +} diff --git a/src/net/mightypork/rcalc/RCalcSession.java b/src/net/mightypork/rcalc/RCalcSession.java new file mode 100644 index 0000000..92c779e --- /dev/null +++ b/src/net/mightypork/rcalc/RCalcSession.java @@ -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 symbolTable = new HashMap(); + + + @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 entry : symbolTable.entrySet()) { + String regex = "(?:(? table) { + + this.symbolTable = table; + } + + + /** + * Get the current symbol table + * + * @return symbol table + */ + public HashMap getSymbolTable() { + + return symbolTable; + } + + +} diff --git a/src/net/mightypork/rcalc/TokenList.java b/src/net/mightypork/rcalc/TokenList.java new file mode 100644 index 0000000..60fa0a7 --- /dev/null +++ b/src/net/mightypork/rcalc/TokenList.java @@ -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 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.
+ * 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 operatorClass) { + + Stack positions = new Stack(); + + // 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 lefts = new Stack(); + Stack rights = new Stack(); + + 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()); + } + + } + + +} diff --git a/src/net/mightypork/rcalc/Tokenizer.java b/src/net/mightypork/rcalc/Tokenizer.java new file mode 100644 index 0000000..bc1d69d --- /dev/null +++ b/src/net/mightypork/rcalc/Tokenizer.java @@ -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; + } +} diff --git a/src/net/mightypork/rcalc/numbers/Fraction.java b/src/net/mightypork/rcalc/numbers/Fraction.java new file mode 100644 index 0000000..9c53326 --- /dev/null +++ b/src/net/mightypork/rcalc/numbers/Fraction.java @@ -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); + } + +} diff --git a/src/net/mightypork/rcalc/operations/BinaryOperation.java b/src/net/mightypork/rcalc/operations/BinaryOperation.java new file mode 100644 index 0000000..d46beaa --- /dev/null +++ b/src/net/mightypork/rcalc/operations/BinaryOperation.java @@ -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(); + +} diff --git a/src/net/mightypork/rcalc/operations/Operation.java b/src/net/mightypork/rcalc/operations/Operation.java new file mode 100644 index 0000000..cd3254d --- /dev/null +++ b/src/net/mightypork/rcalc/operations/Operation.java @@ -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(); +} diff --git a/src/net/mightypork/rcalc/operations/OperationAdd.java b/src/net/mightypork/rcalc/operations/OperationAdd.java new file mode 100644 index 0000000..ee5d5bb --- /dev/null +++ b/src/net/mightypork/rcalc/operations/OperationAdd.java @@ -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 + "}"; + } +} diff --git a/src/net/mightypork/rcalc/operations/OperationDivide.java b/src/net/mightypork/rcalc/operations/OperationDivide.java new file mode 100644 index 0000000..886ffe0 --- /dev/null +++ b/src/net/mightypork/rcalc/operations/OperationDivide.java @@ -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 + "}"; + } + +} diff --git a/src/net/mightypork/rcalc/operations/OperationFactorial.java b/src/net/mightypork/rcalc/operations/OperationFactorial.java new file mode 100644 index 0000000..d5d56a9 --- /dev/null +++ b/src/net/mightypork/rcalc/operations/OperationFactorial.java @@ -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 + "}"; + } +} diff --git a/src/net/mightypork/rcalc/operations/OperationModulo.java b/src/net/mightypork/rcalc/operations/OperationModulo.java new file mode 100644 index 0000000..fc118e9 --- /dev/null +++ b/src/net/mightypork/rcalc/operations/OperationModulo.java @@ -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 + "}"; + } +} diff --git a/src/net/mightypork/rcalc/operations/OperationMultiply.java b/src/net/mightypork/rcalc/operations/OperationMultiply.java new file mode 100644 index 0000000..791fc54 --- /dev/null +++ b/src/net/mightypork/rcalc/operations/OperationMultiply.java @@ -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 + "}"; + } + +} diff --git a/src/net/mightypork/rcalc/operations/OperationPower.java b/src/net/mightypork/rcalc/operations/OperationPower.java new file mode 100644 index 0000000..8fe3bcf --- /dev/null +++ b/src/net/mightypork/rcalc/operations/OperationPower.java @@ -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 + "}"; + } + +} diff --git a/src/net/mightypork/rcalc/operations/OperationSubtract.java b/src/net/mightypork/rcalc/operations/OperationSubtract.java new file mode 100644 index 0000000..307cc0d --- /dev/null +++ b/src/net/mightypork/rcalc/operations/OperationSubtract.java @@ -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 + "}"; + } + +} diff --git a/src/net/mightypork/rcalc/operations/UnaryOperation.java b/src/net/mightypork/rcalc/operations/UnaryOperation.java new file mode 100644 index 0000000..c131787 --- /dev/null +++ b/src/net/mightypork/rcalc/operations/UnaryOperation.java @@ -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(); + +} diff --git a/src/net/mightypork/rcalc/tokens/IOperatorToken.java b/src/net/mightypork/rcalc/tokens/IOperatorToken.java new file mode 100644 index 0000000..a330571 --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/IOperatorToken.java @@ -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 { + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenBinaryOperator.java b/src/net/mightypork/rcalc/tokens/TokenBinaryOperator.java new file mode 100644 index 0000000..7709e2c --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenBinaryOperator.java @@ -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); +} diff --git a/src/net/mightypork/rcalc/tokens/TokenOperatorAdd.java b/src/net/mightypork/rcalc/tokens/TokenOperatorAdd.java new file mode 100644 index 0000000..ec020ca --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenOperatorAdd.java @@ -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()); + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenOperatorDivide.java b/src/net/mightypork/rcalc/tokens/TokenOperatorDivide.java new file mode 100644 index 0000000..536a4e4 --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenOperatorDivide.java @@ -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()); + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenOperatorFactorial.java b/src/net/mightypork/rcalc/tokens/TokenOperatorFactorial.java new file mode 100644 index 0000000..e8ac8f3 --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenOperatorFactorial.java @@ -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()); + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenOperatorModulo.java b/src/net/mightypork/rcalc/tokens/TokenOperatorModulo.java new file mode 100644 index 0000000..2e0237c --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenOperatorModulo.java @@ -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()); + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenOperatorMultiply.java b/src/net/mightypork/rcalc/tokens/TokenOperatorMultiply.java new file mode 100644 index 0000000..cae8f32 --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenOperatorMultiply.java @@ -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()); + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenOperatorPower.java b/src/net/mightypork/rcalc/tokens/TokenOperatorPower.java new file mode 100644 index 0000000..49085f1 --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenOperatorPower.java @@ -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()); + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenOperatorSubtract.java b/src/net/mightypork/rcalc/tokens/TokenOperatorSubtract.java new file mode 100644 index 0000000..22db6bc --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenOperatorSubtract.java @@ -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()); + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenParenthesisLeft.java b/src/net/mightypork/rcalc/tokens/TokenParenthesisLeft.java new file mode 100644 index 0000000..9e78600 --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenParenthesisLeft.java @@ -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 "("; + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenParenthesisRight.java b/src/net/mightypork/rcalc/tokens/TokenParenthesisRight.java new file mode 100644 index 0000000..73eca62 --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenParenthesisRight.java @@ -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 ")"; + } + +} diff --git a/src/net/mightypork/rcalc/tokens/TokenUnaryOperatorLeft.java b/src/net/mightypork/rcalc/tokens/TokenUnaryOperatorLeft.java new file mode 100644 index 0000000..e74d07e --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenUnaryOperatorLeft.java @@ -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); +} diff --git a/src/net/mightypork/rcalc/tokens/TokenUnaryOperatorRight.java b/src/net/mightypork/rcalc/tokens/TokenUnaryOperatorRight.java new file mode 100644 index 0000000..2812ced --- /dev/null +++ b/src/net/mightypork/rcalc/tokens/TokenUnaryOperatorRight.java @@ -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); +} diff --git a/src/net/mightypork/semestralka/CalculatorBatch.java b/src/net/mightypork/semestralka/CalculatorBatch.java new file mode 100644 index 0000000..9e32f9d --- /dev/null +++ b/src/net/mightypork/semestralka/CalculatorBatch.java @@ -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 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 ---"); + } +} diff --git a/src/net/mightypork/semestralka/CalculatorInteractive.java b/src/net/mightypork/semestralka/CalculatorInteractive.java new file mode 100644 index 0000000..6bd82c8 --- /dev/null +++ b/src/net/mightypork/semestralka/CalculatorInteractive.java @@ -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 history = new ArrayList(); + + + //@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 vars = (session.getSymbolTable()); + + Map varsSorted = sortByKeys(vars); + + System.out.println("\nVariables\n"); + + for (Entry 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.
+ * Source: StackOverflow.com + * + * @param map map to be sorted + * @return linked hash map with sorted entries + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static Map sortByKeys(Map map) { + + List keys = new LinkedList(map.keySet()); + Collections.sort(keys); + + Map sortedMap = new LinkedHashMap(); + for (K key : keys) { + sortedMap.put(key, map.get(key)); + } + + return sortedMap; + } +} diff --git a/src/net/mightypork/semestralka/Main.java b/src/net/mightypork/semestralka/Main.java new file mode 100644 index 0000000..b5069be --- /dev/null +++ b/src/net/mightypork/semestralka/Main.java @@ -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(); + + } + + } + + +} diff --git a/src/net/mightypork/semestralka/Tests.java b/src/net/mightypork/semestralka/Tests.java new file mode 100644 index 0000000..b852435 --- /dev/null +++ b/src/net/mightypork/semestralka/Tests.java @@ -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()); + } + } + + } +}