/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.parsers;

import com.github.fge.grappa.annotations.Cached;
import com.github.fge.grappa.annotations.DontExtend;
import com.github.fge.grappa.annotations.DontLabel;
import com.github.fge.grappa.annotations.SkipActionsInPredicates;
import com.github.fge.grappa.exceptions.InvalidGrammarException;
import com.github.fge.grappa.matchers.ActionMatcher;
import com.github.fge.grappa.matchers.AnyMatcher;
import com.github.fge.grappa.matchers.AnyOfMatcher;
import com.github.fge.grappa.matchers.CharIgnoreCaseMatcher;
import com.github.fge.grappa.matchers.CharMatcher;
import com.github.fge.grappa.matchers.CharRangeMatcher;
import com.github.fge.grappa.matchers.EmptyMatcher;
import com.github.fge.grappa.matchers.EndOfInputMatcher;
import com.github.fge.grappa.matchers.NothingMatcher;
import com.github.fge.grappa.matchers.RegexMatcher;
import com.github.fge.grappa.matchers.StringIgnoreCaseMatcher;
import com.github.fge.grappa.matchers.StringMatcher;
import com.github.fge.grappa.matchers.delegate.FirstOfMatcher;
import com.github.fge.grappa.matchers.delegate.OptionalMatcher;
import com.github.fge.grappa.matchers.delegate.SequenceMatcher;
import com.github.fge.grappa.matchers.join.JoinMatcherBootstrap;
import com.github.fge.grappa.matchers.join.JoinMatcherBuilder;
import com.github.fge.grappa.matchers.predicates.TestMatcher;
import com.github.fge.grappa.matchers.predicates.TestNotMatcher;
import com.github.fge.grappa.matchers.repeat.RepeatMatcherBuilder;
import com.github.fge.grappa.matchers.trie.CaseInsensitiveTrieMatcher;
import com.github.fge.grappa.matchers.trie.Trie;
import com.github.fge.grappa.matchers.trie.TrieBuilder;
import com.github.fge.grappa.matchers.trie.TrieMatcher;
import com.github.fge.grappa.matchers.trie.TrieNode;
import com.github.fge.grappa.matchers.unicode.CodePointMatcher;
import com.github.fge.grappa.matchers.unicode.CodePointRangeMatcher;
import com.github.fge.grappa.rules.Action;
import com.github.fge.grappa.rules.Rule;
import com.github.fge.grappa.support.Characters;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Base class of all parboiled parsers. Defines the basic rule creation methods.
 *
 * @param <V> the type of the parser values
 */
