From 4a96d9cad446ea405b51dfeebb01a1b6d7f6fb2b Mon Sep 17 00:00:00 2001 From: Ben Culkin Date: Tue, 27 Sep 2022 19:21:16 -0400 Subject: 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 --- .../java/bjc/utils/patterns/ComplexPattern.java | 347 ++++++++++++++------- 1 file changed, 240 insertions(+), 107 deletions(-) (limited to 'base/src/main/java/bjc/utils/patterns') 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 The type of object being matched against. + * @param The type of object being matched against. * @param The type returned by the pattern. - * @param The state type returned by the predicate. + * @param The state type returned by the predicate. */ public interface ComplexPattern { /** @@ -20,11 +23,11 @@ public interface ComplexPattern { * * @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 matches(InputType input); - + /** * Apply this pattern, once it has matched. * @@ -34,9 +37,9 @@ public interface ComplexPattern { * @return The result of applying this pattern. */ ReturnType apply(InputType input, PredType state); - + /* Pattern producing functions */ - + /** * Create a pattern composed from a predicate & a function. * @@ -44,153 +47,125 @@ public interface ComplexPattern { * @param The type used as intermediate state. * @param 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 ComplexPattern from( - Function> matcher, - BiFunction accepter) - { + Function> matcher, BiFunction 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 The type to check if the object is an instance of. - * @param The type returned by the action. - * @param The type of the thing to match. + * @param The type returned by the action. + * @param 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 ComplexPattern ofClass( - Class clasz, - Function action) - { - return from( - (input) -> Pair.pair(clasz.isInstance(input), null), - (input, ignored) -> action.apply((ClassType)input) - ); - } - + static ComplexPattern ofClass(Class clasz, + Function 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 The type returned when the pattern matches. * @param 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 ComplexPattern matchesObject( - InpType obj, - Function action - ) - { - return from( - (input) -> Pair.pair(obj.equals(input), null), - (input, ignored) -> action.apply(input) - ); - } - + static ComplexPattern matchesObject(InpType obj, + Function 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 The type returned by the pattern. * @param 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 ComplexPattern equalsString( - String pattern, - BiFunction action - ) - { + static ComplexPattern equalsString(String pattern, + BiFunction action) { Function> 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 The type returned by the pattern. - * @param The type of object to match against. + * @param 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 ComplexPattern matchesRegex( - String regex, - Predicate cond, - BiFunction action - ) - { + static ComplexPattern matchesRegex(String regex, + Predicate cond, BiFunction action) { java.util.regex.Pattern regexPat = java.util.regex.Pattern.compile(regex); Function> 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 The type returned. * @param 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 ComplexPattern otherwise( - Function action - ) - { - return from( - (input) -> Pair.pair(true, null), - (input, ignored) -> action.apply(input) - ); - } - + static ComplexPattern otherwise(Function 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 { * @param The type returned by the matcher. * @param 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 ComplexPattern startsWith( - String pattern, - Function action) - { + static ComplexPattern startsWith(String pattern, + Function 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 The type each pattern returns + * @param The input for each pattern. + * @param The first state type. + * @param 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 ComplexPattern, InpType> or( + ComplexPattern left, ComplexPattern 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>> matcher = (inp) -> { + Pair leftRes = left.matches(inp); + if (leftRes.getLeft()) { + return Pair.pair(true, Either.left(leftRes.getRight())); + } + + Pair 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 The type returned by the patterns + * @param The input for the patterns + * @param The state for the right pattern + * @param 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 ComplexPattern, Pair, InpType> and( + ComplexPattern left, + ComplexPattern right) { + Function>> matcher = (inp) -> { + Pair leftRes = left.matches(inp); + if (!leftRes.getLeft()) + return Pair.pair(false, null); + Pair 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 ComplexPattern, Either>, InpType> then( + ComplexPattern first, ComplexPattern second) { + Function>>> matcher = (inp) -> { + Pair firstRes = first.matches(inp); + if (!firstRes.getLeft()) + return Pair.pair(false, null); + Shared shared = first.apply(inp, firstRes.getRight()); + Pair 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 right = state.forceRight(); + return Either.right(second.apply(right.getLeft(), right.getRight())); + }); + } + + static ComplexPattern, Other>, + Either>, Other>, InpType> maybeThen( + ComplexPattern, State1, InpType> first, + ComplexPattern second) { + Function>, Other>>> matcher = (inp) -> { + Pair firstRes = first.matches(inp); + if (!firstRes.getLeft()) return Pair.pair(false, null); + Either maybeShared = first.apply(inp, firstRes.getRight()); + if (!maybeShared.isLeft()) + return Pair.pair(true, maybeShared.newLeft()); + Shared shared = maybeShared.forceLeft(); + Pair 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> left = state.forceLeft(); + if (left.isLeft()) { + return Either.left(left.newRight()); } - }, (ignored, input) -> action.apply(input)); + Pair right = left.forceRight(); + return Either.left(Either.right(second.apply(right.getLeft(), right.getRight()))); + }); + } + + static ComplexPattern collapse( + ComplexPattern, State, Input> patt, Function f, + Function g) { + return patt.mapOutput(eth -> eth.extract(f, g)); + } + + static ComplexPattern collapse( + ComplexPattern, State, Input> patt) { + return patt.mapOutput(Either::collapse); + } + + static ComplexPattern, ?, Input> maybe(ComplexPattern pat) { + return from((inp) -> { + Pair 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) -> { + // 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 ComplexPattern, NewInput> mapInput( + Function func) { + return from((inp) -> { + InputType procInput = func.apply(inp); + Pair res = matches(procInput); + return Pair.pair(res.getLeft(), Pair.pair(res.getRight(), procInput)); + }, (input, state) -> apply(state.getRight(), state.getLeft())); + } + + default ComplexPattern mapOutput(Function func) { + return from(ComplexPattern.this::matches, (inp, state) -> func.apply(apply(inp, state))); + } + + default ComplexPattern, PredType, InputType> withState() { + return from(ComplexPattern.this::matches, (inp, state) -> Pair.pair(apply(inp, state), state)); } -} +} \ No newline at end of file -- cgit v1.2.3