diff --git a/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java b/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java index c822410..3014542 100644 --- a/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java +++ b/src/main/java/world/bentobox/level/calculators/EquationEvaluator.java @@ -3,119 +3,119 @@ package world.bentobox.level.calculators; import java.text.ParseException; /** - * @author tastybento + * Utility class to evaluate equations */ public class EquationEvaluator { private static class Parser { - private final String input; - private int pos = -1; - private int currentChar; + private final String input; + private int pos = -1; + private int currentChar; - public Parser(String input) { - this.input = input; - moveToNextChar(); - } + @SuppressWarnings("unused") + private Parser() { + throw new IllegalStateException("Utility class"); + } - private void moveToNextChar() { - currentChar = (++pos < input.length()) ? input.charAt(pos) : -1; - } + public Parser(String input) { + this.input = input; + moveToNextChar(); + } - private boolean tryToEat(int charToEat) { - while (currentChar == ' ') - moveToNextChar(); - if (currentChar == charToEat) { - moveToNextChar(); - return true; - } - return false; - } + private void moveToNextChar() { + currentChar = (++pos < input.length()) ? input.charAt(pos) : -1; + } - public double evaluate() throws ParseException { - double result = parseExpression(); - if (pos < input.length()) { - throw new ParseException("Unexpected character: " + (char) currentChar, pos); - } - return result; - } + private boolean tryToEat(int charToEat) { + while (currentChar == ' ') { + moveToNextChar(); + } + if (currentChar == charToEat) { + moveToNextChar(); + return true; + } + return false; + } - private double parseExpression() throws ParseException { - double result = parseTerm(); - while (true) { - if (tryToEat('+')) - result += parseTerm(); - else if (tryToEat('-')) - result -= parseTerm(); - else - return result; - } - } + public double evaluate() throws ParseException { + double result = parseExpression(); + if (pos < input.length()) { + throw new ParseException("Unexpected character: " + (char) currentChar, pos); + } + return result; + } - private double parseFactor() throws ParseException { - if (tryToEat('+')) - return parseFactor(); // unary plus - if (tryToEat('-')) - return -parseFactor(); // unary minus + private double parseExpression() throws ParseException { + double result = parseTerm(); + while (true) { + if (tryToEat('+')) { + result += parseTerm(); + } else if (tryToEat('-')) { + result -= parseTerm(); + } else { + return result; + } + } + } - double x; - int startPos = this.pos; - if (tryToEat('(')) { // parentheses - x = parseExpression(); - tryToEat(')'); - } else if ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') { // numbers - while ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') - moveToNextChar(); - x = Double.parseDouble(input.substring(startPos, this.pos)); - } else if (currentChar >= 'a' && currentChar <= 'z') { // functions - while (currentChar >= 'a' && currentChar <= 'z') - moveToNextChar(); - String func = input.substring(startPos, this.pos); - x = parseFactor(); - switch (func) { - case "sqrt": - x = Math.sqrt(x); - break; - case "sin": - x = Math.sin(Math.toRadians(x)); - break; - case "cos": - x = Math.cos(Math.toRadians(x)); - break; - case "tan": - x = Math.tan(Math.toRadians(x)); - break; - case "log": - x = Math.log(x); - break; - default: - throw new ParseException("Unknown function: " + func, startPos); - } - } else { - throw new ParseException("Unexpected: " + (char) currentChar, startPos); - } + private double parseFactor() throws ParseException { + if (tryToEat('+')) { + return parseFactor(); // unary plus + } + if (tryToEat('-')) { + return -parseFactor(); // unary minus + } + double x; + int startPos = this.pos; + if (tryToEat('(')) { // parentheses + x = parseExpression(); + tryToEat(')'); + } else if ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') { // numbers + while ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') { + moveToNextChar(); + } + x = Double.parseDouble(input.substring(startPos, this.pos)); + } else if (currentChar >= 'a' && currentChar <= 'z') { // functions + while (currentChar >= 'a' && currentChar <= 'z') { + moveToNextChar(); + } + String func = input.substring(startPos, this.pos); + x = parseFactor(); + x = switch (func) { + case "sqrt" -> Math.sqrt(x); + case "sin" -> Math.sin(Math.toRadians(x)); + case "cos" -> Math.cos(Math.toRadians(x)); + case "tan" -> Math.tan(Math.toRadians(x)); + case "log" -> Math.log(x); + default -> throw new ParseException("Unknown function: " + func, startPos); + }; + } else { + throw new ParseException("Unexpected: " + (char) currentChar, startPos); + } - if (tryToEat('^')) - x = Math.pow(x, parseFactor()); // exponentiation + if (tryToEat('^')) { + x = Math.pow(x, parseFactor()); // exponentiation + } - return x; - } + return x; + } - private double parseTerm() throws ParseException { - double x = parseFactor(); - for (;;) { - if (tryToEat('*')) - x *= parseFactor(); // multiplication - else if (tryToEat('/')) - x /= parseFactor(); // division - else - return x; - } - } + private double parseTerm() throws ParseException { + double x = parseFactor(); + for (;;) { + if (tryToEat('*')) + x *= parseFactor(); // multiplication + else if (tryToEat('/')) + x /= parseFactor(); // division + else + return x; + } + } } public static double eval(final String equation) throws ParseException { - return new Parser(equation).evaluate(); + return new Parser(equation).evaluate(); } } diff --git a/src/test/java/world/bentobox/level/calculators/EquationEvaluatorTest.java b/src/test/java/world/bentobox/level/calculators/EquationEvaluatorTest.java new file mode 100644 index 0000000..9bcb5e2 --- /dev/null +++ b/src/test/java/world/bentobox/level/calculators/EquationEvaluatorTest.java @@ -0,0 +1,37 @@ +package world.bentobox.level.calculators; + +import static org.junit.Assert.assertEquals; + +import java.text.ParseException; + +import org.junit.Test; + +/** + * Test the equation evaluation + */ +public class EquationEvaluatorTest { + + /** + * Test method for {@link world.bentobox.level.calculators.EquationEvaluator#eval(java.lang.String)}. + * @throws ParseException + */ + @Test + public void testEval() throws ParseException { + assertEquals(4D, EquationEvaluator.eval("2+2"), 0D); + assertEquals(0D, EquationEvaluator.eval("2-2"), 0D); + assertEquals(1D, EquationEvaluator.eval("2/2"), 0D); + assertEquals(4D, EquationEvaluator.eval("2*2"), 0D); + assertEquals(8D, EquationEvaluator.eval("2+2+2+2"), 0D); + assertEquals(5D, EquationEvaluator.eval("2.5+2.5"), 0D); + assertEquals(1.414, EquationEvaluator.eval("sqrt(2)"), 0.001D); + assertEquals(3.414, EquationEvaluator.eval("2 + sqrt(2)"), 0.001D); + assertEquals(0D, EquationEvaluator.eval("sin(0)"), 0.1D); + assertEquals(1D, EquationEvaluator.eval("cos(0)"), 0.1D); + assertEquals(0D, EquationEvaluator.eval("tan(0)"), 0.1D); + assertEquals(0D, EquationEvaluator.eval("log(1)"), 0.1D); + assertEquals(27D, EquationEvaluator.eval("3^3"), 0.D); + assertEquals(84.70332D, EquationEvaluator.eval("3^3 + 2 + 2.65 * (3 / 4) - sin(45) * log(10) + 55.344"), + 0.0001D); + + } +}