diff options
Diffstat (limited to 'src/main/java/sevenUnits/utils/ExpressionParser.java')
-rw-r--r-- | src/main/java/sevenUnits/utils/ExpressionParser.java | 217 |
1 files changed, 104 insertions, 113 deletions
diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index e248ff0..03c763c 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019, 2024 Adrien Hopkins + * Copyright (C) 2019, 2021, 2024, 2025 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -31,7 +31,7 @@ import java.util.function.UnaryOperator; /** * An object that can parse expressions with unary or binary operators. - * + * * @author Adrien Hopkins * @param <T> type of object that exists in parsed expressions * @since 2019-03-14 @@ -40,7 +40,7 @@ import java.util.function.UnaryOperator; public final class ExpressionParser<T> { /** * A builder that can create {@code ExpressionParser<T>} instances. - * + * * @author Adrien Hopkins * @param <T> type of object that exists in parsed expressions * @since 2019-03-17 @@ -51,7 +51,7 @@ public final class ExpressionParser<T> { * A function that obtains a parseable object from a string. For example, * an integer {@code ExpressionParser} would use * {@code Integer::parseInt}. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -59,7 +59,7 @@ public final class ExpressionParser<T> { /** * The function of the space as an operator (like 3 x y) - * + * * @since 2019-03-22 * @since v0.2.0 */ @@ -68,7 +68,7 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to operator functions, for unary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -77,7 +77,7 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to operator functions, for binary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -85,14 +85,15 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to numeric functions. - * + * * @since 2024-03-23 + * @since v0.5.0 */ private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; /** * Creates the {@code Builder}. - * + * * @param objectObtainer a function that can turn strings into objects of * the type handled by the parser. * @throws NullPointerException if {@code objectObtainer} is null @@ -109,7 +110,7 @@ public final class ExpressionParser<T> { /** * Adds a binary operator to the builder. - * + * * @param text text used to reference the operator, like '+' * @param operator operator to add * @param priority operator's priority, which determines which operators @@ -142,7 +143,7 @@ public final class ExpressionParser<T> { /** * Adds a two-argument operator where the second operator is a number. * This is used for operations like vector scaling and exponentation. - * + * * @param text text used to reference the operator, like '^' * @param operator operator to add * @param priority operator's priority, which determines which operators @@ -172,7 +173,7 @@ public final class ExpressionParser<T> { /** * Adds a function for spaces. You must use the text of an existing binary * operator. - * + * * @param operator text of operator to use * @return this builder * @since 2019-03-22 @@ -191,7 +192,7 @@ public final class ExpressionParser<T> { /** * Adds a unary operator to the builder. - * + * * @param text text used to reference the operator, like '-' * @param operator operator to add * @param priority operator's priority, which determines which operators @@ -235,18 +236,18 @@ public final class ExpressionParser<T> { /** * A binary operator with a priority field that determines which operators * apply first. - * + * * @author Adrien Hopkins * @param <T> type of operand and result * @since 2019-03-17 * @since v0.2.0 */ - private static abstract class PriorityBinaryOperator<T> - implements BinaryOperator<T>, Comparable<PriorityBinaryOperator<T>> { + private static abstract class PriorityBiFunction<T, U, R> implements + BiFunction<T, U, R>, Comparable<PriorityBiFunction<T, U, R>> { /** * The operator's priority. Higher-priority operators are applied before * lower-priority operators - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -254,33 +255,32 @@ public final class ExpressionParser<T> { /** * Creates the {@code PriorityBinaryOperator}. - * + * * @param priority operator's priority * @since 2019-03-17 * @since v0.2.0 */ - public PriorityBinaryOperator(final int priority) { + public PriorityBiFunction(final int priority) { this.priority = priority; } /** * Compares this object to another by priority. - * + * * <p> * {@inheritDoc} * </p> - * + * * @since 2019-03-17 * @since v0.2.0 */ @Override - public int compareTo(final PriorityBinaryOperator<T> o) { + public int compareTo(final PriorityBiFunction<T, U, R> o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } /** @@ -296,18 +296,18 @@ public final class ExpressionParser<T> { /** * A binary operator with a priority field that determines which operators * apply first. - * + * * @author Adrien Hopkins * @param <T> type of operand and result * @since 2019-03-17 * @since v0.2.0 */ - private static abstract class PriorityBiFunction<T, U, R> implements - BiFunction<T, U, R>, Comparable<PriorityBiFunction<T, U, R>> { + private static abstract class PriorityBinaryOperator<T> + implements BinaryOperator<T>, Comparable<PriorityBinaryOperator<T>> { /** * The operator's priority. Higher-priority operators are applied before * lower-priority operators - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -315,33 +315,32 @@ public final class ExpressionParser<T> { /** * Creates the {@code PriorityBinaryOperator}. - * + * * @param priority operator's priority * @since 2019-03-17 * @since v0.2.0 */ - public PriorityBiFunction(final int priority) { + public PriorityBinaryOperator(final int priority) { this.priority = priority; } /** * Compares this object to another by priority. - * + * * <p> * {@inheritDoc} * </p> - * + * * @since 2019-03-17 * @since v0.2.0 */ @Override - public int compareTo(final PriorityBiFunction<T, U, R> o) { + public int compareTo(final PriorityBinaryOperator<T> o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } /** @@ -357,7 +356,7 @@ public final class ExpressionParser<T> { /** * A unary operator with a priority field that determines which operators * apply first. - * + * * @author Adrien Hopkins * @param <T> type of operand and result * @since 2019-03-17 @@ -368,7 +367,7 @@ public final class ExpressionParser<T> { /** * The operator's priority. Higher-priority operators are applied before * lower-priority operators - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -376,7 +375,7 @@ public final class ExpressionParser<T> { /** * Creates the {@code PriorityUnaryOperator}. - * + * * @param priority operator's priority * @since 2019-03-17 * @since v0.2.0 @@ -387,11 +386,11 @@ public final class ExpressionParser<T> { /** * Compares this object to another by priority. - * + * * <p> * {@inheritDoc} * </p> - * + * * @since 2019-03-17 * @since v0.2.0 */ @@ -399,10 +398,9 @@ public final class ExpressionParser<T> { public int compareTo(final PriorityUnaryOperator<T> o) { if (this.priority < o.priority) return -1; - else if (this.priority > o.priority) + if (this.priority > o.priority) return 1; - else - return 0; + return 0; } /** @@ -417,18 +415,18 @@ public final class ExpressionParser<T> { /** * The types of tokens that are available. - * + * * @author Adrien Hopkins * @since 2019-03-14 * @since v0.2.0 */ - private static enum TokenType { + private enum TokenType { OBJECT, UNARY_OPERATOR, BINARY_OPERATOR, NUMERIC_OPERATOR; } /** * The opening bracket. - * + * * @since 2019-03-22 * @since v0.2.0 */ @@ -436,7 +434,7 @@ public final class ExpressionParser<T> { /** * The closing bracket. - * + * * @since 2019-03-22 * @since v0.2.0 */ @@ -444,7 +442,7 @@ public final class ExpressionParser<T> { /** * Finds the other bracket in a pair of brackets, given the position of one. - * + * * @param string string that contains brackets * @param bracketPosition position of first bracket * @return position of matching bracket @@ -456,7 +454,7 @@ public final class ExpressionParser<T> { final int bracketPosition) { Objects.requireNonNull(string, "string must not be null."); - final char openingBracket = string.charAt(bracketPosition); + final var openingBracket = string.charAt(bracketPosition); // figure out what closing bracket to look for final char closingBracket; @@ -477,12 +475,12 @@ public final class ExpressionParser<T> { // level of brackets. every opening bracket increments this; every closing // bracket decrements it - int bracketLevel = 0; + var bracketLevel = 0; // iterate over the string to find the closing bracket - for (int currentPosition = bracketPosition; currentPosition < string + for (var currentPosition = bracketPosition; currentPosition < string .length(); currentPosition++) { - final char currentCharacter = string.charAt(currentPosition); + final var currentCharacter = string.charAt(currentPosition); if (currentCharacter == openingBracket) { bracketLevel++; @@ -499,7 +497,7 @@ public final class ExpressionParser<T> { /** * A function that obtains a parseable object from a string. For example, an * integer {@code ExpressionParser} would use {@code Integer::parseInt}. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -507,7 +505,7 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to operator functions, for unary operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -516,7 +514,7 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to operator functions, for binary * operators. - * + * * @since 2019-03-14 * @since v0.2.0 */ @@ -524,14 +522,15 @@ public final class ExpressionParser<T> { /** * A map mapping operator strings to numeric functions. - * + * * @since 2024-03-23 + * @since v0.5.0 */ private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; /** * The operator for space, or null if spaces have no function. - * + * * @since 2019-03-22 * @since v0.2.0 */ @@ -539,7 +538,7 @@ public final class ExpressionParser<T> { /** * Creates the {@code ExpressionParser}. - * + * * @param objectObtainer function to get objects from strings * @param unaryOperators unary operators available to the parser * @param binaryOperators binary operators available to the parser @@ -568,7 +567,7 @@ public final class ExpressionParser<T> { * {@code 2 * (3 + 4)}<br> * becomes<br> * {@code 2 3 4 + *}. - * + * * @param expression expression * @return expression in RPN * @throws IllegalArgumentException if expression is invalid (e.g. @@ -582,13 +581,13 @@ public final class ExpressionParser<T> { final List<String> components = new ArrayList<>(); // the part of the expression remaining to parse - String partialExpression = expression; + var partialExpression = expression; // find and deal with brackets while (partialExpression.indexOf(OPENING_BRACKET) != -1) { - final int openingBracketPosition = partialExpression + final var openingBracketPosition = partialExpression .indexOf(OPENING_BRACKET); - final int closingBracketPosition = findBracketPair(partialExpression, + final var closingBracketPosition = findBracketPair(partialExpression, openingBracketPosition); // check for function @@ -596,7 +595,7 @@ public final class ExpressionParser<T> { && partialExpression.charAt(openingBracketPosition - 1) != ' ') { // function like sin(2) or tempF(32) // find the position of the last space - int spacePosition = openingBracketPosition; + var spacePosition = openingBracketPosition; while (spacePosition >= 0 && partialExpression.charAt(spacePosition) != ' ') { spacePosition--; @@ -607,8 +606,6 @@ public final class ExpressionParser<T> { .substring(0, spacePosition + 1).split(" "))); components.add(partialExpression.substring(spacePosition + 1, closingBracketPosition + 1)); - partialExpression = partialExpression - .substring(closingBracketPosition + 1); } else { // normal brackets like (1 + 2) * (3 / 5) components.addAll(Arrays.asList(partialExpression @@ -616,9 +613,9 @@ public final class ExpressionParser<T> { components.add(this.convertExpressionToReversePolish( partialExpression.substring(openingBracketPosition + 1, closingBracketPosition))); - partialExpression = partialExpression - .substring(closingBracketPosition + 1); } + partialExpression = partialExpression + .substring(closingBracketPosition + 1); } // add everything else @@ -631,7 +628,7 @@ public final class ExpressionParser<T> { // deal with space multiplication (x y) if (this.spaceOperator != null) { - for (int i = 0; i < components.size() - 1; i++) { + for (var i = 0; i < components.size() - 1; i++) { if (this.getTokenType(components.get(i)) == TokenType.OBJECT && this .getTokenType(components.get(i + 1)) == TokenType.OBJECT) { components.add(++i, this.spaceOperator); @@ -641,7 +638,7 @@ public final class ExpressionParser<T> { // turn the expression into reverse Polish while (true) { - final int highestPriorityOperatorPosition = this + final var highestPriorityOperatorPosition = this .findHighestPriorityOperatorPosition(components); if (highestPriorityOperatorPosition == -1) { break; @@ -656,9 +653,9 @@ public final class ExpressionParser<T> { if (components.size() < 2) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); - final String unaryOperator = components + final var unaryOperator = components .remove(highestPriorityOperatorPosition); - final String operand = components + final var operand = components .remove(highestPriorityOperatorPosition); components.add(highestPriorityOperatorPosition, operand + " " + unaryOperator); @@ -668,14 +665,14 @@ public final class ExpressionParser<T> { if (components.size() < 3) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); - final String binaryOperator = components + final var binaryOperator = components .remove(highestPriorityOperatorPosition); - final String operand1 = components + final var operand1 = components .remove(highestPriorityOperatorPosition - 1); - final String operand2 = components + final var operand2 = components .remove(highestPriorityOperatorPosition - 1); components.add(highestPriorityOperatorPosition - 1, - operand2 + " " + operand1 + " " + binaryOperator); + operand1 + " " + operand2 + " " + binaryOperator); break; default: throw new AssertionError("Expected operator, found non-operator."); @@ -684,20 +681,15 @@ public final class ExpressionParser<T> { // join all of the components together, then ensure there is only one // space in a row - String expressionRPN = String.join(" ", components).replaceAll(" +", " "); - - while (expressionRPN.charAt(0) == ' ') { - expressionRPN = expressionRPN.substring(1); - } - while (expressionRPN.charAt(expressionRPN.length() - 1) == ' ') { - expressionRPN = expressionRPN.substring(0, expressionRPN.length() - 1); - } - return expressionRPN; + if (components.size() != 1) + throw new IllegalArgumentException( + "Invalid expression \"" + expression + "\"."); + return components.get(0).replaceAll(" +", " ").trim(); } /** * Finds the position of the highest-priority operator in a list - * + * * @param components components to test * @param blacklist positions of operators that should be ignored * @return position of highest priority, or -1 if the list contains no @@ -710,19 +702,19 @@ public final class ExpressionParser<T> { final List<String> components) { Objects.requireNonNull(components, "components must not be null."); // find highest priority - int maxPriority = Integer.MIN_VALUE; - int maxPriorityPosition = -1; + var maxPriority = Integer.MIN_VALUE; + var maxPriorityPosition = -1; // go over components one by one // if it is an operator, test its priority to see if it's max // if it is, update maxPriority and maxPriorityPosition - for (int i = 0; i < components.size(); i++) { + for (var i = 0; i < components.size(); i++) { switch (this.getTokenType(components.get(i))) { case UNARY_OPERATOR: - final PriorityUnaryOperator<T> unaryOperator = this.unaryOperators + final var unaryOperator = this.unaryOperators .get(components.get(i)); - final int unaryPriority = unaryOperator.getPriority(); + final var unaryPriority = unaryOperator.getPriority(); if (unaryPriority > maxPriority) { maxPriority = unaryPriority; @@ -730,9 +722,9 @@ public final class ExpressionParser<T> { } break; case BINARY_OPERATOR: - final PriorityBinaryOperator<T> binaryOperator = this.binaryOperators + final var binaryOperator = this.binaryOperators .get(components.get(i)); - final int binaryPriority = binaryOperator.getPriority(); + final var binaryPriority = binaryOperator.getPriority(); if (binaryPriority > maxPriority) { maxPriority = binaryPriority; @@ -740,9 +732,9 @@ public final class ExpressionParser<T> { } break; case NUMERIC_OPERATOR: - final PriorityBiFunction<T, UncertainDouble, T> numericOperator = this.numericOperators + final var numericOperator = this.numericOperators .get(components.get(i)); - final int numericPriority = numericOperator.getPriority(); + final var numericPriority = numericOperator.getPriority(); if (numericPriority > maxPriority) { maxPriority = numericPriority; @@ -760,7 +752,7 @@ public final class ExpressionParser<T> { /** * Determines whether an inputted string is an object or an operator - * + * * @param token string to input * @return type of token it is * @throws NullPointerException if {@code expression} is null @@ -772,17 +764,16 @@ public final class ExpressionParser<T> { if (this.unaryOperators.containsKey(token)) return TokenType.UNARY_OPERATOR; - else if (this.binaryOperators.containsKey(token)) + if (this.binaryOperators.containsKey(token)) return TokenType.BINARY_OPERATOR; - else if (this.numericOperators.containsKey(token)) + if (this.numericOperators.containsKey(token)) return TokenType.NUMERIC_OPERATOR; - else - return TokenType.OBJECT; + return TokenType.OBJECT; } /** * Parses an expression. - * + * * @param expression expression to parse * @return result * @throws NullPointerException if {@code expression} is null @@ -796,7 +787,7 @@ public final class ExpressionParser<T> { /** * Parses an expression expressed in reverse Polish notation. - * + * * @param expression expression to parse * @return result * @throws NullPointerException if {@code expression} is null @@ -821,12 +812,12 @@ public final class ExpressionParser<T> { item, stack.size())); // get two arguments and operator, then apply! - final T o1 = stack.pop(); - final T o2 = stack.pop(); + final var o1 = stack.pop(); + final var o2 = stack.pop(); final BinaryOperator<T> binaryOperator = this.binaryOperators .get(item); - stack.push(binaryOperator.apply(o1, o2)); + stack.push(binaryOperator.apply(o2, o1)); break; case NUMERIC_OPERATOR: @@ -835,8 +826,8 @@ public final class ExpressionParser<T> { "Attempted to call binary operator %s with insufficient arguments.", item)); - final T ot = stack.pop(); - final UncertainDouble on = doubleStack.pop(); + final var ot = stack.pop(); + final var on = doubleStack.pop(); final BiFunction<T, UncertainDouble, T> op = this.numericOperators .get(item); stack.push(op.apply(ot, on)); @@ -850,14 +841,14 @@ public final class ExpressionParser<T> { // that's the only way to tell if an expression is a number or not. try { stack.push(this.objectObtainer.apply(item)); - } catch (Exception e) { + } catch (final Exception e) { try { doubleStack.push(UncertainDouble.fromString(item)); - } catch (IllegalArgumentException e2) { + } catch (final IllegalArgumentException e2) { try { doubleStack.push( UncertainDouble.of(Double.parseDouble(item), 0)); - } catch (NumberFormatException e3) { + } catch (final NumberFormatException e3) { throw e; } } @@ -871,7 +862,7 @@ public final class ExpressionParser<T> { item, stack.size())); // get one argument and operator, then apply! - final T o = stack.pop(); + final var o = stack.pop(); final UnaryOperator<T> unaryOperator = this.unaryOperators .get(item); @@ -889,7 +880,7 @@ public final class ExpressionParser<T> { if (stack.size() > 1) throw new IllegalStateException( "Computation ended up with more than one answer."); - else if (stack.size() == 0) + if (stack.size() == 0) throw new IllegalStateException( "Computation ended up without an answer."); return stack.pop(); |