diff --git a/src/main/java/com/refinedmods/refinedstorage/util/EquationEvaluator.java b/src/main/java/com/refinedmods/refinedstorage/util/EquationEvaluator.java index ab5081dab..ef5e59d85 100644 --- a/src/main/java/com/refinedmods/refinedstorage/util/EquationEvaluator.java +++ b/src/main/java/com/refinedmods/refinedstorage/util/EquationEvaluator.java @@ -6,6 +6,8 @@ import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.function.Predicate; +import javax.annotation.Nullable; + /** * Implements evaluation for simple math expressions using the shunting yard * algorithm. @@ -13,47 +15,62 @@ import java.util.function.Predicate; public class EquationEvaluator { public static double evaluate(String input) { - StringTokenizer tokens = new StringTokenizer(input, "+-*/()", true); - Deque operators = new ArrayDeque<>(); - Deque operands = new ArrayDeque<>(); + final StringTokenizer tokens = new StringTokenizer(input, "+-*/()", true); + final Deque operators = new ArrayDeque<>(); + final Deque operands = new ArrayDeque<>(); String lastToken = null; while (tokens.hasMoreTokens()) { String rawToken = tokens.nextToken().trim(); if (rawToken.isEmpty()) continue; - - boolean isUnaryMinus = rawToken.equals("-") && (lastToken == null - || (!lastToken.equals("u") && isOperator(lastToken)) || lastToken.equals("(")); - final String token = isUnaryMinus ? "u" : rawToken; - if (isOperator(token)) { - popOperators(input, operators, operands, - top -> (!top.equals("(") && getPrecedence(token) <= getPrecedence(top))); - operators.push(token); - } else if (token.equals("(")) { - operators.push(token); - } else if (token.equals(")")) { - popOperators(input, operators, operands, top -> !top.equals("(")); - if (!operators.pop().equals("(")) { - throw new IllegalArgumentException("Unbalanced right parentheses in expression: " + input); - } - } else { - try { - operands.push(Double.parseDouble(token)); - } catch (NumberFormatException exception) { - throw new IllegalArgumentException("Could not evaluate expression: " + input); - } + if (isUnaryMinus(rawToken, lastToken)) { + rawToken = "u"; + } else if (isImplicitMultiply(rawToken, lastToken)) { + lastToken = processToken(input, "*", operators, operands); } - lastToken = token; + lastToken = processToken(input, rawToken, operators, operands); } popOperators(input, operators, operands, top -> true); return operands.pop(); } + private static boolean isUnaryMinus(String rawToken, @Nullable String lastToken) { + return rawToken.equals("-") + && (lastToken == null || (isOperator(lastToken) && !lastToken.equals("u")) || lastToken.equals("(")); + } + + private static boolean isImplicitMultiply(String rawToken, @Nullable String lastToken) { + return rawToken.equals("(") + && (lastToken != null && !isOperator(lastToken) && !lastToken.equals("(") && !lastToken.equals(")")); + } + + private static String processToken(String input, String token, Deque operators, Deque operands) { + if (isOperator(token)) { + popOperators(input, operators, operands, + top -> (!top.equals("(") && getPrecedence(token) <= getPrecedence(top))); + operators.push(token); + } else if (token.equals("(")) { + operators.push(token); + } else if (token.equals(")")) { + popOperators(input, operators, operands, top -> !top.equals("(")); + if (!operators.pop().equals("(")) { + throw new IllegalArgumentException("Unbalanced right parentheses in expression: " + input); + } + } else { + try { + operands.push(Double.parseDouble(token)); + } catch (NumberFormatException exception) { + throw new IllegalArgumentException("Could not parse double from:" + token); + } + } + return token; + } + private static void popOperators(String input, Deque operators, Deque operands, Predicate predicate) { try { while (!operators.isEmpty() && predicate.test(operators.peek())) { - String op = operators.pop(); + final String op = operators.pop(); if (op.equals("u")) { operands.push(-operands.pop()); continue; @@ -79,8 +96,8 @@ public class EquationEvaluator { throw new IllegalArgumentException("Unexpected arithmetic operation " + op); } - private static boolean isOperator(String op) { - switch (op) { + private static boolean isOperator(String token) { + switch (token) { case "+": case "-": case "*": diff --git a/src/test/java/com/refinedmods/refinedstorage/util/EquationEvaluatorTest.java b/src/test/java/com/refinedmods/refinedstorage/util/EquationEvaluatorTest.java index 67f9f2295..4e6c6b538 100644 --- a/src/test/java/com/refinedmods/refinedstorage/util/EquationEvaluatorTest.java +++ b/src/test/java/com/refinedmods/refinedstorage/util/EquationEvaluatorTest.java @@ -61,4 +61,17 @@ class EquationEvaluatorTest { assertFuzzyEquals(1, EquationEvaluator.evaluate("1 - 2 + 3 * 4 / 6")); assertFuzzyEquals(4.0 / 3, EquationEvaluator.evaluate("(((1 - 2) + 3) * 4) / 6")); } + + @Test + void testDivideByZero() { + assertFuzzyEquals(Double.POSITIVE_INFINITY, EquationEvaluator.evaluate("1 / 0")); + assertFuzzyEquals(Double.POSITIVE_INFINITY, EquationEvaluator.evaluate("1 / (1 - 1)")); + } + + @Test + void testImplicitMultiply() { + assertFuzzyEquals(-2, EquationEvaluator.evaluate("2(3 + 6) - 5(6 - 2)")); + assertFuzzyEquals(1, EquationEvaluator.evaluate("6 / (2(4 - 1))")); + assertFuzzyEquals(6 * 5 * 4 * 3 * 2, EquationEvaluator.evaluate("6(5(4(3(2(1)))))")); + } }