src/main/java/com/github/fge/grappa/misc/RangeMatcherBuilder.java
@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);
    }
@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);
    }
Core method for building a repeating matcher

<p>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.</p>

<p>The simplifications are as follows:</p>

<ul>
<li>[0..0]: returns an {@link EmptyMatcher};</li>
<li>[0..1]: returns an {@link OptionalMatcher} with the rule as a
submatcher;</li>
<li>[1..1]: returns the rule itself.</li>
</ul>

<p>If none of these apply, this method delegates as follows:</p>

<ul>
<li>[n..+∞) for whatever n: delegates to {@link #boundedDown(int)};
</li>
<li>[0..n] for n &gt;= 2: delegates to {@link #boundedUp(int)};</li>
<li>[n..n] for n &gt;= 2: delegates to {@link #exactly(int)};</li>
<li>[n..m] with 0 &lt; n &lt; m: delegates to {@link
#boundedBoth(int, int)}.</li>
</ul>

@param range the range
@return the final resulting rule