All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nike.riposte.util.text.parsercombinator.Parser Maven / Gradle / Ivy

There is a newer version: 0.20.0
Show newest version
package com.nike.riposte.util.text.parsercombinator;

import com.nike.internal.util.ImmutablePair;
import com.nike.internal.util.Pair;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * A ultra-lightweight parser combinator library with one dependency ({@link Pair} from Nike's internal utils library).
 * parse sequencing is done by returning left nested pairs that are mapped & matched into complex types.
 *
 * Inspired by Haskell's Parsec library
 * This Java8 interface is influenced by Joakim Ahnfelt-Rønne's 'A Parser Combinator for Java 8'
 * Additional interface inspired from the the jparsec project.
 *
 * see: https://en.wikipedia.org/wiki/Parser_combinator
 * see: https://hackage.haskell.org/package/parsec
 * see: http://www.ahnfelt.net/a-parser-combinator-for-java-8-2/
 * see: https://github.com/jparsec/jparsec
 *
 * @author Kirk Peterson
 */
@SuppressWarnings({"unused", "WeakerAccess"})
public interface Parser {

    /**
     * The primary interface to parsing an input to a output of type T.
     *
     * @param parserInput
     *     The input for the parser to ingress.
     *
     * @return the output of this parser.
     *
     * @throws ParserFailure
     *     if this parser is unable to sucessfully parse the input into an output of type T.
     */
    T parse(ParserInput parserInput) throws ParserFailure;


    /**
     * Performs this parser's operation on a given sequence of text characters.
     *
     * @param text
     *     the text input to this parser
     *
     * @return the output of this parser.
     *
     * @throws ParserFailure
     *     if this parser is unable to sucessfully parse the input text into an output of type T.
     */
    default T parse(final CharSequence text) throws ParserFailure {
        return parse(new ParserInput(text, 0));
    }

    /**
     * Attempts to parse the given input into an output of type T. If the attempt fails, the input's offset is reset
     * back to it's starting offset value.
     *
     * @param parserInput
     *     the input for the parser to ingress.
     *
     * @return An Optional with an instance of T, if the parse operation was successful, otherwise Optional.empty().
     */
    default Optional tryParse(final ParserInput parserInput) {
        final int offset = parserInput.getOffset();
        try {
            return Optional.of(parse(parserInput));
        }
        catch (ParserFailure failure) {
            parserInput.setOffset(offset);
            return Optional.empty();
        }
    }

    /**
     * Attempts to parse the given text into an output of type T. If the attempt fails, the input's offset is reset
     * back to it's starting offset value.
     *
     * @param text
     *     the text input to this parser
     *
     * @return An Optional with an instance of T, if the parse operation was successful, otherwise Optional.empty().
     */
    default Optional tryParse(final CharSequence text) {
        return tryParse(new ParserInput(text, 0));
    }


    /**
     * Returns a parser that will attempt to first parse its own result,
     * subsequently will parse to the given parser's result, returning both as a
     * Pair, with this parsers result to the Pair's left, and the given parser's result
     * to the Pair's right.
     *
     * @param parser
     *     parser that will attempt to first parse its own result, subsequently will parse to the given parser's
     *     result.
     *
     * @return a parser that will attempt to first parse its own result, subsequently will parse to the given parser's
     * result.
     */
    default  Parser> thenParse(final Parser parser) {
        return parserInput -> ImmutablePair.of(parse(parserInput), parser.parse(parserInput));
    }

    /**
     * Returns a parser that will attempt to first parse its own result,
     * subsequently will parse to the given result of the parser produced from the given supplier,
     * returning both as a  Pair, with this parsers result to the Pair's left, and the given's result
     * to the Pair's right.
     *
     * @param supplier
     *     the supplier of a parser whos result is to be discarded.
     *
     * @return a parser that will attempt to first parse its own result, subsequently will parse to the given result of
     * the parser produced from the given supplier.
     */
    default  Parser> thenParse(final Supplier> supplier) {
        return parserInput -> thenParse(supplier.get()).parse(parserInput);
    }

    /**
     * Constructs a parser that will first parse this parser's result,
     * subsequently will parse and discard the given parser's result, returning only this
     * parser's result.
     *
     * @param parser
     *     the parser whos result is to be discarded.
     *
     * @return a parser that will first parse this parser's result, subsequently will parse and discard the given
     * parser's result.
     */
    default Parser thenSkip(final Parser parser) {
        return parserInput -> {
            final T result = parse(parserInput);
            parser.parse(parserInput);
            return result;
        };
    }

    /**
     * Constructs a parser that will first parse this parser's result,
     * subsequently will parse and discard the parser returned from the given supplier, returning only this
     * parser's result.
     *
     * @param supplier
     *     the supplier of a parser whos result is to be discarded.
     *
     * @return a parser that will first parse this parser's result, subsequently will parse and discard the given
     * parser's result.
     */
    default Parser thenSkip(final Supplier> supplier) {
        return parserInput -> thenSkip(supplier.get()).parse(parserInput);
    }

    /**
     * Constructs a parser that will first parse this parser's result, if unsuccessful the given parser will attempt to
     * parser its result.
     *
     * The first successfull result will be returned.
     *
     * @param parser
     *     the parser to try, if this parser fails.
     *
     * @return a parser that will first parse this parser's result, if unsuccessful the given parser will attempt to
     * parser its result.
     */
    default Parser or(final Parser parser) {
        return parserInput -> {
            final int offset = parserInput.getOffset();
            try {
                return Parser.this.parse(parserInput);
            }
            catch (ParserFailure failure) {
                parserInput.setOffset(offset);
                return parser.parse(parserInput);
            }
        };
    }

    /**
     * Constructs a parser that will first parse this parser's result, if unsuccessful the given supplier's parser will
     * attempt to parser its result.
     *
     * @param supplier
     *     provides a parser to try if this parser fails.
     *
     * @return a parser that will first parse this parser's result, if unsuccessful the given supplier's parser will
     * attempt to parser its result.
     */
    default Parser or(final Supplier> supplier) {
        return parserInput -> or(supplier.get()).parse(parserInput);
    }

    /**
     * Constructs a parser that will parse this parser's result, returning an Optiona with the result from the parse.
     *
     * @return Parser that will result in an optional. If this parser fails the returned parser will return
     * Optional.empty().
     */
    default Parser> optional() {
        return parserInput -> {
            try {
                return Optional.of(parse(parserInput));
            }
            catch (ParserFailure failure) {
                return Optional.empty();
            }
        };
    }

    /**
     * Constructs a parser that will repeatedly parse this parsers result in until it fails.
     *
     * @return A list of this parser's results. If no matches were found initially, an empty list will be returned.
     */
    default Parser> zeroOrMore() {
        return parserInput -> Parsers.zeroOrMore(Parser.this).parse(parserInput);
    }

    /**
     * Constructs a parser that will repeatedly parse this parsers result in until it or the given seperater-parser
     * fails. After each successful parse of this parser's result, an attempt is made to parse the given
     * seperater-parser's result.
     *
     * @param separator
     *     the parser that seperates this parser's results. the seperater-parser's result is discarded.
     *
     * @return A list of this parser's results. If no matches were found initially, an empty list will be returned.
     */
    default Parser> zeroOrMore(final Parser separator) {
        return parserInput -> Parsers.zeroOrMore(Parser.this, separator).parse(parserInput);
    }

    /**
     * Constructs a parser that will repeatedly parse this parsers result in until it fails.
     * This parser fails if it could not parse at least once successfully.
     *
     * @return A list of this parser's results.
     */
    default Parser> oneOrMore() {
        return parserInput -> Parsers.oneOrMore(Parser.this).parse(parserInput);
    }

    /**
     * Constructs a parser that will repeatedly parse this parsers result in until it or the given seperater-parser
     * fails. After each successful parse of this parser's result, an attempt is made to parse the given
     * seperater-parser's result. This parser fails if it could not parse at least once successfully.
     *
     * @param separator
     *     the parser that seperates this parser's results. the seperater-parser's result is discarded.
     *
     * @return A list of this parser's results.
     */
    default Parser> oneOrMore(final Parser separator) {
        return parserInput -> Parsers.oneOrMore(Parser.this, separator).parse(parserInput);
    }

    /**
     * Constructs a parser that will parse this parser's result then test the result with the given predicate.
     *
     * @param predicate
     *     the funciton to test the success or failure of this parser's result with.
     *
     * @return a parser that will parse this parser's result then test the result with the given predicate, returning
     * the result if the test is a success, failure if the test fails.
     */
    default Parser filter(final Predicate predicate) {
        return parserInput -> Parsers.filter(Parser.this, predicate).parse(parserInput);
    }

    /**
     * Constructs a parser that converts the this parser's result type to another type, using the given function.
     *
     * @param function
     *     the function to convert this parser's result to
     *
     * @return a parser that converts the this parser's result type to another type, using the given function.
     */
    default  Parser map(final Function function) {
        return parserInput -> function.apply(parse(parserInput));
    }

    /**
     * Constructs a parser that converts the this parser's result type to another parser, using the given function.
     *
     * @param function
     *     the function to convert this parser's result to
     *
     * @return a parser that converts the this parser's result type to another parser, using the given function.
     */
    default  Parser flatMap(final Function> function) {
        return parserInput -> function.apply(parse(parserInput)).parse(parserInput);
    }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    Object UNIT = new Object();

    /**
     * A collection of parsers that implement common tasks.
     * Most are useful for creating an initial parser intances.
     */
    final class Parsers {

        public static final Parser EMPTY_PARSER = success(UNIT);
        public static final Parser BEGIN_PARSER = regex("^").map(m -> UNIT);
        public static final Parser END_PARSER = regex("$").map(m -> UNIT);

        /**
         * Lifts a value into a parser of the same type.
         *
         * @param value
         *     the item to return
         *
         * @return The item given.
         */
        public static  Parser success(final R value) {
            return parserInput -> value;
        }

        /**
         * Constructs a parser that will always fail.
         *
         * @param cause
         *     The message to report to the user regarding the failure.
         *
         * @return a parser that will always fail, reporting the given message.
         */
        public static  Parser failure(final String cause) {
            return parserInput -> {
                throw new ParserFailure(cause, parserInput);
            };
        }

        /**
         * Constructs a parser that parsers to UNIT result.
         */
        public static Parser empty() {
            return EMPTY_PARSER;
        }


        public static Parser begin() {
            return BEGIN_PARSER;
        }

        public static Parser end() {
            return END_PARSER;
        }


        /**
         * Constructs a parser that will return the first successful parser result, from the given stream of parsers.
         *
         * @param parsers
         *     the stream of parsers to attempt parsing
         *
         * @return the first successful parser result.
         */
        public static  Parser oneOf(final Stream> parsers) {
            return parsers.reduce(Parser::or).orElse(failure("No parsers given to choice parser."));
        }

        /**
         * Constructs a parser that will return the first successful parser result, from the given list of parsers.
         *
         * @param parsers
         *     the list of parsers to attempt parsing
         *
         * @return the first successful parser result.
         */
        public static  Parser oneOf(final List> parsers) {
            return oneOf(parsers.stream());
        }

        /**
         * Constructs a parser that will return the first successful parser result, from the given array of parsers.
         *
         * @param parsers
         *     the array of parsers to attempt parsing
         *
         * @return the first successful parser result.
         */
        @SafeVarargs
        public static  Parser oneOf(final Parser... parsers) {
            return oneOf(Arrays.stream(parsers));
        }

        /**
         * Constructs a Parser that will repeatedly attempt to parse the given parser sperated by the given separator
         * parser, until failure, returning a list of all successful parse results that occurred before the failure.
         *
         * @param parser
         *     the parser to attempt repeatedly parsing an input string with.
         * @param separator
         *     the parser that seperates the given parser's results. the seperater-parser's result is discarded.
         * @param 
         *     The type of the returned list.
         *
         * @return A list of the given parser's results. If no matches were found, an empty list will be returned.
         */
        public static  Parser> zeroOrMore(final Parser parser, final Parser separator) {
            return parserInput -> {
                final List result = new ArrayList<>();
                Optional element;
                do {
                    element = peek(parser).tryParse(parserInput);
                    if (element.isPresent()) {
                        result.add(element.get());
                    }
                } while (peek(separator).tryParse(parserInput).isPresent());
                return result;
            };
        }

        /**
         * Constructs a Parser that will repeatedly attempt to parse the given parser until failure,
         * returning a list of all successful parse results that occurred before the failure.
         *
         * @param parser
         *     the parser to attempt repeatedly parsing an input string with.
         * @param 
         *     The type of the returned list.
         *
         * @return A list of the given parser's results. If no matches were found, an empty list will be returned.
         */
        public static  Parser> zeroOrMore(final Parser parser) {
            return parserInput -> {
                final List result = new ArrayList();
                Optional element;
                while ((element = peek(parser).tryParse(parserInput)).isPresent()) {
                    result.add(element.get());
                }
                return result;
            };
        }

        /**
         * Constructs a parser that will repeatedly parse the given parsers result in until it fails.
         * This parser fails if it could not parse at least once successfully from the given parser.
         *
         * @param parser
         *     the parser to attempt repeatedly parsing an input string with.
         * @param 
         *     The type of the returned list.
         *
         * @return A list of the given parser's results.
         */
        public static  Parser> oneOrMore(final Parser parser) {
            return parserInput -> {
                final List result = new ArrayList<>();
                result.add(peek(parser).parse(parserInput));
                Optional element;
                while ((element = peek(parser).tryParse(parserInput)).isPresent()) {
                    result.add(element.get());
                }
                return result;
            };
        }

        /**
         * Constructs a parser that will repeatedly parse the given parsers result in until it or the given
         * seperater-parser fails. After each successful parse of the given parser's result, an attempt is made to parse
         * the given seperater-parser's result. This parser fails if it could not parse at least once successfully from
         * the given parser.
         *
         * @param parser
         *     the parser to attempt repeatedly parsing an input string with.
         * @param separator
         *     the parser that seperates this parser's results. the seperater-parser's result is discarded.
         * @param 
         *     The type of the returned list.
         *
         * @return A list of the given parser's results.
         */
        public static  Parser> oneOrMore(final Parser parser, final Parser separator) {
            return parserInput -> {
                final List result = new ArrayList<>();
                result.add(peek(parser).parse(parserInput));
                try {
                    while (peek(separator).tryParse(parserInput).isPresent()) {
                        result.add(peek(parser).parse(parserInput));
                    }
                }
                finally {
                    return result;
                }
            };
        }

        /**
         * Constructs a parser that will discard the result of the given parser.
         *
         * @param parser
         *     the parser to consume.
         *
         * @return a parser that will discard the result of the given parser.
         */
        public static SkipParser skip(final Parser parser) {
            return new SkipParser(parser);
        }

        /**
         * Convenience wrapper for constructing a new instance of a FloarParser.
         */
        public static Parser floatNumber() {
            return FloatParser.singleton;
        }


        /**
         * Attempts to parse the given Parser, resetting the parser input state if the parse fails.
         * to the text and offset settings that were set prior to parsing.
         *
         * @param parser
         *     the parser to attempt parsing from.
         * @param 
         *     The return type of a successful parse
         *
         * @return the result of the given parser.
         */
        public static  Parser peek(final Parser parser) {
            return parserInput -> {
                final CharSequence text = parserInput.getText();
                final int offset = parserInput.getOffset();

                try {
                    return parser.parse(parserInput);
                }
                catch (ParserFailure e) {
                    parserInput.setText(text);
                    parserInput.setOffset(offset);
                    throw e;
                }
            };
        }

        /**
         * Constructsa a Parser that will parse the given parser and feed it's result to the given predicate.
         * If the predicate returns true, then the result is returned.
         * If parsing with the given parser fails, or the predicate returns false, then this parser will fail.
         */
        public static  Parser filter(final Parser parser, Predicate predicate) {
            return parserInput -> {
                final CharSequence text = parserInput.getText();
                final int offset = parserInput.getOffset();

                final R result = peek(parser).parse(parserInput);

                if (predicate.test(result)) {
                    return result;
                }

                parserInput.setText(text);
                parserInput.setOffset(offset);
                throw new ParserFailure(parserInput);
            };
        }

        /**
         * Lifts a supplier into a parser that will and forward any input to the supplier's parser.
         *
         * @param parser
         *     the parser to receive
         */
        public static  Parser then(final Supplier> parser) {
            return parserInput -> parser.get().parse(parserInput);
        }

        /**
         * Constructs a parser that will parse the given string from its input.
         *
         * @return the string parsed from an input, that exactly matches the given string.
         */
        public static Parser string(final String string) {
            return regex(Pattern.quote(string)).map(MatchResult::group);
        }

        /**
         * Constructs a parser that will compile the given string into a java regular expression pattern,
         * and match its input to it.
         *
         * @param regex
         *     the java regex string to compile into a pattern and match input against.
         *
         * @return the MatchResult of a successful Matcher.lookingAt() test.
         */
        public static Parser regex(final String regex) {
            return regex(Pattern.compile(regex));
        }

        /**
         * Constructs a parser that matches its input to the given regular expression pattern.
         *
         * @param pattern
         *     the java regex pattern to match input with.
         *
         * @return a parser that matches its input to the given regular expression pattern.
         */
        public static Parser regex(final Pattern pattern) {
            return new RegexParser(pattern);
        }

    }

    /**
     * A set of functions for simplifying the evaluation of anonmyous closures.
     * Useful when combined with .map() to reduse the result of several successful parses into a single result.
     */
    final class Apply {

        /**
         * Returns the given funciton.
         * e.g. string("A").map(match( (a) -> ...))
         */
        public static  Function match(final Function function) {
            return function;
        }

        /**
         * Constructs a function that takes a nested-left pair, two deep, and applies it to the given two-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use match() in conjunction with
         * parser.map() to construct useful structures while maintaining type safety. e.g.
         * string("A").string("B").map(match( (a,b) -> ...))
         */
        public static  Function, R> match(final BiFunction function) {
            return pair -> function.apply(
                pair.getLeft(),
                pair.getRight()
            );
        }

        /**
         * Constructs a function that takes a nested-left pair, three deep, and applies it to the given three-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use match() in conjunction with
         * parser.map() to construct useful structures while maintaining type safety. e.g.
         * string("A").string("B").string("C").map(match( (a,b,c) -> ...))
         */
        public static  Function, C>, R> match(final Function3 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft(),
                pair.getLeft().getRight(),
                pair.getRight()
            );

        }

        /**
         * Constructs a function that takes a nested-left pair, four deep, and applies it to the given four-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use match() in conjunction with
         * parser.map() to construct useful structures while maintaining type safety. e.g.
         * string("A").string("B").string("C").string("D").map(match( (a,b,c,d) -> ...))
         */
        public static  Function, C>, D>, R> match(
            final Function4 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );

        }

        /**
         * Constructs a function that takes a nested-left pair, five deep, and applies it to the given five-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use match() in conjunction with
         * parser.map() to construct useful structures while maintaining type safety. e.g.
         * string("A").string("B").string("C").string("D").string("E").map(match( (a,b,c,d,e) -> ...))
         */
        public static  Function, C>, D>, E>, R> match(
            final Function5 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Constructs a function that takes a nested-left pair, six deep, and applies it to the given six-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use match() in conjunction with
         * parser.map() to construct useful structures while maintaining type safety. e.g.
         * string("A").string("B").string("C").string("D").string("E").string("F").map(match( (a,b,c,d,e,f) -> ...))
         */
        public static  Function, C>, D>, E>, F>, R> match(
            final Function6 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Constructs a function that takes a nested-left pair, seven deep, and applies it to the given seven-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use match() in conjunction with
         * parser.map() to construct useful structures while maintaining type safety. e.g.
         * string("A").string("B").string("C").string("D").string("E").string("F").string("G").map(match(
         * (a,b,c,d,e,f,g) -> ...))
         */
        public static  Function, C>, D>, E>, F>, G>, R> match(
            final Function7 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Constructs a function that takes a nested-left pair, eight deep, and applies it to the given eight-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use match() in conjunction with
         * parser.map() to construct useful structures while maintaining type safety. e.g.
         * string("A").string("B").string("C").string("D").string("E").string("F").string("G").string("H").map(match(
         * (a,b,c,d,e,f,g,h) -> ...))
         */
        public static  Function, C>, D>, E>, F>, G>, H>, R> match(
            final Function8 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );

        }

        /**
         * Constructs a function that takes a nested-left pair, nine deep, and applies it to the given nine-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use match() in conjunction with
         * parser.map() to construct useful structures while maintaining type safety. e.g.
         * string("A").string("B").string("C").string("D").string("E").string("F").string("G").string("H").string("I").map(match(
         * (a,b,c,d,e,f,g,h,i) -> ...))
         */
        public static  Function, C>, D>, E>, F>, G>, H>, I>, R> match(
            final Function9 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }


        public static  Predicate test(final Function function) {
            return function::apply;
        }


        /**
         * Constructs a predicate that takes a nested-left pair, two deep, and applies it to the given two-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use test() in conjunction with
         * parser.filter() to pass/fail a parser's result. e.g. string("A").string("B").string("C").string("D").filter(test(
         * (a,b,c,d) -> a+b != c+d)).map(match( (a,b,c,d) -> ...))
         */
        public static  Predicate> test(final BiFunction function) {
            return pair -> function.apply(
                pair.getLeft(),
                pair.getRight()
            );
        }

        /**
         * Constructs a predicate that takes a nested-left pair, three deep, and applies it to the given three-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use test() in conjunction with
         * parser.filter() to pass/fail a parser's result. e.g. string("A").string("B").string("C").string("D").filter(test(
         * (a,b,c,d) -> a+b != c+d)).map(match( (a,b,c,d) -> ...))
         */
        public static  Predicate, C>> test(final Function3 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Constructs a predicate that takes a nested-left pair, four deep, and applies it to the given four-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use test() in conjunction with
         * parser.filter() to pass/fail a parser's result. e.g. string("A").string("B").string("C").string("D").filter(test(
         * (a,b,c,d) -> a+b != c+d)).map(match( (a,b,c,d) -> ...))
         */
        public static  Predicate, C>, D>> test(
            final Function4 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Constructs a predicate that takes a nested-left pair, five deep, and applies it to the given five-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use test() in conjunction with
         * parser.filter() to pass/fail a parser's result. e.g. string("A").string("B").string("C").string("D").filter(test(
         * (a,b,c,d) -> a+b != c+d)).map(match( (a,b,c,d) -> ...))
         */
        public static  Predicate, C>, D>, E>> test(
            final Function5 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Constructs a predicate that takes a nested-left pair, six deep, and applies it to the given six-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use test() in conjunction with
         * parser.filter() to pass/fail a parser's result. e.g. string("A").string("B").string("C").string("D").filter(test(
         * (a,b,c,d) -> a+b != c+d)).map(match( (a,b,c,d) -> ...))
         */
        public static  Predicate, C>, D>, E>, F>> test(
            final Function6 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Constructs a predicate that takes a nested-left pair, seven deep, and applies it to the given seven-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use test() in conjunction with
         * parser.filter() to pass/fail a parser's result. e.g. string("A").string("B").string("C").string("D").filter(test(
         * (a,b,c,d) -> a+b != c+d)).map(match( (a,b,c,d) -> ...))
         */
        public static  Predicate, C>, D>, E>, F>, G>> test(
            final Function7 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Constructs a predicate that takes a nested-left pair, eight deep, and applies it to the given eight-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use test() in conjunction with
         * parser.filter() to pass/fail a parser's result. e.g. string("A").string("B").string("C").string("D").filter(test(
         * (a,b,c,d) -> a+b != c+d)).map(match( (a,b,c,d) -> ...))
         */
        public static  Predicate, C>, D>, E>, F>, G>, H>> test(
            final Function8 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );

        }

        /**
         * Constructs a predicate that takes a nested-left pair, nine deep, and applies it to the given nine-arity
         * function. Chains of thenParse() calls will produce nested-left pairs, use test() in conjunction with
         * parser.filter() to test a parser's result. e.g. string("A").string("B").string("C").string("D").filter(test(
         * (a,b,c,d) -> a+b != c+d)).map(match( (a,b,c,d) -> ...))
         */
        public static  Predicate, C>, D>, E>, F>, G>, H>, I>> test(
            final Function9 function) {
            return pair -> function.apply(
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getLeft(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getLeft().getRight(),
                pair.getLeft().getLeft().getRight(),
                pair.getLeft().getRight(),
                pair.getRight()
            );
        }

        /**
         * Represents a function that accepts three arguments and produces a result.
         * This is the three-arity specialization of {@link Function}.
         *
         * @param 
         *     the type of the first argument to the function
         * @param 
         *     the type of the second argument to the function
         * @param 
         *     the type of the third argument to the function
         * @param 
         *     the type of the result of the function
         */
        @FunctionalInterface
        public interface Function3 {

            R apply(A a, B b, C c);
        }

        /**
         * Represents a function that accepts four arguments and produces a result.
         * This is the four-arity specialization of {@link Function}.
         *
         * @param 
         *     the type of the first argument to the function
         * @param 
         *     the type of the second argument to the function
         * @param 
         *     the type of the third argument to the function
         * @param 
         *     the type of the fourth argument to the function
         * @param 
         *     the type of the result of the function
         */
        @FunctionalInterface
        public interface Function4 {

            R apply(A a, B b, C c, D d);
        }

        /**
         * Represents a function that accepts five arguments and produces a result.
         * This is the five-arity specialization of {@link Function}.
         *
         * @param 
         *     the type of the first argument to the function
         * @param 
         *     the type of the second argument to the function
         * @param 
         *     the type of the third argument to the function
         * @param 
         *     the type of the fourth argument to the function
         * @param 
         *     the type of the fith argument to the function
         * @param 
         *     the type of the result of the function
         */
        @FunctionalInterface
        public interface Function5 {

            R apply(A a, B b, C c, D d, E e);
        }

        /**
         * Represents a function that accepts six arguments and produces a result.
         * This is the six-arity specialization of {@link Function}.
         *
         * @param 
         *     the type of the first argument to the function
         * @param 
         *     the type of the second argument to the function
         * @param 
         *     the type of the third argument to the function
         * @param 
         *     the type of the fourth argument to the function
         * @param 
         *     the type of the fith argument to the function
         * @param 
         *     the type of the sixth argument to the function
         * @param 
         *     the type of the result of the function
         */
        @FunctionalInterface
        public interface Function6 {

            R apply(A a, B b, C c, D d, E e, F f);
        }

        /**
         * Represents a function that accepts seven arguments and produces a result.
         * This is the seven-arity specialization of {@link Function}.
         *
         * @param 
         *     the type of the first argument to the function
         * @param 
         *     the type of the second argument to the function
         * @param 
         *     the type of the third argument to the function
         * @param 
         *     the type of the fourth argument to the function
         * @param 
         *     the type of the fith argument to the function
         * @param 
         *     the type of the sixth argument to the function
         * @param 
         *     the type of the seventh argument to the function
         * @param 
         *     the type of the result of the function
         */
        @FunctionalInterface
        public interface Function7 {

            R apply(A a, B b, C c, D d, E e, F f, G g);
        }

        /**
         * Represents a function that accepts eight arguments and produces a result.
         * This is the eight-arity specialization of {@link Function}.
         *
         * @param 
         *     the type of the first argument to the function
         * @param 
         *     the type of the second argument to the function
         * @param 
         *     the type of the third argument to the function
         * @param 
         *     the type of the fourth argument to the function
         * @param 
         *     the type of the fith argument to the function
         * @param 
         *     the type of the sixth argument to the function
         * @param 
         *     the type of the seventh argument to the function
         * @param 
         *     the type of the eighth argument to the function
         * @param 
         *     the type of the result of the function
         */
        @FunctionalInterface
        public interface Function8 {

            R apply(A a, B b, C c, D d, E e, F f, G g, H h);
        }

        /**
         * Represents a function that accepts nine arguments and produces a result.
         * This is the nine-arity specialization of {@link Function}.
         *
         * @param 
         *     the type of the first argument to the function
         * @param 
         *     the type of the second argument to the function
         * @param 
         *     the type of the third argument to the function
         * @param 
         *     the type of the fourth argument to the function
         * @param 
         *     the type of the fith argument to the function
         * @param 
         *     the type of the sixth argument to the function
         * @param 
         *     the type of the seventh argument to the function
         * @param 
         *     the type of the eighth argument to the function
         * @param 
         *     the type of the nineth argument to the function
         * @param 
         *     the type of the result of the function
         */
        @FunctionalInterface
        public interface Function9 {

            R apply(A a, B b, C c, D d, E e, F f, G g, H h, I i);
        }
    }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    /**
     * A parser that will consume from the input, but omit returning the result of the parse.
     * Useful when structure-defining tokens exist within the input, but are not needed as data in the parser output.
     */
    class SkipParser {

        private final Parser parser;

        public SkipParser(Parser parser) {
            this.parser = parser;
        }

        public SkipParser skip(final Parser that) {
            return new SkipParser(parserInput -> {
                parser.parse(parserInput);
                that.parse(parserInput);
                return UNIT;
            });
        }

        public  Parser thenParse(final Parser that) {
            return parserInput -> {
                parser.parse(parserInput);
                return that.parse(parserInput);
            };
        }
    }

    /**
     * A parser that attempts to match the input against a Java regular expression. Useful for building simple text
     * matching primatives, when the Parser Combinater interface can then compose into more complex matchers.
     */
    class RegexParser implements Parser {

        private final Pattern pattern;

        public RegexParser(final Pattern pattern) {
            this.pattern = pattern;
        }

        public MatchResult parse(final ParserInput parserInput) throws ParserFailure {
            final Matcher matcher = pattern.matcher(
                parserInput.getText().subSequence(parserInput.getOffset(),
                                                  parserInput.getText().length())
            );

            if (matcher.lookingAt()) {
                parserInput.setOffset(parserInput.getOffset() + matcher.end());
                return matcher.toMatchResult();
            }
            else {
                throw new ParserFailure("Regular Expression parser with pattern '" + pattern.pattern()
                                        + "' did not match the input text ( using matcher.lookingAt() )", parserInput);
            }
        }
    }

    /**
     * A Parser that attempts to parse a Float value.
     */
    class FloatParser implements Parser {

        private static final Pattern pattern = Pattern.compile("([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)");

        public static final Parser singleton = new FloatParser();

        /**
         * Marked private to encurage the use of singleton.
         */
        private FloatParser() {
            super();
        }

        public Float parse(final ParserInput parserInput) throws ParserFailure {
            final Matcher matcher = pattern.matcher(
                parserInput.getText().subSequence(parserInput.getOffset(),
                                                  parserInput.getText().length())
            );

            if (matcher.lookingAt()) {
                parserInput.setOffset(parserInput.getOffset() + matcher.end());
                final String regexResult = matcher.toMatchResult().group(1);
                try {
                    return Float.parseFloat(regexResult);
                }
                catch (NullPointerException e) {
                    throw new ParserFailure("Float parser parsed null float value from input.");
                }
                catch (NumberFormatException e) {
                    throw new ParserFailure(
                        "Float parser parsed invalid float value from input text of '" + regexResult + "'.");
                }
            }
            else {
                throw new ParserFailure("Float parser using regex pattern '" + pattern.pattern()
                                        + "' did not match the input text ( using matcher.lookingAt() )", parserInput);
            }
        }
    }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    /**
     * A simple marker class for representing parse failures.
     */
    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
    class ParserFailure extends Exception {

        public ParserFailure() {
            super();
        }

        public ParserFailure(final String message) {
            super(message);
        }

        public ParserFailure(final ParserInput parserInput) {
            super(formatParserInput(parserInput, Optional.empty()));
        }

        public ParserFailure(final String cause, final ParserInput parserInput) {
            super(formatParserInput(parserInput, Optional.of(cause)));
        }

        private static String formatParserInput(final ParserInput parserInput, Optional cause) {
            final CharSequence text = parserInput.getText();
            final int offset = parserInput.getOffset();
            final int length = (text.length() > (offset + 20)) ? offset + 20 : text.length();
            final StringBuilder s = new StringBuilder("Failed on input at '");
            if (text.length() > (offset + 20)) {
                s.append(text.subSequence(offset, offset + 20));
                s.append("...'");
            }
            else {
                s.append(text.subSequence(offset, text.length()));
                s.append("'");
            }

            if (cause.isPresent()) {
                s.append(" cause: ");
                s.append(cause.get());
            }

            return s.toString();
        }
    }

    /**
     * A simple state for holding a text source and an offset into the given text source.
     */
    class ParserInput {

        protected CharSequence text;
        protected int offset;

        public ParserInput(final CharSequence text, final int offset) {
            this.text = text;
            this.offset = offset;
        }

        public CharSequence getText() {
            return text;
        }

        public void setText(final CharSequence text) {
            this.text = text;
        }

        public int getOffset() {
            return offset;
        }

        public void setOffset(final int offset) {
            this.offset = offset;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }

            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            ParserInput that = (ParserInput) o;

            return Objects.equals(offset, that.offset) &&
                   Objects.equals(text, that.text);
        }

        @Override
        public int hashCode() {
            return Objects.hash(offset, text);
        }
    }


}