diff options
Diffstat (limited to 'base/src/main/java')
10 files changed, 557 insertions, 0 deletions
diff --git a/base/src/main/java/bjc/utils/patterns/ComplexPattern.java b/base/src/main/java/bjc/utils/patterns/ComplexPattern.java new file mode 100644 index 0000000..3926f2c --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/ComplexPattern.java @@ -0,0 +1,183 @@ +package bjc.utils.patterns; + +import java.util.function.*; +import java.util.regex.*; + +import bjc.data.*; + +/** + * A pattern that can be matched against. + * + * @author Ben Culkin + * + * @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. + */ +public interface ComplexPattern<ReturnType, PredType, InputType> { + /** + * 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. + */ + IPair<Boolean, PredType> 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); + + /** + * Create a pattern composed from a predicate & a function. + * + * @param <RetType> The type returned by the pattern. + * @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. + * + * @return A pattern composed from the passed in functions. + */ + static <RetType, PreType, InpType> ComplexPattern<RetType, PreType, InpType> from( + Function<InpType, IPair<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). + * + * @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 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) -> IPair.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. + * + * @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) -> IPair.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. + */ + static <RetType, InpType> ComplexPattern<RetType, ?, InpType> equalsString( + String pattern, + BiFunction<InpType, String, RetType> action + ) { + return from( + (input) -> { + String objString = input.toString(); + + return IPair.pair(pattern.equals(objString), objString); + }, + (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 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 + ) { + java.util.regex.Pattern regexPat = java.util.regex.Pattern.compile(regex); + + return from( + (input) -> { + String inpString = input.toString(); + + Matcher mat = regexPat.matcher(inpString); + + if (cond.test(mat)) { + return IPair.pair(true, mat); + } else { + return IPair.pair(false, null); + } + }, + (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. + * + * @return A pattern which will be executed. + */ + static <RetType, InpType> ComplexPattern<RetType, ?, InpType> otherwise( + Function<InpType, RetType> action + ) { + return from( + (input) -> IPair.pair(true, null), + (input, ignored) -> action.apply(input) + ); + } +}
\ No newline at end of file diff --git a/base/src/main/java/bjc/utils/patterns/FunctionalPattern.java b/base/src/main/java/bjc/utils/patterns/FunctionalPattern.java new file mode 100644 index 0000000..e4b4a3d --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/FunctionalPattern.java @@ -0,0 +1,47 @@ +package bjc.utils.patterns; + +import java.util.*; +import java.util.function.*; + +import bjc.data.*; + +class FunctionalPattern<ReturnType, PredType, InputType> + implements ComplexPattern<ReturnType, PredType, InputType> { + private final Function<InputType, IPair<Boolean, PredType>> matcher; + private final BiFunction<InputType, PredType, ReturnType> accepter; + + FunctionalPattern( + Function<InputType, IPair<Boolean, PredType>> matcher, + BiFunction<InputType, PredType, ReturnType> accepter) { + super(); + this.matcher = matcher; + this.accepter = accepter; + } + + @Override + public IPair<Boolean, PredType> matches(InputType input) { + return matcher.apply(input); + } + + @Override + public ReturnType apply(InputType input, PredType state) { + return accepter.apply(input, state); + } + + @Override + public int hashCode() { + return Objects.hash(accepter, matcher); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + + FunctionalPattern<?, ?, ?> other = (FunctionalPattern<?, ?, ?>) obj; + + return Objects.equals(accepter, other.accepter) + && Objects.equals(matcher, other.matcher); + } +}
\ No newline at end of file diff --git a/base/src/main/java/bjc/utils/patterns/FunctionalPatternMatcher.java b/base/src/main/java/bjc/utils/patterns/FunctionalPatternMatcher.java new file mode 100644 index 0000000..5a214d3 --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/FunctionalPatternMatcher.java @@ -0,0 +1,31 @@ +package bjc.utils.patterns; + +import bjc.functypes.*; + +/** + * A simple pattern matcher backed by a function. + * + * @author Ben Culkin + * + * @param <ReturnType> The type returned by the matcher. + * @param <InputType> The type to match against. + */ +public class FunctionalPatternMatcher<ReturnType, InputType> + implements IPatternMatcher<ReturnType, InputType> { + + private final ThrowFunction<InputType, ReturnType, NonExhaustiveMatch> matcher; + + /** + * Create a new function-backed pattern matcher. + * + * @param matcher The function backing this matcher. + */ + public FunctionalPatternMatcher(ThrowFunction<InputType, ReturnType, NonExhaustiveMatch> matcher) { + this.matcher = matcher; + } + + @Override + public ReturnType matchFor(InputType input) throws NonExhaustiveMatch { + return matcher.apply(input); + } +} diff --git a/base/src/main/java/bjc/utils/patterns/IPatternMatcher.java b/base/src/main/java/bjc/utils/patterns/IPatternMatcher.java new file mode 100644 index 0000000..b688a47 --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/IPatternMatcher.java @@ -0,0 +1,85 @@ +package bjc.utils.patterns; + +import java.util.function.*; + +import bjc.functypes.*; + +/** + * Represents a pattern matcher against a series of patterns. + * + * @author Ben Culkin + * + * @param <ReturnType> The type returned from matching the patterns. + * @param <InputType> The type to match against. + */ +@FunctionalInterface +public interface IPatternMatcher<ReturnType, InputType> { + /** + * Match an input object against a set of patterns. + * + * @param input The object to match against. + * + * @return The result of matching against the object. + * + * @throws NonExhaustiveMatch If none of the patterns in this set match + */ + ReturnType matchFor(InputType input) throws NonExhaustiveMatch; + + /** + * Create a pattern matcher against a static set of patterns. + * + * @param <RetType> The type returned from matching the patterns. + * @param <InpType> The type to match against. + * + * @param patterns The set of patterns to match on. + * + * @return A pattern matcher which matches on the given patterns. + */ + @SafeVarargs + static <RetType, InpType> IPatternMatcher<RetType, InpType> matchingOn( + ComplexPattern<RetType, ?, InpType>... patterns) { + return new PatternMatcher<>(patterns); + } + + /** + * Create a pattern matcher from a handler function. + * + * @param <RetType> The type returned by the matcher. + * @param <InpType> The type to match against. + * + * @param handler The handler function. + * + * @return A pattern matcher defined by the given handler. + */ + static <RetType, InpType> IPatternMatcher<RetType, InpType> from( + ThrowFunction<InpType, RetType, NonExhaustiveMatch> handler) { + return new FunctionalPatternMatcher<>(handler); + } + + /** + * Create a pattern matcher which applies a transform to its input. + * + * @param <NewInput> The new input type to use. + * @param transformer The function to convert from the new input to the old input. + * + * @return A pattern matcher which takes values of the new type instead. + */ + default <NewInput> IPatternMatcher<ReturnType, NewInput> transformInput( + Function<NewInput, InputType> transformer) { + return from(inp -> matchFor(transformer.apply(inp))); + } + + /** + * Create a pattern matcher which applies a transform to its output. + * + * @param <NewOutput> The new output type to use. + * + * @param transformer The function to convert from the new output to the old output. + * + * @return A pattern matcher which takes values of the new type instead. + */ + default <NewOutput> IPatternMatcher<NewOutput, InputType> transformOutput( + Function<ReturnType, NewOutput> transformer) { + return from(inp -> transformer.apply(matchFor(inp))); + } +}
\ No newline at end of file diff --git a/base/src/main/java/bjc/utils/patterns/MutablePatternMatcher.java b/base/src/main/java/bjc/utils/patterns/MutablePatternMatcher.java new file mode 100644 index 0000000..7900262 --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/MutablePatternMatcher.java @@ -0,0 +1,84 @@ +package bjc.utils.patterns; + +import java.util.*; + +import bjc.data.*; + +/** + * A pattern matcher over a mutable set of patterns. + * + * Note that modifying a pattern matcher while it is currently doing pattern + * matching is a wonderful way to cause strange behavior. + * + * @author Ben Culkin + * + * @param <ReturnType> The type returned by the pattern matcher. + */ +public class MutablePatternMatcher<ReturnType, InputType> + implements IPatternMatcher<ReturnType, InputType>{ + private final List<ComplexPattern<ReturnType, Object, InputType>> patterns; + + /** + * Create a new mutable pattern matcher with no patterns. + */ + public MutablePatternMatcher() { + patterns = new ArrayList<>(); + } + + /** + * Create a new mutable pattern matcher with the given set of patterns. + * + * @param patterns The set of patterns to match on. + */ + @SuppressWarnings("unchecked") + public MutablePatternMatcher(ComplexPattern<ReturnType, ?, InputType>... patterns) { + this(); + + for (ComplexPattern<ReturnType, ?, InputType> pattern : patterns) { + // Note: this may seem a somewhat questionable cast, but because we never + // actually do anything with the value who has a type matching the second + // parameter, this should be safe + this.patterns.add((ComplexPattern<ReturnType, Object, InputType>) pattern); + } + } + + @Override + public ReturnType matchFor(InputType input) throws NonExhaustiveMatch { + Iterator<ComplexPattern<ReturnType, Object, InputType>> iterator; + for (iterator = new NonCMEIterator<>(patterns); + iterator.hasNext();) { + ComplexPattern<ReturnType, Object, InputType> pattern = iterator.next(); + + IPair<Boolean, Object> matches = pattern.matches(input); + + if (matches.getLeft()) { + pattern.apply(input, matches.getRight()); + } + } + + throw new NonExhaustiveMatch("Non-exhaustive match against " + input); + } + + /** + * Add a pattern to this pattern matcher. + * + * @param pattern The pattern to add. + * + * @return Whether or not the pattern was added. + */ + @SuppressWarnings("unchecked") + public boolean addPattern(ComplexPattern<ReturnType, ?, InputType> pattern) { + return patterns.add((ComplexPattern<ReturnType, Object, InputType>) pattern); + } + + /** + * Remove a pattern from this pattern matcher. + * + * @param pattern The pattern to remove. + * + * @return Whether or not the pattern was removed. + */ + public boolean removePattern(ComplexPattern<ReturnType, ?, InputType> pattern) { + return patterns.remove(pattern); + } +} diff --git a/base/src/main/java/bjc/utils/patterns/NonExhaustiveMatch.java b/base/src/main/java/bjc/utils/patterns/NonExhaustiveMatch.java new file mode 100644 index 0000000..aaa7e89 --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/NonExhaustiveMatch.java @@ -0,0 +1,20 @@ +package bjc.utils.patterns; + +/** + * Exception thrown when a non-exhaustive match happens. + * @author Ben Culkin + * + */ +public class NonExhaustiveMatch extends Exception { + private static final long serialVersionUID = 3892904574888418544L; + + /** + * Create a new non-exhaustive match. + * + * @param message The message for the exception. + */ + public NonExhaustiveMatch(String message) { + super(message); + } + +} diff --git a/base/src/main/java/bjc/utils/patterns/Pattern.java b/base/src/main/java/bjc/utils/patterns/Pattern.java new file mode 100644 index 0000000..e03623e --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/Pattern.java @@ -0,0 +1,20 @@ +package bjc.utils.patterns; + +import java.util.*; +import java.util.function.*; +import java.util.regex.*; + +import bjc.data.*; + +/** + * A simpler version of ComplexPattern, which always applies against Object + * + * @author Ben Culkin + * + * @param <ReturnType> The type returned by the pattern. + * @param <PredType> The state type returned by the predicate. + */ +public interface Pattern<ReturnType, PredType> + extends ComplexPattern<ReturnType, PredType, Object> { + /* Pattern factory methods */ +}
\ No newline at end of file diff --git a/base/src/main/java/bjc/utils/patterns/PatternMatcher.java b/base/src/main/java/bjc/utils/patterns/PatternMatcher.java new file mode 100644 index 0000000..e2ae9f6 --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/PatternMatcher.java @@ -0,0 +1,41 @@ +package bjc.utils.patterns; + +import bjc.data.*; + +/** + * Implements pattern-matching (of a sort) against a collection of patterns. + * + * @author Ben Culkin + * + * @param <ReturnType> The type returned by the pattern. + */ +public class PatternMatcher<ReturnType, InputType> + implements IPatternMatcher<ReturnType, InputType> { + private final ComplexPattern<ReturnType, Object, InputType>[] patterns; + + /** + * Create a new pattern matcher. + * + * @param patterns The set of patterns to match against. + */ + @SuppressWarnings("unchecked") + @SafeVarargs + public PatternMatcher(ComplexPattern<ReturnType, ?, InputType>...patterns) { + // Note: this may seem a somewhat questionable cast, but because we never + // actually do anything with the value who has a type matching the second + // parameter, this should be safe + this.patterns = (ComplexPattern<ReturnType, Object, InputType>[]) patterns; + } + + @Override + public ReturnType matchFor(InputType input) throws NonExhaustiveMatch { + for (ComplexPattern<ReturnType, Object, InputType> pattern : patterns) { + IPair<Boolean, Object> matches = pattern.matches(input); + if (matches.getLeft()) { + pattern.apply(input, matches.getRight()); + } + } + + throw new NonExhaustiveMatch("Non-exhaustive match against " + input); + } +} diff --git a/base/src/main/java/bjc/utils/patterns/SimplePatttern.java b/base/src/main/java/bjc/utils/patterns/SimplePatttern.java new file mode 100644 index 0000000..1601894 --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/SimplePatttern.java @@ -0,0 +1,40 @@ +package bjc.utils.patterns; + +import bjc.data.*; + +/** + * A simpler form of a pattern. + * + * @author Ben Culkin + * + * @param <ReturnType> The type returned by matching the pattern. + */ +public interface SimplePatttern<ReturnType> extends Pattern<ReturnType, Void> { + /** + * Test if this pattern does match a given object. + * + * @param input The object to test against. + * + * @return Whether the object matches this pattern. + */ + boolean doesMatch(Object input); + + /** + * Applies this pattern to the input object. + * + * @param input The object that passed the condition. + * + * @return The result of applying this action to the input. + */ + ReturnType doApply(Object input); + + @Override + default ReturnType apply(Object input, Void state) { + return doApply(input); + } + + @Override + default IPair<Boolean, Void> matches(Object input) { + return new Pair<>(doesMatch(input), null); + } +}
\ No newline at end of file diff --git a/base/src/main/java/bjc/utils/patterns/package-info.java b/base/src/main/java/bjc/utils/patterns/package-info.java new file mode 100644 index 0000000..a562d95 --- /dev/null +++ b/base/src/main/java/bjc/utils/patterns/package-info.java @@ -0,0 +1,6 @@ +package bjc.utils.patterns; + +/* + * Pattern matching (of a sort) in Java. Based off of + * http://kerflyn.wordpress.com/2012/05/09/towards-pattern-matching-in-java/ + */
\ No newline at end of file |
