summaryrefslogtreecommitdiff
path: root/base/src/main/java/bjc/utils/patterns/ComplexPattern.java
diff options
context:
space:
mode:
authorBen Culkin <scorpress@gmail.com>2022-09-27 19:21:16 -0400
committerBen Culkin <scorpress@gmail.com>2022-09-27 19:21:16 -0400
commit4a96d9cad446ea405b51dfeebb01a1b6d7f6fb2b (patch)
tree9aac0b901b53e99fbd06f59461519367bf4ca8ec /base/src/main/java/bjc/utils/patterns/ComplexPattern.java
parenta3d2728f84375566da3da560b3faad018d34005d (diff)
Add some interesting new things
Adds a number of things based off of some of the notes I've made over time, plus a few papers I've read. More details to come later, whenever I decide to actually get serious about documentation and examples and the like
Diffstat (limited to 'base/src/main/java/bjc/utils/patterns/ComplexPattern.java')
-rw-r--r--base/src/main/java/bjc/utils/patterns/ComplexPattern.java347
1 files changed, 240 insertions, 107 deletions
diff --git a/base/src/main/java/bjc/utils/patterns/ComplexPattern.java b/base/src/main/java/bjc/utils/patterns/ComplexPattern.java
index 2d9fc71..69ee67c 100644
--- a/base/src/main/java/bjc/utils/patterns/ComplexPattern.java
+++ b/base/src/main/java/bjc/utils/patterns/ComplexPattern.java
@@ -1,18 +1,21 @@
package bjc.utils.patterns;
+import java.util.Optional;
import java.util.function.*;
import java.util.regex.*;
import bjc.data.*;
+import bjc.functypes.ID;
+import bjc.functypes.Unit;
/**
* A pattern that can be matched against.
*
* @author Ben Culkin
*
- * @param <InputType> The type of object being matched against.
+ * @param <InputType> The type of object being matched against.
* @param <ReturnType> The type returned by the pattern.
- * @param <PredType> The state type returned by the predicate.
+ * @param <PredType> The state type returned by the predicate.
*/
public interface ComplexPattern<ReturnType, PredType, InputType> {
/**
@@ -20,11 +23,11 @@ public interface ComplexPattern<ReturnType, PredType, InputType> {
*
* @param input The object to check against this pattern.
*
- * @return Whether or not this pattern is matched, as well as a state value
- * that will get passed to the pattern if it did match.
+ * @return Whether or not this pattern is matched, as well as a state value that
+ * will get passed to the pattern if it did match.
*/
Pair<Boolean, PredType> matches(InputType input);
-
+
/**
* Apply this pattern, once it has matched.
*
@@ -34,9 +37,9 @@ public interface ComplexPattern<ReturnType, PredType, InputType> {
* @return The result of applying this pattern.
*/
ReturnType apply(InputType input, PredType state);
-
+
/* Pattern producing functions */
-
+
/**
* Create a pattern composed from a predicate &amp; a function.
*
@@ -44,153 +47,125 @@ public interface ComplexPattern<ReturnType, PredType, InputType> {
* @param <PreType> The type used as intermediate state.
* @param <InpType> The type initially matched against.
*
- * @param matcher The predicate that says what this pattern matches.
- * @param accepter The action that happens when this pattern matches.
+ * @param matcher The predicate that says what this pattern matches.
+ * @param accepter The action that happens when this pattern matches.
*
* @return A pattern composed from the passed in functions.
*/
static <RetType, PreType, InpType> ComplexPattern<RetType, PreType, InpType> from(
- Function<InpType, Pair<Boolean, PreType>> matcher,
- BiFunction<InpType, PreType, RetType> accepter)
- {
+ Function<InpType, Pair<Boolean, PreType>> matcher, BiFunction<InpType, PreType, RetType> accepter) {
return new FunctionalPattern<>(matcher, accepter);
}
-
+
/**
- * Create a pattern which checks if an object is of a given type (or a subtype of it).
+ * Create a pattern which checks if an object is of a given type (or a subtype
+ * of it).
*
* @param <ClassType> The type to check if the object is an instance of.
- * @param <RetType> The type returned by the action.
- * @param <InpType> The type of the thing to match.
+ * @param <RetType> The type returned by the action.
+ * @param <InpType> The type of the thing to match.
*
- * @param clasz The Class instance for the type you want to check.
- * @param action The action to execute if the pattern does match.
+ * @param clasz The Class instance for the type you want to check.
+ * @param action The action to execute if the pattern does match.
*
* @return A pattern which follows the specified condition.
*/
@SuppressWarnings("unchecked")
- static <ClassType, RetType, InpType> ComplexPattern<RetType, ?, InpType> ofClass(
- Class<ClassType> clasz,
- Function<ClassType, RetType> action)
- {
- return from(
- (input) -> Pair.pair(clasz.isInstance(input), null),
- (input, ignored) -> action.apply((ClassType)input)
- );
- }
-
+ static <ClassType, RetType, InpType> ComplexPattern<RetType, Unit, InpType> ofClass(Class<ClassType> clasz,
+ Function<ClassType, RetType> action) {
+ return from((input) -> Pair.pair(clasz.isInstance(input), null),
+ (input, ignored) -> action.apply((ClassType) input));
+ }
+
/**
* Creates a pattern which matches a given object.
*
* @param <RetType> The type returned when the pattern matches.
* @param <InpType> The type of the thing to match.
*
- * @param obj The object being tested for equality.
- * @param action The action to execute when the object matches.
+ * @param obj The object being tested for equality.
+ * @param action The action to execute when the object matches.
*
* @return A pattern which tests against the equality of an object.
*/
- static <RetType, InpType> ComplexPattern<RetType, ?, InpType> matchesObject(
- InpType obj,
- Function<InpType, RetType> action
- )
- {
- return from(
- (input) -> Pair.pair(obj.equals(input), null),
- (input, ignored) -> action.apply(input)
- );
- }
-
+ static <RetType, InpType> ComplexPattern<RetType, Unit, InpType> matchesObject(InpType obj,
+ Function<InpType, RetType> action) {
+ return from((input) -> Pair.pair(obj.equals(input), null), (input, ignored) -> action.apply(input));
+ }
+
/**
* Tests if the toString rendition of an object matches a given condition.
*
* @param <RetType> The type returned by the pattern.
* @param <InpType> The type of the thing to match.
*
- * @param pattern The string to check against.
- * @param action The action to check when the toString of the object matches
- * the provided string. This is passed both the object, and its
- * string form (in the event that you don't want to call toString
- * multiple times, for whatever reason)
- *
- * @return A pattern which tests against the toString representation of an object.
+ * @param pattern The string to check against.
+ * @param action The action to check when the toString of the object matches
+ * the provided string. This is passed both the object, and its
+ * string form (in the event that you don't want to call
+ * toString multiple times, for whatever reason)
+ *
+ * @return A pattern which tests against the toString representation of an
+ * object.
*/
- static <RetType, InpType> ComplexPattern<RetType, ?, InpType> equalsString(
- String pattern,
- BiFunction<InpType, String, RetType> action
- )
- {
+ static <RetType, InpType> ComplexPattern<RetType, String, InpType> equalsString(String pattern,
+ BiFunction<InpType, String, RetType> action) {
Function<InpType, Pair<Boolean, String>> matcher = (input) -> {
String objString = input.toString();
-
+
return Pair.pair(pattern.equals(objString), objString);
};
-
- return from(
- matcher,
- (input, objString) -> action.apply(input, objString)
- );
+
+ return from(matcher, (input, objString) -> action.apply(input, objString));
}
-
+
/**
* Check if the toString of a given object matches a regex.
*
* @param <RetType> The type returned by the pattern.
- * @param <InpType> The type of object to match against.
+ * @param <InpType> The type of object to match against.
*
- * @param regex The regex to match against.
- * @param cond The predicate to use to determine if the regex matched.
- * @param action The action to call when the regex matched.
+ * @param regex The regex to match against.
+ * @param cond The predicate to use to determine if the regex matched.
+ * @param action The action to call when the regex matched.
*
* @return A pattern which does the regex matching.
*/
- static <RetType, InpType> ComplexPattern<RetType, Matcher, InpType> matchesRegex(
- String regex,
- Predicate<Matcher> cond,
- BiFunction<InpType, Matcher, RetType> action
- )
- {
+ static <RetType, InpType> ComplexPattern<RetType, Matcher, InpType> matchesRegex(String regex,
+ Predicate<Matcher> cond, BiFunction<InpType, Matcher, RetType> action) {
java.util.regex.Pattern regexPat = java.util.regex.Pattern.compile(regex);
Function<InpType, Pair<Boolean, Matcher>> matcher = (input) -> {
String inpString = input.toString();
-
+
Matcher mat = regexPat.matcher(inpString);
-
- if (cond.test(mat)) return Pair.pair(true, mat);
- else return Pair.pair(false, null);
+
+ if (cond.test(mat))
+ return Pair.pair(true, mat);
+ return Pair.pair(false, null);
};
-
- return from(
- matcher,
- (input, res) -> action.apply(input, res)
- );
+
+ return from(matcher, (input, res) -> action.apply(input, res));
}
-
+
// @TODO Nov 21, 2020 Ben Culkin :MorePatterns
// Try and write something to iterate over Iterator in a type-safe manner
// Also, something for doing a sub-pattern match
-
+
/**
* Create a pattern which will always execute.
*
* @param <RetType> The type returned.
* @param <InpType> The type being matched against.
*
- * @param action The action to execute.
+ * @param action The action to execute.
*
* @return A pattern which will be executed.
*/
- static <RetType, InpType> ComplexPattern<RetType, ?, InpType> otherwise(
- Function<InpType, RetType> action
- )
- {
- return from(
- (input) -> Pair.pair(true, null),
- (input, ignored) -> action.apply(input)
- );
- }
-
+ static <RetType, InpType> ComplexPattern<RetType, Unit, InpType> otherwise(Function<InpType, RetType> action) {
+ return from((input) -> Pair.pair(true, null), (input, ignored) -> action.apply(input));
+ }
+
/**
* Create a pattern which checks if the string form of a given object starts
* with a specific string.
@@ -198,26 +173,184 @@ public interface ComplexPattern<ReturnType, PredType, InputType> {
* @param <RetType> The type returned by the matcher.
* @param <InpType> The type being matched against.
*
- * @param pattern The string to check against.
- * @param action The action to execute.
+ * @param pattern The string to check against.
+ * @param action The action to execute.
*
* @return A pattern which functions as described.
*/
- static <RetType, InpType> ComplexPattern<RetType, String, InpType> startsWith(
- String pattern,
- Function<String, RetType> action)
- {
+ static <RetType, InpType> ComplexPattern<RetType, String, InpType> startsWith(String pattern,
+ Function<String, RetType> action) {
return from((input) -> {
String objString = input.toString();
-
- if (objString.startsWith(pattern)) {
- return Pair.pair(
- true,
- objString.substring(
- pattern.length()));
- } else {
+
+ if (objString.startsWith(pattern))
+ return Pair.pair(true, objString.substring(pattern.length()));
+ return Pair.pair(false, null);
+ }, (input, state) -> action.apply(state));
+ }
+
+ // TODO: See about generalizing these to be able to take different return types
+ /**
+ * Create a pattern which matches if any of its two components match.
+ *
+ * @param <RetType> The type each pattern returns
+ * @param <InpType> The input for each pattern.
+ * @param <LeftState> The first state type.
+ * @param <RightState> The second state type.
+ *
+ * @param left The first pattern.
+ * @param right The second pattern.
+ *
+ * @return A pattern which matches if either of its components do.
+ */
+ static <RetType, InpType, LeftState, RightState> ComplexPattern<RetType, Either<LeftState, RightState>, InpType> or(
+ ComplexPattern<RetType, LeftState, InpType> left, ComplexPattern<RetType, RightState, InpType> right) {
+ // It would be convenient if we could just omit the two state types.
+ // However, java isn't smart enough to infer the right types without the help
+ Function<InpType, Pair<Boolean, Either<LeftState, RightState>>> matcher = (inp) -> {
+ Pair<Boolean, LeftState> leftRes = left.matches(inp);
+ if (leftRes.getLeft()) {
+ return Pair.pair(true, Either.left(leftRes.getRight()));
+ }
+
+ Pair<Boolean, RightState> rightRes = right.matches(inp);
+ if (rightRes.getLeft()) {
+ return Pair.pair(true, Either.right(rightRes.getRight()));
+ }
+ return Pair.pair(false, null);
+ };
+ return from(matcher, (input, state) -> state.isLeft() ? left.apply(input, state.forceLeft())
+ : right.apply(input, state.forceRight()));
+ }
+
+ /**
+ * Create a pattern which matches if both component patterns do.
+ *
+ * @param <RetType> The type returned by the patterns
+ * @param <InpType> The input for the patterns
+ * @param <LeftState> The state for the right pattern
+ * @param <RightState> The state for the left pattern
+ *
+ * @param left The left pattern
+ * @param right The right pattern
+ *
+ * @return A pattern which matches if both of the given patterns do.
+ */
+ static <RetType, InpType, LeftState,
+ RightState> ComplexPattern<Pair<RetType, RetType>, Pair<LeftState, RightState>, InpType> and(
+ ComplexPattern<RetType, LeftState, InpType> left,
+ ComplexPattern<RetType, RightState, InpType> right) {
+ Function<InpType, Pair<Boolean, Pair<LeftState, RightState>>> matcher = (inp) -> {
+ Pair<Boolean, LeftState> leftRes = left.matches(inp);
+ if (!leftRes.getLeft())
+ return Pair.pair(false, null);
+ Pair<Boolean, RightState> rightRes = right.matches(inp);
+ if (!rightRes.getLeft())
+ return Pair.pair(false, null);
+ return Pair.pair(true, Pair.pair(leftRes.getRight(), rightRes.getRight()));
+ };
+
+ return from(matcher, (input, state) -> {
+ return Pair.pair(left.apply(input, state.getLeft()), right.apply(input, state.getRight()));
+ });
+ }
+
+ static <RetType, InpType, Shared, State1,
+ State2> ComplexPattern<Either<Shared, RetType>, Either<Shared, Pair<Shared, State2>>, InpType> then(
+ ComplexPattern<Shared, State1, InpType> first, ComplexPattern<RetType, State2, Shared> second) {
+ Function<InpType, Pair<Boolean, Either<Shared, Pair<Shared, State2>>>> matcher = (inp) -> {
+ Pair<Boolean, State1> firstRes = first.matches(inp);
+ if (!firstRes.getLeft())
+ return Pair.pair(false, null);
+ Shared shared = first.apply(inp, firstRes.getRight());
+ Pair<Boolean, State2> secondRes = second.matches(shared);
+ if (!secondRes.getLeft())
+ return Pair.pair(true, Either.left(shared));
+ return Pair.pair(true, Either.right(Pair.pair(shared, secondRes.getRight())));
+ };
+
+ return from(matcher, (input, state) -> {
+ if (state.isLeft())
+ return state.newRight();
+ Pair<Shared, State2> right = state.forceRight();
+ return Either.right(second.apply(right.getLeft(), right.getRight()));
+ });
+ }
+
+ static <RetType, InpType, Shared, Other, State1,
+ State2> ComplexPattern<Either<Either<Shared, RetType>, Other>,
+ Either<Either<Shared, Pair<Shared, State2>>, Other>, InpType> maybeThen(
+ ComplexPattern<Either<Shared, Other>, State1, InpType> first,
+ ComplexPattern<RetType, State2, Shared> second) {
+ Function<InpType, Pair<Boolean, Either<Either<Shared, Pair<Shared, State2>>, Other>>> matcher = (inp) -> {
+ Pair<Boolean, State1> firstRes = first.matches(inp);
+ if (!firstRes.getLeft())
return Pair.pair(false, null);
+ Either<Shared, Other> maybeShared = first.apply(inp, firstRes.getRight());
+ if (!maybeShared.isLeft())
+ return Pair.pair(true, maybeShared.newLeft());
+ Shared shared = maybeShared.forceLeft();
+ Pair<Boolean, State2> secondRes = second.matches(shared);
+ if (!secondRes.getLeft())
+ return Pair.pair(true, Either.left(Either.left(shared)));
+ return Pair.pair(true, Either.left(Either.right(Pair.pair(shared, secondRes.getRight()))));
+ };
+
+ // Can't inline matcher, that breaks type-inference
+ return from(matcher, (input, state) -> {
+ if (!state.isLeft()) {
+ return state.newLeft();
+ }
+ Either<Shared, Pair<Shared, State2>> left = state.forceLeft();
+ if (left.isLeft()) {
+ return Either.left(left.newRight());
}
- }, (ignored, input) -> action.apply(input));
+ Pair<Shared, State2> right = left.forceRight();
+ return Either.left(Either.right(second.apply(right.getLeft(), right.getRight())));
+ });
+ }
+
+ static <RetType, Shared1, Shared2, State, Input> ComplexPattern<RetType, State, Input> collapse(
+ ComplexPattern<Either<Shared1, Shared2>, State, Input> patt, Function<Shared1, RetType> f,
+ Function<Shared2, RetType> g) {
+ return patt.mapOutput(eth -> eth.extract(f, g));
+ }
+
+ static <Shared, State, Input> ComplexPattern<Shared, State, Input> collapse(
+ ComplexPattern<Either<Shared, Shared>, State, Input> patt) {
+ return patt.mapOutput(Either::collapse);
+ }
+
+ static <RetType, State,
+ Input> ComplexPattern<Optional<RetType>, ?, Input> maybe(ComplexPattern<RetType, State, Input> pat) {
+ return from((inp) -> {
+ Pair<Boolean, State> res = pat.matches(inp);
+ if (res.getLeft())
+ return Pair.pair(true, Either.left(res.getRight()));
+ return Pair.pair(true, Either.right(null));
+ }, (Input inp, Either<State, State> state) -> {
+ // Need to specify the type; inference isn't smart enough to guess the right
+ // thing
+ if (state.isLeft())
+ return Optional.of(pat.apply(inp, state.forceLeft()));
+ return Optional.empty();
+ });
+ }
+
+ default <NewInput> ComplexPattern<ReturnType, Pair<PredType, InputType>, NewInput> mapInput(
+ Function<NewInput, InputType> func) {
+ return from((inp) -> {
+ InputType procInput = func.apply(inp);
+ Pair<Boolean, PredType> res = matches(procInput);
+ return Pair.pair(res.getLeft(), Pair.pair(res.getRight(), procInput));
+ }, (input, state) -> apply(state.getRight(), state.getLeft()));
+ }
+
+ default <NewOutput> ComplexPattern<NewOutput, PredType, InputType> mapOutput(Function<ReturnType, NewOutput> func) {
+ return from(ComplexPattern.this::matches, (inp, state) -> func.apply(apply(inp, state)));
+ }
+
+ default ComplexPattern<Pair<ReturnType, PredType>, PredType, InputType> withState() {
+ return from(ComplexPattern.this::matches, (inp, state) -> Pair.pair(apply(inp, state), state));
}
-}
+} \ No newline at end of file