summaryrefslogtreecommitdiff
path: root/src/main/java/sevenUnits/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/sevenUnits/utils')
-rw-r--r--src/main/java/sevenUnits/utils/ExpressionParser.java156
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: