diff options
Diffstat (limited to 'src/main/java/sevenUnits/utils/ExpressionParser.java')
-rw-r--r-- | src/main/java/sevenUnits/utils/ExpressionParser.java | 156 |
1 files changed, 152 insertions, 4 deletions
diff --git a/src/main/java/sevenUnits/utils/ExpressionParser.java b/src/main/java/sevenUnits/utils/ExpressionParser.java index 941c2a4..a41f37d 100644 --- a/src/main/java/sevenUnits/utils/ExpressionParser.java +++ b/src/main/java/sevenUnits/utils/ExpressionParser.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2024 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 @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.UnaryOperator; @@ -83,6 +84,13 @@ public final class ExpressionParser<T> { private final Map<String, PriorityBinaryOperator<T>> binaryOperators; /** + * A map mapping operator strings to numeric functions. + * + * @since 2024-03-23 + */ + private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; + + /** * Creates the {@code Builder}. * * @param objectObtainer a function that can turn strings into objects of @@ -96,6 +104,7 @@ public final class ExpressionParser<T> { "objectObtainer must not be null."); this.unaryOperators = new HashMap<>(); this.binaryOperators = new HashMap<>(); + this.numericOperators = new HashMap<>(); } /** @@ -131,6 +140,32 @@ 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 + * are applied first + * @return this builder + */ + public Builder<T> addNumericOperator(final String text, final BiFunction<T, UncertainDouble, T> operator, final int priority) { + Objects.requireNonNull(text, "text must not be null."); + Objects.requireNonNull(operator, "operator must not be null."); + + // Unfortunately, I cannot use a lambda because the + // PriorityBinaryOperator requires arguments. + final PriorityBiFunction<T, UncertainDouble, T> priorityOperator = new PriorityBiFunction<>( + priority) { + @Override + public T apply(final T t, final UncertainDouble u) { + return operator.apply(t, u); + } + + }; + this.numericOperators.put(text, priorityOperator); + return this; + } + + /** * Adds a function for spaces. You must use the text of an existing binary * operator. * @@ -189,7 +224,7 @@ public final class ExpressionParser<T> { */ public ExpressionParser<T> build() { return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, - this.binaryOperators, this.spaceFunction); + this.binaryOperators, this.numericOperators, this.spaceFunction); } } @@ -253,6 +288,67 @@ public final class ExpressionParser<T> { return this.priority; } } + + /** + * 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>> { + /** + * The operator's priority. Higher-priority operators are applied before + * lower-priority operators + * + * @since 2019-03-17 + * @since v0.2.0 + */ + private final int priority; + + /** + * Creates the {@code PriorityBinaryOperator}. + * + * @param priority operator's priority + * @since 2019-03-17 + * @since v0.2.0 + */ + 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 PriorityBiFunction<T, U, R> o) { + if (this.priority < o.priority) + return -1; + else if (this.priority > o.priority) + return 1; + else + return 0; + } + + /** + * @return priority + * @since 2019-03-22 + * @since v0.2.0 + */ + public final int getPriority() { + return this.priority; + } + } /** * A unary operator with a priority field that determines which operators @@ -323,7 +419,7 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private static enum TokenType { - OBJECT, UNARY_OPERATOR, BINARY_OPERATOR; + OBJECT, UNARY_OPERATOR, BINARY_OPERATOR, NUMERIC_OPERATOR; } /** @@ -421,6 +517,13 @@ public final class ExpressionParser<T> { * @since v0.2.0 */ private final Map<String, PriorityBinaryOperator<T>> binaryOperators; + + /** + * A map mapping operator strings to numeric functions. + * + * @since 2024-03-23 + */ + private final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators; /** * The operator for space, or null if spaces have no function. @@ -436,6 +539,7 @@ public final class ExpressionParser<T> { * @param objectObtainer function to get objects from strings * @param unaryOperators unary operators available to the parser * @param binaryOperators binary operators available to the parser + * @param numericOperators numeric operators available to the parser * @param spaceOperator operator used by spaces * @since 2019-03-14 * @since v0.2.0 @@ -443,10 +547,12 @@ public final class ExpressionParser<T> { private ExpressionParser(final Function<String, ? extends T> objectObtainer, final Map<String, PriorityUnaryOperator<T>> unaryOperators, final Map<String, PriorityBinaryOperator<T>> binaryOperators, + final Map<String, PriorityBiFunction<T, UncertainDouble, T>> numericOperators, final String spaceOperator) { this.objectObtainer = objectObtainer; this.unaryOperators = unaryOperators; this.binaryOperators = binaryOperators; + this.numericOperators = numericOperators; this.spaceOperator = spaceOperator; } @@ -554,6 +660,7 @@ public final class ExpressionParser<T> { operand + " " + unaryOperator); break; case BINARY_OPERATOR: + case NUMERIC_OPERATOR: if (components.size() < 3) throw new IllegalArgumentException( "Invalid expression \"" + expression + "\""); @@ -628,6 +735,16 @@ public final class ExpressionParser<T> { maxPriorityPosition = i; } break; + case NUMERIC_OPERATOR: + final PriorityBiFunction<T, UncertainDouble, T> numericOperator = this.numericOperators + .get(components.get(i)); + final int numericPriority = numericOperator.getPriority(); + + if (numericPriority > maxPriority) { + maxPriority = numericPriority; + maxPriorityPosition = i; + } + break; default: break; } @@ -653,6 +770,8 @@ public final class ExpressionParser<T> { return TokenType.UNARY_OPERATOR; else if (this.binaryOperators.containsKey(token)) return TokenType.BINARY_OPERATOR; + else if (this.numericOperators.containsKey(token)) + return TokenType.NUMERIC_OPERATOR; else return TokenType.OBJECT; } @@ -684,6 +803,7 @@ public final class ExpressionParser<T> { Objects.requireNonNull(expression, "expression must not be null."); final Deque<T> stack = new ArrayDeque<>(); + final Deque<UncertainDouble> doubleStack = new ArrayDeque<>(); // iterate over every item in the expression, then for (final String item : expression.split(" ")) { @@ -704,10 +824,38 @@ public final class ExpressionParser<T> { stack.push(binaryOperator.apply(o1, o2)); break; + + case NUMERIC_OPERATOR: + if (stack.size() < 1 || doubleStack.size() < 1) + throw new IllegalStateException(String.format( + "Attempted to call binary operator %s with insufficient arguments.", + item)); + + final T ot = stack.pop(); + final UncertainDouble on = doubleStack.pop(); + final BiFunction<T, UncertainDouble, T> op = this.numericOperators.get(item); + stack.push(op.apply(ot, on)); + break; case OBJECT: // just add it to the stack - stack.push(this.objectObtainer.apply(item)); + // these try-catch statements are necessary + // to make the code as generalizable as possible + // also they're required for number formatting code because + // 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) { + try { + doubleStack.push(UncertainDouble.fromString(item)); + } catch (IllegalArgumentException e2) { + try { + doubleStack.push(UncertainDouble.of(Double.parseDouble(item), 0)); + } catch (NumberFormatException e3) { + throw e; + } + } + } break; case UNARY_OPERATOR: |