@ParametersAreNonnullByDefault
public abstract class BaseParser<V>
    extends BaseActions<V>
{
    /**
     * Match only at the end of the input
     */
    protected static final Rule EOI = new EndOfInputMatcher();

    /**
     * Matches any character.
     *
     *
Note that is is a Java {@code char} (ie, a UTF-16 code unit), not a
     * Unicode code point. If the latter is what you want, use {@link
     * #unicodeRange(int, int)}.

     */
    protected static final Rule ANY = new AnyMatcher();

    /**
     * Matches nothing and always succeeds.
     */
    protected static final Rule EMPTY = new EmptyMatcher();

    /**
     * Matches nothing and always fails.
     */
    protected static final Rule NOTHING = new NothingMatcher();

    /*
     * CORE RULES
     */

    /**
     * Match an empty string
     *
     * @return a rule
     */
    @DontExtend
    public Rule empty()
    {
        return EMPTY;
    }

    /**
     * Match only at the end of input
     *
     * @return a rule
     */
    @DontExtend
    public Rule eof()
    {
        return EOI;
    }

    /**
     * Match one given character
     *
     * @param c the character to match
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule ch(final char c)
    {
        return new CharMatcher(c);
    }

    /**
     * Match a given character in a case-insensitive manner
     *
     * @param c the character to match
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule ignoreCase(final char c)
    {
        return Character.isLowerCase(c) == Character.isUpperCase(c)
            ? ch(c) : new CharIgnoreCaseMatcher(c);
    }

    /**
     * Match one Unicode character
     *
     * @param codePoint the code point
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule unicodeChar(final int codePoint)
    {
        if (!Character.isValidCodePoint(codePoint))
            throw new InvalidGrammarException("invalid code point "
                + codePoint);
        return new CodePointMatcher(codePoint);
    }

    /**
     * Match a Unicode character range
     *
     *
Note that this method will delegate to "regular" character matchers if
     * part of, or all of, the specified range is into the basic multilingual
     * plane.

     *
     * @param low the lower code point (inclusive)
     * @param high the upper code point (inclusive)
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule unicodeRange(final int low, final int high)
    {
        if (!Character.isValidCodePoint(low))
            throw new InvalidGrammarException("invalid code point " + low);
        if (!Character.isValidCodePoint(high))
            throw new InvalidGrammarException("invalid code point " + high);
        if (low > high)
            throw new InvalidGrammarException("invalid code point range: "
                + low + " > " + high);
        return low == high ? new CodePointMatcher(low)
            : new CodePointRangeMatcher(low, high);
    }

    /**
     * Match an inclusive range of {@code char}s
     *
     * @param cLow the start char of the range (inclusively)
     * @param cHigh the end char of the range (inclusively)
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule charRange(final char cLow, final char cHigh)
    {
        return cLow == cHigh ? ch(cLow) : new CharRangeMatcher(cLow, cHigh);
    }

    /**
     * Match any of the characters in the given string
     *
     *
This method delegates to {@link #anyOf(Characters)}.

     *
     * @param characters the characters
     * @return a rule
     *
     * @see #anyOf(Characters)
     */
    @DontLabel
    public Rule anyOf(final String characters)
    {
        Objects.requireNonNull(characters);
        // TODO: see in this Characters class whether it is possible to wrap
        return anyOf(characters.toCharArray());
    }

    /**
     * Match any character in the given {@code char} array
     *
     *
This method delegates to {@link #anyOf(Characters)}.

     *
     * @param characters the characters
     * @return a rule
     *
     * @see #anyOf(Characters)
     */
    @DontLabel
    public Rule anyOf(final char[] characters)
    {
        Objects.requireNonNull(characters);
        switch (characters.length) {
            case 0:
                throw new IllegalArgumentException("empty character array");
            case 1:
                return ch(characters[0]);
            default:
                return anyOf(Characters.of(characters));
        }
    }

    /**
     * Match any given character among a set of characters
     *
     *
Both {@link #anyOf(char[])} and {@link #anyOf(String)} ultimately
     * delegate to this method, which caches its resuls.

     *
     * @param characters the characters
     * @return a new rule
     */
    @Cached
    @DontLabel
    public Rule anyOf(final Characters characters)
    {
        Objects.requireNonNull(characters);
        if (!characters.isSubtractive() && characters.getChars().length == 1)
            return ch(characters.getChars()[0]);
        if (characters.equals(Characters.NONE))
            return NOTHING;
        return new AnyOfMatcher(characters);
    }

    /**
     * Match any characters except the ones contained in the strings
     *
     * @param characters the characters
     * @return a rule
     */
    @DontLabel
    public Rule noneOf(final String characters)
    {
        Objects.requireNonNull(characters);
        return noneOf(characters.toCharArray());
    }

    /**
     * Match all characters except the ones in the {@code char} array
     * given as an argument
     *
     * @param characters the characters
     * @return a new rule
     */
    @DontLabel
    public Rule noneOf(final char[] characters)
    {
        Objects.requireNonNull(characters);
        Preconditions.checkArgument(characters.length > 0);

        return anyOf(Characters.allBut(characters));
    }

    /**
     * Match a string literal
     *
     * @param string the string to match
     * @return a rule
     */
    @DontLabel
    public Rule string(final String string)
    {
        Objects.requireNonNull(string);
        return string(string.toCharArray());
    }

    /**
     * Match a given set of characters as a string literal
     *
     * @param characters the characters of the string to match
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule string(final char... characters)
    {
        if (characters.length == 1)
            return ch(characters[0]); // optimize one-char strings
        return new StringMatcher(new String(characters));
    }

    /**
     * Match a string literal in a case insensitive manner
     *
     * @param string the string to match
     * @return a rule
     */
    @DontLabel
    public Rule ignoreCase(final String string)
    {
        Objects.requireNonNull(string);
        return ignoreCase(string.toCharArray());
    }

    /**
     * Match a sequence of characters as a string literal (case insensitive)
     *
     * @param characters the characters of the string to match
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule ignoreCase(final char... characters)
    {
        if (characters.length == 1)
            return ignoreCase(characters[0]); // optimize one-char strings
        return new StringIgnoreCaseMatcher(new String(characters));
    }

    /**
     * Match the input text using a Java regular expression
     *
     *
A few important considerations:

     *
     *

     *
the method used to match text is {@link Matcher#lookingAt()}; it
     *     means that the match will always be attempted at the current index in
     *     the input;

     *
no flags are passed to {@link Pattern#compile(String)}, therefore
     *     if you want case insensitive matches etc, you have to use the
     *     appropriate regex modifiers in the regex itself (for instance, {@code
     *     (?i)} for case insensitivity).

     *

     *
     * @param regex the regex
     * @return a rule
     *
     * @see Pattern
     * @see Matcher#lookingAt()
     * @see Matcher#end()
     */
    @Cached
    @DontLabel
    public Rule regex(final String regex)
    {
        return new RegexMatcher(regex);
    }

    /**
     * Match the longest possible string among a series of strings (case
     * sensitive)
     *
     *
As its name says, this rule will always try and match the longest
     * possible string among its arguments. It means that, for instance, if you
     * write:

     *
     *

     *     longestString("do", "double")
     *

     *
     *
then with an input of {@code doubles}, then {@code double} will be
     * matched; but if the input is {@code doubling} then {@code do} is matched.
     *

     *
     *
This method builds a {@link Collection} and delegates to {@link
     * #longestString(Collection)}.

     *
     * @param first the first string to match
     * @param second the second string to match
     * @param others the other strings to match, if any
     * @return a rule
     */
    public Rule longestString(final String first, final String second,
        final String... others)
    {
        return trie(first, second, others);
    }

    /**
     * Match the longest possible string among a series of strings (case
     * sensitive)
     *
     *
Duplicate elements will be silently eliminated.

     *
     *
As its name says, this rule will always try and match the longest
     * possible string among its arguments. It means that, for instance, if you
     * write:

     *
     *

     *     longestString("do", "double")
     *

     *
     *
then with an input of {@code doubles}, then {@code double} will be
     * matched; but if the input is {@code doubling} then {@code do} is matched.
     *

     *
     *
This method ultimately delegates to {@link #trie(Collection)}.

     *
     * @param strings the list of strings for this trie
     * @return a rule
     */
    public Rule longestString(final Collection<String> strings)
    {
        return trie(strings);
    }

    /**
     * Match the longest possible string among a series of strings (case
     * insensitive)
     *
     *
As its name says, this rule will always try and match the longest
     * possible string among its arguments. It means that, for instance, if you
     * write:

     *
     *

     *     longestStringIgnoreCase("do", "double")
     *

     *
     *
then with an input of {@code doubles}, then {@code double} will be
     * matched; but if the input is {@code doubling} then {@code do} is matched.
     *

     *
     *
This method builds a {@link Collection}, and delegates to
     * {@link #longestString(Collection)}.

     *
     * @param first the first string to match
     * @param second the second string to match
     * @param others the other strings to match, if any
     * @return a rule
     */
    public Rule longestStringIgnoreCase(final String first, final String second,
        final String... others)
    {
        return trieIgnoreCase(first, second, others);
    }

    /**
     * Match the longest possible string among a series of strings (case
     * insensitive)
     *
     *
Duplicate elements will be silently eliminated.

     *
     *
As its name says, this rule will always try and match the longest
     * possible string among its arguments. It means that, for instance, if you
     * write:

     *
     *

     *     longestStringIgnoreCase("do", "double")
     *

     *
     *
then with an input of {@code doubles}, then {@code double} will be
     * matched; but if the input is {@code doubling} then {@code do} is matched.
     *

     *
     *
This method ultimately delegates to {@link
     * #trieIgnoreCase(Collection)}.

     *
     * @param strings the list of strings for this trie
     * @return a rule
     */
    public Rule longestStringIgnoreCase(final Collection<String> strings)
    {
        return trieIgnoreCase(strings);
    }

    /**
     * Match one string among many using a trie
     *
     *
Duplicate elements will be silently eliminated.

     *
     *
Note that order of elements does not matter, and that this rule will
     * always trie (err, try) and match the longest possible sequence.
     * That is, if you build a rule with inputs "do" and "double" in this order
     * and the input text is "doubles", then "double" will be matched. However,
     * if the input text is "doubling" then "do" is matched instead.

     *
     *
Note also that the minimum length of strings in a trie is 1.

     *
     * @param strings the list of strings for this trie
     * @return a rule
     *
     * @see TrieMatcher
     * @see TrieNode
     */
    /*
     * TODO:
     *
     * - caching may not be that good of an idea...
     * - we may end up with only one string and that is a waste
     * - non BMP!
     */

    @Cached
    public Rule trie(final Collection<String> strings)
    {
        final List<String> list = ImmutableList.copyOf(strings);

        final TrieBuilder builder = Trie.newBuilder();

        list.forEach(builder::addWord);

        return new TrieMatcher(builder.build());
    }

    /**
     * Match one string among many using a trie
     *
     *
This method delegates to {@link #trie(Collection)}.

     *
     * @param first the first string
     * @param second the second string
     * @param others other strings
     * @return a rule
     *
     * @see TrieMatcher
     * @see TrieNode
     */
    public Rule trie(final String first, final String second,
        final String... others)
    {
        final List<String> words = ImmutableList.<String>builder().add(first)
            .add(second).add(others).build();

        return trie(words);
    }

    /**
     * Match one string among many using a trie, case
     * insensitive version
     *
     *
Duplicate elements will be silently eliminated.

     *
     *
Note that order of elements does not matter, and that this rule will
     * always trie (err, try) and match the longest possible sequence.
     * That is, if you build a rule with inputs "do" and "double" in this order
     * and the input text is "doubles", then "double" will be matched. However,
     * if the input text is "doubling" then "do" is matched instead.

     *
     *
Note also that the minimum length of strings in a trie is 1.

     *
     * @param strings the list of strings for this trie
     * @return a rule
     *
     * @see CaseInsensitiveTrieMatcher
     */
    @Cached
    public Rule trieIgnoreCase(final Collection<String> strings)
    {
        final List<String> list = ImmutableList.copyOf(strings);

        final TrieBuilder builder = Trie.newBuilder();

        list.forEach(builder::addWord);

        return new CaseInsensitiveTrieMatcher(builder.build());
    }

    /**
     * Match one string among many using a trie, case
     * insensitive
     *
     *
This method delegates to {@link #trieIgnoreCase(Collection)}.

     *
     * @param first the first string
     * @param second the second string
     * @param others other strings
     * @return a rule
     *
     * @see CaseInsensitiveTrieMatcher
     */
    public Rule trieIgnoreCase(final String first, final String second,
        final String... others)
    {
        final List<String> words = ImmutableList.<String>builder().add(first)
            .add(second).add(others).build();

        return trieIgnoreCase(words);
    }

    /*
     * "DELEGATING" RULES
     *
     * All rules below delegate to one or more other rules
     */

    /**
     * Match the first rule of a series of rules
     *
     *
When one rule matches, all others are ignored.

     *
     *
Note: if you are considering matching one string among many, consider
     * using {@link #trie(Collection)}/{@link #trie(String, String, String...)}
     * instead.

     *
     * @param rule the first subrule
     * @param rule2 the second subrule
     * @param moreRules the other subrules
     * @return a rule
     */
    @DontLabel
    public Rule firstOf(final Object rule, final Object rule2,
        final Object... moreRules)
    {
        Objects.requireNonNull(moreRules);
        final Object[] rules = ImmutableList.builder().add(rule).add(rule2)
            .add(moreRules).build().toArray();
        return firstOf(rules);
    }

    /**
     * Match the first rule of a series of rules
     *
     *
When one rule matches, all others are ignored.

     *
     * @param rules the subrules
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule firstOf(final Object[] rules)
    {
        Objects.requireNonNull(rules, "rules");
        if (rules.length == 1)
            return toRule(rules[0]);

        final Collection<String> strings = new ArrayList<>();

        for (final Object object: rules) {
            if (!(object instanceof String))
                return new FirstOfMatcher(toRules(rules));
            strings.add((String) object);
        }

        return trie(strings);
    }

    /**
     * Try and match a rule repeatedly, at least once
     *
     *
This is in fact an alias for {@code repeat(rule).min(1)}.

     *
     * @param rule the subrule
     * @return a rule
     *
     * @see #repeat(Object)
     */
    @DontLabel
    public Rule oneOrMore(final Object rule)
    {
        return repeat(rule).min(1);
    }

    /**
     * Try and repeatedly match a set of rules, at least once
     *
     *
This is in fact an alias for {@code repeat(rule, rule2, ...).min(1)}.
     *

     *
     * @param rule the first subrule
     * @param rule2 the second subrule
     * @param moreRules the other subrules
     * @return a rule
     *
     * @see #repeat(Object, Object, Object...)
     */
    @DontLabel
    public Rule oneOrMore(final Object rule, final Object rule2,
        final Object... moreRules)
    {
        return repeat(rule, rule2, moreRules).min(1);
    }

    /**
     * Try and match a rule zero or one time
     *
     *
This rule therefore always succeeds.

     *
     * @param rule the subrule
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule optional(final Object rule)
    {
        Objects.requireNonNull(rule);
        return new OptionalMatcher(toRule(rule));
    }

    /**
     * Try and match a given set of rules once
     *
     *
This rule will therefore never fail.

     *
     * @param rule the first subrule
     * @param rule2 the second subrule
     * @param moreRules the other subrules
     * @return a rule
     */
    @DontLabel
    public Rule optional(final Object rule, final Object rule2,
        final Object... moreRules)
    {
        Objects.requireNonNull(moreRules);
        return optional(sequence(rule, rule2, moreRules));
    }

    /**
     * Match a given set of rules, exactly once
     *
     * @param rule the first subrule
     * @param rule2 the second subrule
     * @param moreRules the other subrules
     * @return a rule
     */
    @DontLabel
    public Rule sequence(final Object rule, final Object rule2,
        final Object... moreRules)
    {
        final Object[] rules = ImmutableList.builder().add(rule).add(rule2)
            .add(moreRules).build().toArray();
        return sequence(rules);
    }

    /**
     * Match a given set of rules, exactly once
     *
     * @param rules the rules
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule sequence(final Object[] rules)
    {
        Objects.requireNonNull(rules);
        return rules.length == 1 ? toRule(rules[0])
            : new SequenceMatcher(toRules(rules));
    }

    /**
     * Kickstart a {@code join} rule
     *
     *
Usages:

     *
     *

     *     return join(rule()).using(otherRule()).times(n);
     *     return join(rule()).using(otherRule()).min(n);
     *

     *
     *
etc. See {@link JoinMatcherBuilder} for more possible constructs.

     *
     * @param rule the joined rule (must not match an empty sequence!)
     * @return a {@link JoinMatcherBootstrap}
     *
     * @see JoinMatcherBootstrap#using(Object)
     */
    @DontLabel
    public final JoinMatcherBootstrap<V, BaseParser<V>> join(final Object rule)
    {
        final Rule matcher = toRule(rule);
        return new JoinMatcherBootstrap<>(this, matcher);
    }

    /**
     * Kickstart a {@code join} rule
     *
     *
Like {@link #join(Object)}, except that several rules are accepted as
     * arguments.

     *
     * @param rule first rule
     * @param rule2 second rule
     * @param moreRules other rules
     * @return a {@link JoinMatcherBootstrap}
     *
     * @see #sequence(Object, Object, Object...)
     */
    @DontLabel
    public final JoinMatcherBootstrap<V, BaseParser<V>> join(final Object rule,
        final Object rule2, final Object... moreRules)
    {
        Objects.requireNonNull(moreRules);
        return join(sequence(rule, rule2, moreRules));
    }

    /**
     * Kickstart a repeat rule
     *
     *
Usages:

     *
     *

     *     return repeat("Yes we can!").times(3);
     *     return repeat(someRule).min(2);
     *     // etc
     *

     *
     * @param rule the rule to be repeated
     * @return a builder
     */
    @DontLabel
    public final RepeatMatcherBuilder<V> repeat(final Object rule)
    {
        return new RepeatMatcherBuilder<>(this, toRule(rule));
    }

    /**
     * Kickstart a repeat rule
     *
     *
Like {@link #repeat(Object)}, except several rules are accepted as
     * arguments.

     *
     * @param rule first rule
     * @param rule2 second rule
     * @param moreRules other rules
     * @return a rule
     *
     * @see #sequence(Object, Object, Object...)
     */
    @DontLabel
    public final RepeatMatcherBuilder<V> repeat(final Object rule,
        final Object rule2, final Object... moreRules)
    {
        Objects.requireNonNull(moreRules);
        return repeat(sequence(rule, rule2, moreRules));
    }

    /*
     * PREDICATES
     */

    /**
     * Test a rule, but do not consume any input (predicate)
     *
     *
Its success conditions are the same as the rule. Note that this rule
     * will never consume any input, nor will it create a parse tree node.

     *
     *
Note that the embedded rule can be arbitrarily complex, and this
     * includes potential {@link Action}s which can act on the stack for
     * instance; these will be executed here, unless you have chosen to
     * annotate your rule, or parser class, with {@link
     * SkipActionsInPredicates}.

     *
     * @param rule the subrule
     * @return a new rule
     */
    @Cached
    @DontLabel
    public Rule test(final Object rule)
    {
        final Rule subMatcher = toRule(rule);
        return new TestMatcher(subMatcher);
    }

    /**
     * Test a set of rules, but do not consume any input
     *
     * @param rule the first subrule
     * @param rule2 the second subrule
     * @param moreRules the other subrules
     * @return a new rule
     *
     * @see #test(Object)
     */
    @DontLabel
    public Rule test(final Object rule, final Object rule2,
        final Object... moreRules)
    {
        Objects.requireNonNull(moreRules);
        return test(sequence(rule, rule2, moreRules));
    }

    /**
     * Test, without consuming an input, that a rule does not match
     *
     *
The same warnings given in the description of {@link #test(Object)}
     * apply here.

     *
     * @param rule the subrule
     * @return a rule
     */
    @Cached
    @DontLabel
    public Rule testNot(final Object rule)
    {
        return new TestNotMatcher(toRule(rule));
    }

    /**
     * Test that a set of rules do not apply at this position
     *
     * @param rule the first subrule
     * @param rule2 the second subrule
     * @param moreRules the other subrules
     * @return a new rule
     *
     * @see #test(Object)
     * @see #testNot(Object)
     */
    @DontLabel
    public Rule testNot(final Object rule, final Object rule2,
        final Object... moreRules)
    {
        Objects.requireNonNull(moreRules);
        return testNot(sequence(rule, rule2, moreRules));
    }

    /**
     * Try and match a rule zero or more times
     *
     *
This is an alias for {@code repeat(rule).min(0)}.

     *
     * @param rule the subrule
     * @return a rule
     *
     * @see #repeat(Object)
     */
    @DontLabel
    public Rule zeroOrMore(final Object rule)
    {
        return repeat(rule).min(0);
    }

    /**
     * Try and match a set of rules zero or more times
     *
     *
This is an alias for {@code repeat(rule, rule2, ...).min(0)}.

     *
     * @param rule the first subrule
     * @param rule2 the second subrule
     * @param moreRules the other subrules
     * @return a rule
     */
    @DontLabel
    public Rule zeroOrMore(final Object rule, final Object rule2,
        final Object... moreRules)
    {
        return repeat(rule, rule2, moreRules).min(0);
    }

    /*
     * UTILITY RULES
     *
     *  All rules defined by RFC 5234, appendix B, section 1
     */

    /**
     * ALPHA as defined by RFC 5234, appendix B, section 1: ASCII letters
     *
     *
Therefore a-z, A-Z.

     *
     * @return a rule
     */
    public Rule alpha()
    {
        return firstOf(charRange('a', 'z'), charRange('A', 'Z'));
    }

    /**
     * BIT as defined by RFC 5234, appendix B, section 1: {@code 0} or {@code 1}
     *
     * @return a rule
     */
    public Rule bit()
    {
        return anyOf(Characters.of('0', '1'));
    }

    /**
     * CHAR as defined by RFC 5234, appendix B, section 1: ASCII, except NUL
     *
     *
That is, 0x01 to 0x7f.

     *
     * @return a rule
     */
    public Rule asciiChars()
    {
        return charRange((char) 0x01, (char) 0x7f);
    }

    /**
     * CR as defined by RFC 5234, appendix B, section 1 ({@code \r})
     *
     * @return a rule
     */
    public Rule cr()
    {
        return ch('\r');
    }

    /**
     * CRLF as defined by RFC 5234, appendix B, section 1 ({@code \r\n}
     *
     * @return a rule
     */
    public Rule crlf()
    {
        return string("\r\n");
    }

    /**
     * CTL as defined by RFC 5234, appendix B, section 1: control characters
     *
     *
0x00-0x1f, plus 0x7f.

     *
     * @return a rule
     */
    public Rule ctl()
    {
        return firstOf(charRange((char) 0x00, (char) 0x1f), ch((char) 0x7f));
    }

    /**
     * DIGIT as defined by RFC 5234, appendix B, section 1 (0 to 9)
     *
     * @return a rule
     */
    public Rule digit()
    {
        return charRange('0', '9');
    }

    /**
     * DQUOTE as defined by RFC 5234, appendix B, section 1 {@code "}
     *
     * @return a rule
     */
    public Rule dquote()
    {
        return ch('"');
    }

    /**
     * Hexadecimal digits, case insensitive
     *
     *
Note: RFC 5234 only defines {@code HEXDIG} for uppercase
     * letters ({@code A} to {@code F}). Use {@link #hexDigitUpperCase()} for
     * this definition. Use {@link #hexDigitLowerCase()} for lowercase letters
     * only.

     *
     * @return a rule
     */
    public Rule hexDigit()
    {
        return anyOf("ABCDEFabcdef0123456789");
    }

    /**
     * Hexadecimal digits, uppercase
     *
     * @return a rule
     * @see #hexDigit()
     */
    public Rule hexDigitUpperCase()
    {
        return anyOf("ABCDEF0123456789");
    }

    /**
     * Hexadecimal digits, lowercase
     *
     * @return a rule
     * @see #hexDigit()
     */
    public Rule hexDigitLowerCase()
    {
        return anyOf("abcdef0123456789");
    }

    /**
     * HTAB as defined by RFC 5234, appendix B, section 1 ({@code \t})
     *
     * @return a rule
     */
    public Rule hTab()
    {
        return ch('\t');
    }

    /**
     * LF as defined by RFC 5234, appendix B, section 1 ({@code \n})
     *
     * @return a rule
     */
    public Rule lf()
    {
        return ch('\n');
    }

    /**
     * OCTET as defined by RFC 5234, appendix B, section 1 (0x00 to 0xff)
     *
     * @return a rule
     */
    public Rule octet()
    {
        return charRange((char) 0x00, (char) 0xff);
    }

    /**
     * SP as defined by RFC 5234, appendix B, section 1 (one space, 0x20)
     *
     * @return a rule
     */
    public Rule sp()
    {
        return ch(' ');
    }

    /**
     * VCHAR as defined by RFC 5234, appendix B, section 1: ASCII "visible"
     *
     *
Letters, {@code @}, etc etc. Note that this excludes
     * whitespace characters!

     *
     * @return a rule
     */
    public Rule vchar()
    {
        return charRange((char) 0x21, (char) 0x7e);
    }

    /**
     * WSP as defined by RFC 5234, appendix B, section 1: space or tab
     *
     * @return a rule
     */
    public Rule wsp()
    {
        return anyOf(" \t");
    }

    ///************************* "MAGIC" METHODS ***************************///

    /**
     * Explicitly marks the wrapped expression as an action expression.
     *
     *
Grappa transforms the wrapped expression into an {@link Action}
     * instance during parser construction

     *
     * @param expression the expression to turn into an Action
     * @return the Action wrapping the given expression
     */
    public static <T> Action<T> ACTION(final boolean expression)
    {
        throw new UnsupportedOperationException("ACTION(...) calls can only be"
            + " used in Rule creating parser methods");
    }

    ///************************* HELPER METHODS ***************************///

    /**
     * Convert a character literal to a parser rule
     *
     * @param c the character
     * @return the rule
     */
    @DontExtend
    protected Rule fromCharLiteral(final char c)
    {
        return ch(c);
    }

    /**
     * Convert a string literal to a parser rule
     *
     * @param string the string
     * @return the rule
     */
    @DontExtend
    protected Rule fromStringLiteral(final String string)
    {
        Objects.requireNonNull(string);
        return fromCharArray(string.toCharArray());
    }

    /**
     * Convert a char array to a parser rule
     *
     * @param array the char array
     * @return the rule
     */
    @DontExtend
    protected Rule fromCharArray(final char[] array)
    {
        Objects.requireNonNull(array);
        return string(array);
    }

    /**
     * Convert the given object array to an array of rules
     *
     * @param objects the objects to convert
     * @return the rules corresponding to the given objects
     */
    @DontExtend
    public Rule[] toRules(final Object... objects)
    {
        return Arrays.stream(objects).map(this::toRule).toArray(Rule[]::new);
    }

    /**
     * Converts the given object to a rule
     *
     * @param obj the object to convert
     * @return the rule corresponding to the given object
     */
    @DontExtend
    public Rule toRule(final Object obj)
    {
        Objects.requireNonNull(obj);
        if (obj instanceof Rule)
            return (Rule) obj;
        if (obj instanceof Character)
            return fromCharLiteral((Character) obj);
        if (obj instanceof String)
            return fromStringLiteral((String) obj);
        if (obj instanceof char[])
            return fromCharArray((char[]) obj);
        if (obj instanceof Action) {
            final Action<?> action = (Action<?>) obj;
            return new ActionMatcher(action);
        }

        final String errmsg = obj instanceof  Boolean
            ? "unwrapped Boolean value in rule (wrap it with ACTION())"
            : "'" + obj + "' cannot be automatically converted to a rule";
        throw new InvalidGrammarException(errmsg);
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.parsers;

import com.github.fge.grappa.buffers.InputBuffer;
import com.github.fge.grappa.exceptions.InvalidGrammarException;
import com.github.fge.grappa.rules.Action;
import com.github.fge.grappa.run.context.Context;
import com.github.fge.grappa.run.context.ContextAware;
import com.github.fge.grappa.stack.ValueStack;
import com.github.fge.grappa.support.IndexRange;
import com.github.fge.grappa.support.Position;

import javax.annotation.Nonnull;
import java.util.Objects;

/**
 * The basic class for all {@link ContextAware} implementations.
 *
 *
This class is used by all basic parsers which you may extend ({@link
 * BaseParser}, {@link EventBusParser}), but also by all of a parser's {@link
 * Action actions} (that is, any boolean expressions in rules, or any methods in
 * a parser returning a boolean).

 *
 * @param <V> parameter type of the values on the parser stack
 */
public abstract class BaseActions<V>
    implements ContextAware<V>
{
    @SuppressWarnings("InstanceVariableMayNotBeInitialized")
    private Context<V> context;

    /**
     * The current context for use with action methods. Updated immediately
     * before action calls.
     *
     * @return the current context
     */
    public final Context<V> getContext()
    {
        return context;
    }

    /**
     * ContextAware interface implementation.
     *
     * @param context the context
     */
    @Override
    public final void setContext(final Context<V> context)
    {
        this.context = Objects.requireNonNull(context, "context");
    }

    /**
     * Returns the current index in the input buffer.
     *
     * @return the current index
     */
    public final int currentIndex()
    {
        check();
        return context.getCurrentIndex();
    }

    /**
     * Return the input text matched by the immediately preceding rule
     *
     *
This call can only be used in actions (or any implementation of {@link
     * ContextAware} that are part of a sequence rule and are not at first
     * position in this sequence.

     *
     * @return the input text matched by the immediately previous rule
     */
    public String match()
    {
        check();
        return context.getMatch();
    }

    /**
     * Returns the range covering the input text matched by the immediately
     * preceding rule
     *
     *
This call can only be used in actions (or any implementation of {@link
     * ContextAware} that are part of a sequence rule and are not at first
     * position in this sequence.

     *
     * @return a new IndexRange instance
     */
    public IndexRange matchRange()
    {
        check();
        return context.getMatchRange();
    }

    /**
     * Returns the start index of the immediately preceding rule's context
     *
     *
This call can only be used in actions (or any implementation of {@link
     * ContextAware} that are part of a sequence rule and are not at first
     * position in this sequence.

     *
     * @return see description
     */
    public int matchStart()
    {
        check();
        return context.getMatchStartIndex();
    }

    /**
     * Returns the end index of the immediately preceding rule's context
     *
     *
Note that the index is exclusive; that is, it will point to the next
     * character in the buffer (possibly past the end of the sequence if the
     * previous rule matched until the end of the input).

     *
     *
This call can only be used in actions (or any implementation of {@link
     * ContextAware} that are part of a sequence rule and are not at first
     * position in this sequence.

     *
     * @return see description
     */
    public int matchEnd()
    {
        check();
        return context.getMatchEndIndex();
    }

    /**
     * Returns the current position in the underlying {@link InputBuffer} as a
     * {@link Position}
     *
     * @return the current position in the underlying inputbuffer
     */
    public Position position()
    {
        check();
        return context.getPosition();
    }

    /**
     * Pushes the given value onto the value stack
     *
     *
Equivalent to {@code push(0, value)}.
     *
     * @param value the value to push
     * @return true
     */
    public boolean push(final V value)
    {
        check();
        context.getValueStack().push(value);
        return true;
    }

    /**
     * Inserts the given value a given number of elements below the current top
     * of the value stack
     *
     * @param down the number of elements to skip before inserting the value (0
     * being equivalent to {@code push(value)})
     * @param value the value
     * @return true
     *
     * @throws IllegalArgumentException the stack does not contain enough
     * elements to perform this operation
     */
    public boolean push(final int down, final V value)
    {
        check();
        context.getValueStack().push(down, value);
        return true;
    }

    /**
     * Removes the value at the top of the value stack and returns it
     *
     * @return the current top value
     *
     * @throws IllegalArgumentException the stack is empty
     */
    public V pop()
    {
        check();
        return context.getValueStack().pop();
    }

    /**
     * Removes the value at the top of the value stack and casts it before
     * returning it
     *
     * @param c the class to cast to
     * @param <E> type of the class
     * @return the current top value
     * @throws IllegalArgumentException the stack is empty
     *
     * @see #pop()
     */
    public <E extends V> E popAs(@Nonnull final Class<E> c)
    {
        return c.cast(pop());
    }

    /**
     * Removes the value the given number of elements below the top of the value
     * stack and returns it
     *
     * @param down the number of elements to skip before removing the value (0
     * being equivalent to {@code pop()})
     * @return the value
     *
     * @throws IllegalArgumentException the stack does not contain enough
     * elements to perform this operation
     */
    public V pop(final int down)
    {
        check();
        return context.getValueStack().pop(down);
    }

    /**
     * Removes the value the given number of elements below the top of the value
     * stack and casts it before returning it
     *
     * @param c the class to cast to
     * @param down the number of elements to skip before removing the value (0
     * being equivalent to {@code pop()})
     * @param <E> type of the class
     * @return the value
     * @throws IllegalArgumentException the stack does not contain enough
     * elements to perform this operation
     *
     * @see #pop(int)
     */
    public <E extends V> E popAs(final Class<E> c, final int down)
    {
        return c.cast(pop(down));
    }

    /**
     * Removes the value at the top of the value stack
     *
     * @return true
     *
     * @throws IllegalArgumentException the stack is empty
     */
    public boolean drop()
    {
        check();
        context.getValueStack().pop();
        return true;
    }

    /**
     * Removes the value the given number of elements below the top of the value
     * stack
     *
     * @param down the number of elements to skip before removing the value (0
     * being equivalent to {@code drop()})
     * @return true
     *
     * @throws IllegalArgumentException the stack does not contain enough
     * elements to perform this operation
     */
    public boolean drop(final int down)
    {
        check();
        context.getValueStack().pop(down);
        return true;
    }

    /**
     * Returns the value at the top of the value stack without removing it
     *
     * @return the current top value
     *
     * @throws IllegalArgumentException if the stack is empty
     */
    public V peek()
    {
        check();
        return context.getValueStack().peek();
    }

    /**
     * Returns and casts the value at the top of the value stack without
     * removing it
     *
     * @param c the class to cast to
     * @param <E> type of the class
     * @return the value
     *
     * @see #peek()
     */
    public <E extends V> E peekAs(final Class<E> c)
    {
        return c.cast(peek());
    }

    /**
     * Returns the value the given number of elements below the top of the value
     * stack without removing it
     *
     * @param down the number of elements to skip (0 being equivalent to {@code
     * peek()})
     * @return the value
     *
     * @throws IllegalArgumentException the stack does not contain enough
     * elements to perform this operation
     */
    public V peek(final int down)
    {
        check();
        return context.getValueStack().peek(down);
    }

    /**
     * Returns and casts the value the given number of elements below the top
     * of the value stack without removing it.
     *
     * @param c the class to cast to
     * @param down the number of elements to skip (0 being equivalent to {@code
     * peek()})
     * @param <E> type of the class
     * @return the value
     *
     * @see #peek(int)
     */
    public <E extends V> E peekAs(final Class<E> c, final int down)
    {
        return c.cast(peek(down));
    }

    /**
     * Replaces the current top value of the value stack with the given value
     *
     *
Equivalent to {@code poke(0, value)}.
     *
     * @param value the value
     * @return true
     *
     * @throws IllegalArgumentException the stack is empty
     */
    public boolean poke(final V value)
    {
        check();
        context.getValueStack().poke(value);
        return true;
    }

    /**
     * Replaces the element the given number of elements below the current top
     * of the value stack
     *
     * @param down the number of elements to skip before replacing the value (0
     * being equivalent to {@code poke(value)})
     * @param value the value to replace with
     * @return true
     *
     * @throws IllegalArgumentException the stack does not contain enough
     * elements to perform this operation
     */
    public boolean poke(final int down, final V value)
    {
        check();
        context.getValueStack().poke(down, value);
        return true;
    }

    /**
     * Duplicates the top value of the value stack
     *
     * @return true
     *
     * @throws IllegalArgumentException the stack is empty
     */
    public boolean dup()
    {
        check();
        context.getValueStack().dup();
        return true;
    }

    /**
     * Swaps the top two elements of the value stack.
     *
     * @return true
     *
     * @throws IllegalArgumentException the stack does not contain at least
     * two elements
     */
    public boolean swap()
    {
        check();
        context.getValueStack().swap();
        return true;
    }

    /**
     * Reverse the order of the top n elements of this context's value stack
     *
     * @param n the number of elements to swap
     * @return always true
     * @throws IllegalArgumentException stack does not contain enough elements
     *
     * @see ValueStack#swap(int)
     */
    public boolean swap(final int n)
    {
        check();
        context.getValueStack().swap(n);
        return true;
    }

    /**
     * Check whether the end of input has been reached by the current context
     *
     * @return true if the end of the input has been reached
     */
    public boolean atEnd()
    {
        check();
        return context.atEnd();
    }

    /**
     * Returns the next input character about to be matched
     *
     *
If you use this method, you MUST first check whether you have reached
     * the end of the buffer using {@link #atEnd()}.

     *
     * @return the next input character about to be matched
     */
    public Character currentChar()
    {
        check();
        return context.getCurrentChar();
    }

    /**
     * Check whether the current context is within a predicate ({@code test()}
     * or {@code testNot()})
     *
     *
Useful for example for making sure actions are not run inside of a
     * predicate evaluation:

     *
     *

     *     return sequence(inPredicate() || someActionHere());
     *

     *
     * @return true if in a predicate
     */
    public boolean inPredicate()
    {
        check();
        return context.inPredicate();
    }

    /**
     * Check whether the current context has recorded a parse error
     *
     * @return true if either the current rule or a sub rule has recorded a
     * parse error
     */
    public boolean hasError()
    {
        check();
        return context.hasError();
    }

    // TODO: pain point here
    private void check()
    {
        if (context == null || context.getMatcher() == null)
            throw new InvalidGrammarException("rule has an unwrapped action"
                + " expression");
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.run;

import com.github.fge.grappa.Grappa;
import com.github.fge.grappa.buffers.CharSequenceInputBuffer;
import com.github.fge.grappa.buffers.InputBuffer;
import com.github.fge.grappa.exceptions.GrappaException;
import com.github.fge.grappa.internal.NonFinalForTesting;
import com.github.fge.grappa.matchers.base.Matcher;
import com.github.fge.grappa.rules.Rule;
import com.github.fge.grappa.run.context.DefaultMatcherContext;
import com.github.fge.grappa.run.context.MatcherContext;
import com.github.fge.grappa.run.events.MatchContextEvent;
import com.github.fge.grappa.run.events.MatchFailureEvent;
import com.github.fge.grappa.run.events.MatchSuccessEvent;
import com.github.fge.grappa.run.events.PostParseEvent;
import com.github.fge.grappa.run.events.PreMatchEvent;
import com.github.fge.grappa.run.events.PreParseEvent;
import com.github.fge.grappa.stack.ArrayValueStack;
import com.github.fge.grappa.stack.ValueStack;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.EventBus;

import javax.annotation.Nonnull;
import java.util.Objects;

/**
 * Class to run a parser on an input, and retrieve a result
 *
 *
This is the class which is used when you want to actually run an instance
 * of a parser (created using {@link Grappa#createParser(Class, Object...)})
 * against a text input.

 *
 *
The basic text input is a {@link CharSequence}; since {@link String}
 * implements this interface, this is the most direct way to run a parser.
 * If you wish to, however, you may implement your own {@link InputBuffer} and
 * pass is as an alternative.

 *
 *
You can also register a number of listeners implementing {@link
 * ParseEventListener}. Such listeners have the ability to listen to the parse
 * events of your choice (before parsing, before matching, match failure
 * or success, after parsing), further extending the use of grappa.

 *
 * @param <V> type parameter of the parser's stack values
 *
 * @see ParseEventListener
 */
@NonFinalForTesting
public class ParseRunner<V>
    implements MatchHandler
{
    // TODO: does it need to be volatile?
    private volatile Throwable throwable = null;

    private final EventBus bus = new EventBus((exception, context) -> {
        if (throwable == null)
            throwable = exception;
        else
            throwable.addSuppressed(exception);
    });

    protected final Matcher rootMatcher;
    protected ValueStack<V> valueStack;
    protected Object stackSnapshot;

    /**
     * Constructor
     *
     * @param rule the rule
     */
    public ParseRunner(@Nonnull final Rule rule)
    {
        rootMatcher = Objects.requireNonNull((Matcher) rule, "rule");
    }

    public final ParsingResult<V> run(final CharSequence input)
    {
        Objects.requireNonNull(input, "input");
        return run(new CharSequenceInputBuffer(input));
    }

    public final ParsingResult<V> run(final InputBuffer inputBuffer)
    {
        Objects.requireNonNull(inputBuffer, "inputBuffer");
        resetValueStack();

        final MatcherContext<V> context = createRootContext(inputBuffer, this);
        bus.post(new PreParseEvent<>(context));

        if (throwable != null)
            throw new GrappaException("parsing listener error (before parse)",
                throwable);

        final boolean matched = context.runMatcher();
        final ParsingResult<V> result
            = createParsingResult(matched, context);

        bus.post(new PostParseEvent<>(result));

        if (throwable != null)
            throw new GrappaException("parsing listener error (after parse)",
                throwable);

        return result;
    }

    private void resetValueStack()
    {
        // TODO: write a "memoizing" API
        valueStack = new ArrayValueStack<>();
        stackSnapshot = null;
    }

    @VisibleForTesting
    MatcherContext<V> createRootContext(
        final InputBuffer inputBuffer, final MatchHandler matchHandler)
    {
        return new DefaultMatcherContext<>(inputBuffer, valueStack,
            matchHandler, rootMatcher);
    }

    @VisibleForTesting
    ParsingResult<V> createParsingResult(final boolean matched,
        final MatcherContext<V> context)
    {
        return new ParsingResult<>(matched, valueStack, context);
    }

    public final void registerListener(final ParseEventListener<V> listener)
    {
        bus.register(listener);
    }

    /**
     * Internal method. DO NOT USE!
     *
     * @param context the MatcherContext
     * @param <T> type parameter of the values on the parser stack
     * @return true on a match; false otherwise
     */
    @Override
    public <T> boolean match(final MatcherContext<T> context)
    {
        final Matcher matcher = context.getMatcher();

        final PreMatchEvent<T> preMatchEvent = new PreMatchEvent<>(context);
        bus.post(preMatchEvent);

        if (throwable != null)
            throw new GrappaException("parsing listener error (before match)",
                throwable);

        // FIXME: is there any case at all where context.getMatcher() is null?
        @SuppressWarnings("ConstantConditions")
        final boolean match = matcher.match(context);

        final MatchContextEvent<T> postMatchEvent = match
            ? new MatchSuccessEvent<>(context)
            : new MatchFailureEvent<>(context);

        bus.post(postMatchEvent);

        if (throwable != null)
            throw new GrappaException("parsing listener error (after match)",
                throwable);

        return match;
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.support;

import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;

/**
 * A simple immutable container for a range of indices into an underlying
 * InputBuffer.
 */
public final class IndexRange
{
    /**
     * The index of the first character in the range.
     */
    public final int start;

    /**
     * The index of the character following the last character of the range.
     */
    public final int end;

    public IndexRange(final int start, final int end)
    {
        Preconditions.checkArgument(start >= 0, "start must be >= 0");
        Preconditions.checkArgument(end >= start, "end must be >= start");
        this.start = start;
        this.end = end;
    }

    /**
     * Determines whether this range contains no characters.
     *
     * @return true if the end matches the start of the range.
     */
    public boolean isEmpty()
    {
        return start == end;
    }

    /**
     * @return the number of characters covered by this range
     */
    public int length()
    {
        return end - start;
    }

    /**
     * Determines whether this range overlaps with the given other one.
     *
     * @param other the other range
     * @return true if there is at least one index that is contained in both ranges
     */
    public boolean overlapsWith(final IndexRange other)
    {
        Objects.requireNonNull(other, "other");
        return end > other.start && other.end > start;
    }

    /**
     * Determines whether this range immediated follows the given other one.
     *
     * @param other the other range
     * @return true if this range immediated follows the given other one
     */
    public boolean isPrecededBy(final IndexRange other)
    {
        Objects.requireNonNull(other, "other");
        return other.end == start;
    }

    /**
     * Determines whether this range is immediated followed by the given other one.
     *
     * @param other the other range
     * @return true if this range is immediated followed by the given other one
     */
    public boolean isFollowedBy(final IndexRange other)
    {
        Objects.requireNonNull(other, "other");
        return end == other.start;
    }

    /**
     * Determines whether this range immediated follows or precedes the given other one.
     *
     * @param other the other range
     * @return true if this range immediated follows or precedes the given other one.
     */
    public boolean touches(final IndexRange other)
    {
        Objects.requireNonNull(other, "other");
        return other.end == start || end == other.start;
    }

    /**
     * Created a new IndexRange that spans all characters between the smallest and the highest index of the two ranges.
     *
     * @param other the other range
     * @return a new IndexRange instance
     */
    public IndexRange mergedWith(final IndexRange other)
    {
        Objects.requireNonNull(other, "other");
        return new IndexRange(Math.min(start, other.start),
            Math.max(end, other.end));
    }

    @Override
    public boolean equals(@Nullable final Object obj)
    {
        if (this == obj)
            return true;
        if (!(obj instanceof IndexRange))
            return false;
        final IndexRange that = (IndexRange) obj;
        return end == that.end && start == that.start;
    }

    @Override
    public int hashCode()
    {
        return start ^ end;
    }

    @Nonnull
    @Override
    public String toString()
    {
        return MoreObjects.toStringHelper(this)
            .add("start", start)
            .add("end", end)
            .toString();
    }
}


/*
 * Copyright (C) 2014 Francis Galiegue <fgaliegue@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.matchers.join;

import com.github.fge.grappa.exceptions.GrappaException;
import com.github.fge.grappa.matchers.MatcherType;
import com.github.fge.grappa.matchers.base.CustomDefaultLabelMatcher;
import com.github.fge.grappa.matchers.base.Matcher;
import com.github.fge.grappa.rules.Rule;
import com.github.fge.grappa.run.context.MatcherContext;

/**
 * A joining matcher
 *
 *
Such a matcher has two submatchers: a "joined" matcher and a "joining"
 * matcher.

 *
 *
This matcher will run cycles through both of these; the first will be a
 * run of the "joined" matcher, subsequent cycles will be ("joining", "joined").
 *

 *
 *
Therefore:

 *
 *

 *
one cycle is {@code joined};

 *
two cycles is {@code joined, joining, joined};

 *
etc etc.

 *

 *
 *
This matcher will correctly reset the index to the last successful
 * match; for instance, if the match sequence is {@code joined, joining, joined,
 * joining} and two cycles are enough, it will reset the index to before the
 * last {@code joining} so that subsequent matchers can proceed from there.

 *
 *
It is forbidden for the "joining" matcher to match an
 * empty sequence. Unfortunately, due to current limitations, this can only
 * be detected at runtime.

 *
 *
This matcher is not built directly; its build is initiated by a {@link
 * JoinMatcherBootstrap}. Example:

 *
 *

 *     Rule threeDigitsExactly()
 *     {
 *         return join(digit()).using('.').times(3);
 *     }
 *

 *
 * @see JoinMatcherBootstrap
 */
public abstract class JoinMatcher
    extends CustomDefaultLabelMatcher<JoinMatcher>
{
    private static final int JOINED_CHILD_INDEX = 0;
    private static final int JOINING_CHILD_INDEX = 1;

    protected final Matcher joined;
    protected final Matcher joining;

    protected JoinMatcher(final Rule joined, final Rule joining)
    {
        super(new Rule[] { joined, joining }, "join");
        this.joined = getChildren().get(JOINED_CHILD_INDEX);
        this.joining = getChildren().get(JOINING_CHILD_INDEX);
    }

    @Override
    public final MatcherType getType()
    {
        return MatcherType.COMPOSITE;
    }

    /**
     * Tries a match on the given MatcherContext.
     *
     * @param context the MatcherContext
     * @return true if the match was successful
     */
    @Override
    public final <V> boolean match(final MatcherContext<V> context)
    {
        /*
         * TODO! Check logic
         *
         * At this point, if we have enough cycles, we can't determined whether
         * our joining rule would match empty... Which is illegal.
         */
        int cycles = 0;
        if (!joined.getSubContext(context).runMatcher())
            return enoughCycles(cycles);

        cycles++;

        Object snapshot = context.getValueStack().takeSnapshot();
        int beforeCycle = context.getCurrentIndex();

        while (runAgain(cycles) && matchCycle(context, beforeCycle)) {
            beforeCycle = context.getCurrentIndex();
            snapshot = context.getValueStack().takeSnapshot();
            cycles++;
        }

        context.getValueStack().restoreSnapshot(snapshot);
        context.setCurrentIndex(beforeCycle);

        return enoughCycles(cycles);
    }

    protected abstract boolean runAgain(final int cycles);

    protected abstract boolean enoughCycles(final int cycles);

    protected final <V> boolean matchCycle(final MatcherContext<V> context,
        final int beforeCycle)
    {
        if (!joining.getSubContext(context).runMatcher())
            return false;
        if (context.getCurrentIndex() == beforeCycle)
            throw new GrappaException("joining rule (" + joining + ") of a "
                + "JoinMatcher cannot match an empty character sequence!");
        return joined.getSubContext(context).runMatcher();
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa;

import com.github.fge.grappa.exceptions.InvalidGrammarException;
import com.github.fge.grappa.parsers.BaseParser;
import com.github.fge.grappa.run.ParseRunner;
import com.github.fge.grappa.transform.ParserTransformer;

import javax.annotation.Nonnull;
import java.lang.reflect.Constructor;
import java.util.Objects;

/**
 * Main class providing the high-level entry point into the parboiled library.
 */
public final class Grappa
{
    private Grappa()
    {
        throw new Error("nice try!");
    }

    /**
     *
Creates a parser object whose rule creation methods can then be used
     * with one of the {@link ParseRunner} implementations.

     *
Since parboiled needs to extend your parser with certain extra logic
     * (e.g. to prevent infinite recursions in recursive rule definitions) you
     * cannot create your parser object yourself, but have to go through this
     * method. Also your parser class has to be derived from {@link BaseParser}.
     * If you want to use a non-default constructor you can provide its
     * arguments to this method. Make sure your non-default constructor does not
     * use primitive type parameters (like "int") but rather their boxed
     * counterparts (like "Integer"), otherwise the constructor will not be
     * found.

     *
Performing the rule analysis and extending the parser class is an
     * expensive process (time-wise) and can take up to several hundred
     * milliseconds for large grammars. However, this cost is only incurred once
     * per parser class and class loader. Subsequent calls to this method are
     * therefore fast once the initial extension has been performed.

     *
     * @param parserClass the type of the parser to create
     * @param constructorArgs optional arguments to the parser class constructor
     * @return the ready to use parser instance
     */
    public static <P extends BaseParser<V>, V> P createParser(
        @Nonnull final Class
 parserClass, final Object... constructorArgs)
    {
        Objects.requireNonNull(parserClass, "parserClass");
        try {
            final Class<?> extendedClass
                = ParserTransformer.transformParser(parserClass);
            final Constructor<?> constructor
                = findConstructor(extendedClass, constructorArgs);
            @SuppressWarnings("unchecked")
            final P ret = (P) constructor.newInstance(constructorArgs);
            return ret;
        } catch (Exception e) { // TODO: catch better than Exception
            throw new RuntimeException(
                "Error creating extended parser class: " + e.getMessage(), e);
        }
    }

    /**
     * Generate the byte code of a transformed parser class
     *
     *
When you create a parser using {@link
     * #createParser(Class, Object...)}, Parboiled generates a new class which
     * is the one you actually use for parsing. This method allows to get the
     * byte code of such a generated class in a byte array.

     *
     * @param parserClass the parser class
     * @param
 class of the parser
     * @param <V> see {@link BaseParser}
     * @return the byte code
     *
     * @throws RuntimeException byte code generation failure
     * @see ParserTransformer#getByteCode(Class)
     */
    public static <P extends BaseParser<V>, V> byte[] getByteCode(
        final Class
 parserClass)
    {
        try {
            return ParserTransformer.getByteCode(parserClass);
        } catch (Exception e) {
            throw new RuntimeException("failed to generate byte code", e);
        }
    }

    private static Constructor<?> findConstructor(final Class<?> c,
        final Object[] arguments)
    {
        Class<?>[] paramTypes;
        Object argument;

outer:
        for (final Constructor<?> constructor : c.getConstructors()) {
            paramTypes = constructor.getParameterTypes();
            if (paramTypes.length != arguments.length)
                continue;
            for (int i = 0; i < arguments.length; i++) {
                argument = arguments[i];
                if (argument != null && !paramTypes[i]
                    .isAssignableFrom(argument.getClass()))
                    continue outer;
                if (argument == null && paramTypes[i].isPrimitive())
                    continue outer;
            }
            return constructor;
        }
        throw new InvalidGrammarException("No constructor found for " + c
            + " and the given " + arguments.length+ " arguments");
    }
}


/*
 * Copyright (C) 2014 Francis Galiegue <fgaliegue@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.matchers.trie;

import java.nio.CharBuffer;
import java.util.Map;
import java.util.TreeMap;

/**
 * A builder for a {@link TrieNode} tree
 *
 *
The tree is built "in depth"; each character of a string will create a new
 * builder unless there is already a builder for that character.

 *
 *
When {@link #build()} is called, the whole tree is built from the leaves
 * up to the root.

 *
 * @since 1.0.0-beta.6
 */
public final class TrieNodeBuilder
{
    private boolean fullWord = false;

    private final Map<Character, TrieNodeBuilder> subnodes = new TreeMap<>();

    public TrieNodeBuilder addWord(final String word)
    {
        doAddWord(CharBuffer.wrap(word));
        return this;
    }

    /**
     * Add a word
     *
     *
Here also, a {@link CharBuffer} is used, which changes position as we
     * progress into building the tree, character by character, node by node.
     *

     *
     *
If the buffer is "empty" when entering this method, it means a match
     * must be recorded (see {@link #fullWord}).

     *
     * @param buffer the buffer (never null)
     */
    private void doAddWord(final CharBuffer buffer)
    {
        if (!buffer.hasRemaining()) {
            fullWord = true;
            return;
        }

        final char c = buffer.get();
        TrieNodeBuilder builder = subnodes.get(c);
        if (builder == null) {
            builder = new TrieNodeBuilder();
            subnodes.put(c, builder);
        }
        builder.doAddWord(buffer);
    }

    public TrieNode build()
    {
        final char[] nextChars = new char[subnodes.size()];
        final TrieNode[] nextNodes = new TrieNode[subnodes.size()];

        int index = 0;
        for (final Map.Entry<Character, TrieNodeBuilder> entry:
            subnodes.entrySet()) {
            nextChars[index] = entry.getKey();
            nextNodes[index] = entry.getValue().build();
            index++;
        }
        return new TrieNode(fullWord, nextChars, nextNodes);
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.matchers.wrap;

import com.github.fge.grappa.matchers.MatcherType;
import com.github.fge.grappa.matchers.base.Matcher;
import com.github.fge.grappa.rules.Rule;
import com.github.fge.grappa.run.context.MatcherContext;

import java.util.List;
import java.util.Objects;

/**
 * A {@link Matcher} that delegates all {@link Rule} and {@link Matcher}
 * interface methods to another {@link Matcher}
 *
 *
It can also hold a label and a leaf marker and lazily apply these to the
 * underlying {@link Matcher} once it is available.

 */
// TODO: REMOVE!! It is THE pain point in generation today
public final class ProxyMatcher
    implements Matcher, Cloneable
{
    private Matcher target;
    private String label;
    private boolean dirty;

    @Override
    public MatcherType getType()
    {
        if (dirty)
            apply();
        return target.getType();
    }

    @Override
    public List<Matcher> getChildren()
    {
        if (dirty)
            apply();
        return target.getChildren();
    }

    public void setLabel(final String label)
    {
        this.label = label;
        updateDirtyFlag();
    }

    /*
     * TODO: here in particular
     *
     * This is UGLY!! Builders to the rescue?
     */
    private void updateDirtyFlag()
    {
        dirty = label != null;
    }

    @Override
    public <V> boolean match(final MatcherContext<V> context)
    {
        if (dirty)
            apply();
        return target.match(context);
    }

    @Override
    public String getLabel()
    {
        if (dirty)
            apply();
        return target.getLabel();
    }

    @Override
    public boolean hasCustomLabel()
    {
        if (dirty)
            apply();
        return target.hasCustomLabel();
    }

    @Override
    public String toString()
    {
        if (target == null)
            return super.toString();
        if (dirty)
            apply();
        return target.toString();
    }

    private void apply()
    {
        if (label != null)
            label(label);
    }

    @Override
    public Rule label(final String label)
    {
        if (target == null) {
            // if we have no target yet we need to save the label and "apply" it later
            if (this.label == null) {
                setLabel(label);
                return this;
            }

            // this proxy matcher is already waiting for its label application opportunity,
            // so we need to create another proxy level
            final ProxyMatcher anotherProxy = createClone();
            anotherProxy.setLabel(label);
            anotherProxy.arm(this);
            return anotherProxy;
        }

        // we already have a target to which we can directly apply the label
        final Rule inner = unwrap(target);
        // since relabelling might change the instance we have to update it
        target = (Matcher) inner.label(label);
        setLabel(null);
        return target;
    }

    /**
     * Supplies this ProxyMatcher with its underlying delegate.
     *
     * @param target the Matcher to delegate to
     */
    public void arm(final Matcher target)
    {
        this.target = Objects.requireNonNull(target, "target");
    }

    /**
     * Retrieves the innermost Matcher that is not a ProxyMatcher.
     *
     * @param matcher the matcher to unwrap
     * @return the given instance if it is not a ProxyMatcher, otherwise the innermost non-proxy Matcher
     */
    public static Matcher unwrap(final Matcher matcher)
    {
        if (matcher instanceof ProxyMatcher) {
            final ProxyMatcher proxyMatcher = (ProxyMatcher) matcher;
            if (proxyMatcher.dirty)
                proxyMatcher.apply();
            return proxyMatcher.target == null ? proxyMatcher
                : proxyMatcher.target;
        }
        return matcher;
    }

    @Override
    public <V> MatcherContext<V> getSubContext(final MatcherContext<V> context)
    {
        if (dirty)
            apply();
        return target.getSubContext(context);
    }

    // creates a shallow copy
    private ProxyMatcher createClone()
    {
        try {
            return (ProxyMatcher) clone();
        } catch (CloneNotSupportedException e) {
            throw new IllegalStateException(e);
        }
    }
}


/*
 * Copyright (C) 2014 Francis Galiegue <fgaliegue@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.matchers.trie;

import com.github.fge.grappa.matchers.MatcherType;
import com.github.fge.grappa.matchers.base.AbstractMatcher;
import com.github.fge.grappa.parsers.BaseParser;
import com.github.fge.grappa.run.context.MatcherContext;

import javax.annotation.concurrent.Immutable;
import java.util.Collection;
import java.util.Objects;

/**
 * The trie matcher
 *
 * <p>This is the matcher used by {@link BaseParser#trie(Collection) trie()}.
 * </p>
 *
 * @since 1.0.0-beta.6
 */
@Immutable
public final class TrieMatcher
    extends AbstractMatcher
{
    private final Trie trie;

    public TrieMatcher(final Trie trie)
    {
        super("trie(" + Objects.requireNonNull(trie).getNrWords()
            + " strings)");
        this.trie = trie;
    }

    @Override
    public MatcherType getType()
    {
        return MatcherType.TERMINAL;
    }

    /**
     * Tries a match on the given MatcherContext.
     *
     * @param context the MatcherContext
     * @return true if the match was successful
     */
    @Override
    public <V> boolean match(final MatcherContext<V> context)
    {
        /*
         * Since the trie knows about the length of its possible longest match,
         * extract that many characters from the buffer. Remind that .extract()
         * will adjust illegal indices automatically.
         */
        final int maxLength = trie.getMaxLength();
        final int index = context.getCurrentIndex();
        final String input = context.getInputBuffer()
            .extract(index, index + maxLength);

        /*
         * We now just have to trie and search... (pun intended)
         */
        final int ret = trie.search(input, false);
        if (ret == -1)
            return false;

        /*
         * and since the result, when positive, is the length of the match,
         * advance the index in the buffer by that many positions.
         */
        context.advanceIndex(ret);
        return true;
    }
}


/*
 * Copyright (C) 2016 Francis Galiegue <fgaliegue@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.parsers;

import com.github.fge.grappa.annotations.SkipActionsInPredicates;
import com.github.fge.grappa.rules.Action;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

import javax.annotation.Nonnull;
import java.util.Objects;

/**
 * A {@link BaseParser} with an attached {@link EventBus}
 *
 *
This parser allows you to register instances of classes with methods which
 * have subscribed to a bus using Guava's {@link Subscribe} annotation. For the
 * recall, such methods must obey the following criteria:

 *
 *

 *
they must be public,

 *
they must return {@code void},

 *
they must accept one, and only one, argument.

 *

 *
 *
A sample usage is shown below, where an event is posted on the bus, with
 * the text matched as an argument:

 *
 *

 *     public Rule someRule()
 *     {
 *         // will trigger on any subscribed method having a String as an
 *         // argument, since match() returns a String
 *         return sequence("foobar", post(match()));
 *     }
 *

 *
 *
This method ({@link #post(Object)} ultimately becomes an {@link Action},
 * which means it obeys all the rules of an action. This includes, for instance,
 * the presence (or absence thereof) of annotations such as {@link
 * SkipActionsInPredicates}.

 *
 *
Usage notes

 *
 *
Listeners to such a parser can only be registered once the parser has been
 * created. For instance:

 *
 *

 *     // MyParser extends EventBusParser
 *     final MyParser parser = Grappa.createParser(MyParser.class);
 *
 *     // Create the listener
 *     final MyListener listener = new MyListener();
 *
 *     // Register it to the parser
 *     parser.register(listener);
 *
 *     // Crete a runner, run on an input
 *

 *
 *
Please note that such a listener's state, if any, will be retained across
 * reuses of the same parser on different inputs. According to the use case,
 * this may, or may not, be a desirable property.

 *
 * @see Subscribe
 * @see EventBus#register(Object)
 * @see EventBus#post(Object)
 */
public abstract class EventBusParser<V>
    extends BaseParser<V>
{
    protected final EventBus bus = new EventBus();

    /**
     * Register a listener to the event bus
     *
     * @param listener the listener
     * @return always true
     *
     * @see EventBus#register(Object)
     */
    public final boolean register(@Nonnull final Object listener)
    {
        bus.register(Objects.requireNonNull(listener));
        return true;
    }

    /**
     * Post an arbitrary, non null, object on the bus
     *
     * @param object the object (must not be null)
     * @return always true
     */
    public final boolean post(@Nonnull final Object object)
    {
        Objects.requireNonNull(object);
        bus.post(object);
        return true;
    }
}


/*
 * Copyright (C) 2014 Francis Galiegue <fgaliegue@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.matchers.join;

import com.github.fge.grappa.parsers.BaseParser;
import com.github.fge.grappa.rules.Rule;
import com.google.common.annotations.VisibleForTesting;

import javax.annotation.Nonnull;
import java.util.Objects;

/**
 * Bootstrapping a {@link JoinMatcher}
 *
 *
An instance of this class is created by {@link BaseParser#join(Object)}.
 *

 *
 *
Its two arguments are a {@link BaseParser} instance and the object to use
 * as a rule which will become the {@link JoinMatcher}'s "joining" rule; this
 * object is converted to a {@link Rule} using {@link
 * BaseParser#toRule(Object)}.

 *
 *
Its {@link #using(Object)} method produces a {@link JoinMatcherBuilder};
 * the argument to this method (converted to a rule in a similar fashion as the
 * above) will become the "joined" rule of the produced matcher.

 *
 * @param <V> value type produced by the parser
 * @param
 parser class
 *
 * @see JoinMatcherBuilder
 */
public final class JoinMatcherBootstrap<V, P extends BaseParser<V>>
{
    private final P parser;
    private final Rule joined;

    @VisibleForTesting
    static <T, E extends BaseParser<T>> JoinMatcherBootstrap<T, E> create(
        final E parser, final Object joined)
    {
        return new JoinMatcherBootstrap<>(parser, parser.toRule(joined));
    }

    public JoinMatcherBootstrap(@Nonnull final P parser,
        @Nonnull final Rule joined)
    {
        this.parser = Objects.requireNonNull(parser);
        this.joined = joined;
    }

    /**
     * Define the joining rule
     *
     * @param joining the rule
     * @return a new {@link JoinMatcherBuilder}
     *
     * @see BaseParser#toRule(Object)
     */
    public JoinMatcherBuilder<V> using(@Nonnull final Object joining)
    {
        return new JoinMatcherBuilder<>(joined, parser, parser.toRule(joining));
    }

    /**
     * Define the joining rule
     *
     *
Like {@link #using(Object)}, except several rules are accepted as
     * arguments.

     *
     * @param rule first rule
     * @param rule2 second rule
     * @param moreRules other rules
     * @return a new {@link JoinMatcherBuilder}
     *
     * @see BaseParser#sequence(Object, Object, Object...)
     */
    public JoinMatcherBuilder<V> using(final Object rule, final Object rule2,
        final Object... moreRules)
    {
        return using(parser.sequence(rule, rule2, moreRules));
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.fge.grappa.matchers.base;

import com.github.fge.grappa.rules.Rule;
import com.google.common.collect.ImmutableList;
import com.github.fge.grappa.run.context.MatcherContext;
import com.github.fge.grappa.misc.ImmutableGraphNode;

import java.util.Objects;

/**
 * Abstract base class of most regular {@link Matcher}s
 */
public abstract class AbstractMatcher
    extends ImmutableGraphNode<Matcher>
    implements Matcher, Cloneable
{
    private String label;

    protected AbstractMatcher(final String label)
    {
        this(new Rule[0], label);
    }

    protected AbstractMatcher(final Rule subRule, final String label)
    {
        this(new Rule[]{ Objects.requireNonNull(subRule, "subRule") },
            label);
    }

    protected AbstractMatcher(final Rule[] subRules, final String label)
    {
        super(ImmutableList.copyOf(
                toMatchers(Objects.requireNonNull(subRules))
            )
        );
        this.label = label;
    }

    private static Matcher[] toMatchers(final Rule... subRules)
    {
        final Matcher[] matchers = new Matcher[subRules.length];
        for (int i = 0; i < subRules.length; i++)
            matchers[i] = (Matcher) subRules[i];
        return matchers;
    }

    @Override
    public String getLabel()
    {
        return label;
    }

    @Override
    public boolean hasCustomLabel()
    {
        // this is the default implementation for single character matchers
        // complex matchers override with a custom implementation
        return true;
    }

    @Override
    public final String toString()
    {
        return getLabel();
    }

    @Override
    public final AbstractMatcher label(final String label)
    {
        if (Objects.equals(label, this.label))
            return this;
        final AbstractMatcher clone = createClone();
        clone.label = label;
        return clone;
    }

    // default implementation is to simply delegate to the context
    @Override
    public <V> MatcherContext<V> getSubContext(final MatcherContext<V> context)
    {
        return context.getSubContext(this);
    }

    // creates a shallow copy
    private AbstractMatcher createClone()
    {
        try {
            return (AbstractMatcher) clone();
        } catch (CloneNotSupportedException e) {
            throw new IllegalStateException(e);
        }
    }

}


package com.github.fge.grappa.misc;

import com.github.fge.grappa.annotations.Cached;
import com.github.fge.grappa.annotations.DontLabel;
import com.github.fge.grappa.matchers.EmptyMatcher;
import com.github.fge.grappa.matchers.delegate.OptionalMatcher;
import com.github.fge.grappa.matchers.join.JoinMatcherBuilder;
import com.github.fge.grappa.matchers.repeat.RepeatMatcherBuilder;
import com.github.fge.grappa.parsers.BaseParser;
import com.github.fge.grappa.rules.Rule;
import com.google.common.base.Preconditions;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;

import java.util.Objects;

/**
 * A matcher builder for matchers repeating a given number of times
 *
 *
This class allows to specify a number of times which a given rule should
 * be repeated:

 *
 *

 *
at least n times;

 *
at most n times;

 *
exactly n times;

 *
between n1 and n2 times.

 *

 *
 *
When appropriate, the returned rule is a simplified version; for instance,
 * a rule repeated "exactly once" is the rule itself. See {@link #range(Range)}
 * for more details.

 *
 * @param <V> the type parameter of the parser
 *
 * @see JoinMatcherBuilder
 * @see RepeatMatcherBuilder
 */
public abstract class RangeMatcherBuilder<V>
{
    private static final Range<Integer> AT_LEAST_ZERO = Range.atLeast(0);

    protected final BaseParser<V> parser;
    protected final Rule rule;

    protected RangeMatcherBuilder(final BaseParser<V> parser, final Rule rule)
    {
        this.parser = Objects.requireNonNull(parser);
        this.rule = Objects.requireNonNull(rule);
    }

    /**
     * Return a rule with a minimum number of cycles to run
     *
     * @param nrCycles the number of cycles
     * @return a rule
     * @throws IllegalArgumentException {@code nrCycles} is less than 0
     *
     * @see Range#atLeast(Comparable)
     */
    public Rule min(final int nrCycles)
    {
        Preconditions.checkArgument(nrCycles >= 0,
            "illegal repetition number specified (" + nrCycles
                + "), must be 0 or greater");
        return range(Range.atLeast(nrCycles));
    }

    /**
     * Return a rule with a maximum number of cycles to run
     *
     * @param nrCycles the number of cycles
     * @return a rule
     * @throws IllegalArgumentException {@code nrCycles} is less than 0
     *
     * @see Range#atMost(Comparable)
     */
    public Rule max(final int nrCycles)
    {
        Preconditions.checkArgument(nrCycles >= 0,
            "illegal repetition number specified (" + nrCycles
                + "), must be 0 or greater");
        return range(Range.atMost(nrCycles));
    }

    /**
     * Return a rule with an exact number of cycles to run
     *
     * @param nrCycles the number of cycles
     * @return a rule
     * @throws IllegalArgumentException {@code nrCycles} is less than 0
     *
     * @see Range#singleton(Comparable)
     */
    public Rule times(final int nrCycles)
    {
        Preconditions.checkArgument(nrCycles >= 0,
            "illegal repetition number specified (" + nrCycles
                + "), must be 0 or greater");
        return range(Range.singleton(nrCycles));
    }

    /**
     * Return a rule with both lower and upper bounds on the number of cycles
     *
     *
Note that the range of cycles to run is closed on both ends (that is,
     * the minimum and maximum number of cycles) are inclusive.
     *

     *
     * @param minCycles the minimum number of cycles
     * @param maxCycles the maximum number of cycles
     * @return a rule
     * @throws IllegalArgumentException minimum number of cycles is negative; or
     * maximum number of cycles is less than the minimum
     *
     * @see Range#closed(Comparable, Comparable)
     */
    public Rule times(final int minCycles, final int maxCycles)
    {
        Preconditions.checkArgument(minCycles >= 0,
            "illegal repetition number specified (" + minCycles
                + "), must be 0 or greater");
        Preconditions.checkArgument(maxCycles >= minCycles,
            "illegal range specified (" + minCycles + ", " + maxCycles
                + "): maximum must be greater than minimum");
        return range(Range.closed(minCycles, maxCycles));
    }

    /**
     * Core method for building a repeating matcher
     *
     *
This is the method which all other methods (min, max, times) delegate
     * to; among other things it is responsible for the logic of simplifying
     * matchers where possible.

     *
     *
The simplifications are as follows:

     *
     *

     *
[0..0]: returns an {@link EmptyMatcher};

     *
[0..1]: returns an {@link OptionalMatcher} with the rule as a
     *     submatcher;

     *
[1..1]: returns the rule itself.

     *

     *
     *
If none of these apply, this method delegates as follows:

     *
     *

     *
[n..+∞) for whatever n: delegates to {@link #boundedDown(int)};
     *

     *
[0..n] for n >= 2: delegates to {@link #boundedUp(int)};

     *
[n..n] for n >= 2: delegates to {@link #exactly(int)};

     *
[n..m] with 0 < n < m: delegates to {@link
     *     #boundedBoth(int, int)}.

     *

     *
     * @param range the range
     * @return the final resulting rule
     */
    @Cached
    @DontLabel
    public Rule range(final Range<Integer> range)
    {
        Objects.requireNonNull(range, "range must not be null");

        final Range<Integer> realRange = normalizeRange(range);

        /*
         * We always have a lower bound
         */
        final int lowerBound = realRange.lowerEndpoint();

        /*
         * Handle the case where there is no upper bound
         */
        if (!realRange.hasUpperBound())
            return boundedDown(lowerBound);

        /*
         * There is an upper bound. Handle the case where it is 0 or 1. Since
         * the range is legal, we know that if it is 0, so is the lowerbound;
         * and if it is one, the lower bound is either 0 or 1.
         */
        final int upperBound = realRange.upperEndpoint();
        if (upperBound == 0)
            return parser.empty();
        if (upperBound == 1)
            return lowerBound == 0 ? parser.optional(rule) : rule;

        /*
         * So, upper bound is 2 or greater; return the appropriate matcher
         * according to what the lower bound is.
         *
         * Also, if the lower and upper bounds are equal, return a matcher doing
         * a fixed number of matches.
         */
        if (lowerBound == 0)
            return boundedUp(upperBound);

        return lowerBound == upperBound
            ? exactly(lowerBound)
            : boundedBoth(lowerBound, upperBound);
    }

    /**
     * Build a rule which is expected to match a minimum number of times
     *
     *
The returned matcher will attempt to match indefinitely its input
     * until it fails. Success should be declared if and only if the number of
     * times the matcher has succeeded is greater than, or equal to, the number
     * of cycles given as an argument (including 0).

     *
     * @param minCycles the minimum number of cycles (inclusive)
     * @return a rule
     */
    protected abstract Rule boundedDown(int minCycles);

    /**
     * Build a rule which is expected to match a maximum number of times
     *
     *
The returned matcher will attempt to match repeatedly up to, and
     * including, the number of cycles returned as an argument. Note that the
     * argument will always be greater than or equal to 2.

     *
     *
One consequence is that this matcher always succeeds.

     *
     * @param maxCycles the maximum number of cycles (inclusive)
     * @return a rule
     */
    protected abstract Rule boundedUp(int maxCycles);

    /**
     * Build a rule which is expected to match a fixed number of times
     *
     *
The returned matcher will attempt to match repeatedly up to, and
     * including, the number of cycles given as an argument. Success should be
     * declared if and only if the number of cycles matched is exactly this
     * number.

     *
     * @param nrCycles the number of cycles (inclusive)
     * @return a rule
     */
    protected abstract Rule exactly(int nrCycles);

    /**
     * Build a rule which is expected to match a number of times between two
     * end points
     *
     *
The returned matcher will attempt to match repeatedly up to, and
     * including, the maximum number of cycles specified as the second argument.
     * Success should be declared if and only if the number of cycles performed
     * is at least equal to the number of cycles specified as the first
     * argument.

     *
     *
Note that the first argument will always be strictly greater than 0,
     * and that the second argument will always be strictly greater than the
     * first.

     *
     * @param minCycles the minimum number of cycles (inclusive)
     * @param maxCycles the maximum number of cycles (exclusive)
     * @return a rule
     */
    protected abstract Rule boundedBoth(int minCycles, int maxCycles);

    private static Range<Integer> normalizeRange(final Range<Integer> range)
    {
        Range<Integer> newRange = AT_LEAST_ZERO.intersection(range);

        if (newRange.isEmpty())
            throw new IllegalArgumentException("illegal range " + range
                + ": intersection with " + AT_LEAST_ZERO + " is empty");

        newRange = newRange.canonical(DiscreteDomain.integers());

        final int lowerBound = newRange.lowerEndpoint();

        return newRange.hasUpperBound()
            ? Range.closed(lowerBound, newRange.upperEndpoint() - 1)
            : Range.atLeast(lowerBound);
    }
}