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 returned by the pattern. * @param The state type returned by the predicate. */ public interface ComplexPattern { /** * Whether or not the given input matches this pattern. * * @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. */ Pair matches(InputType input); /** * Apply this pattern, once it has matched. * * @param input The object to apply this pattern to. * @param state The state from the matcher. * * @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. * * @param The type returned by the pattern. * @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. * * @return A pattern composed from the passed in functions. */ static ComplexPattern from( 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). * * @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 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)); } /** * 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. * * @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)); } /** * 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. */ 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)); } /** * 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 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) { 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); return Pair.pair(false, null); }; 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. * * @return A pattern which will be executed. */ 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. * * @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. * * @return A pattern which functions as described. */ 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())); 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()); } 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)); } }