STATIC I32
S_regrepeat(pTHX_ regexp *prog, char **startposp, const regnode *p,
            regmatch_info *const reginfo, I32 max, int depth)
{
    char *scan;     /* Pointer to current position in target string */
    I32 c;
    char *loceol = reginfo->strend;   /* local version */
    I32 hardcount = 0;  /* How many matches so far */
    bool utf8_target = reginfo->is_utf8_target;
    unsigned int to_complement = 0;  /* Invert the result? */
    UV utf8_flags;
    _char_class_number classnum;
#ifndef DEBUGGING
    PERL_UNUSED_ARG(depth);
#endif

    PERL_ARGS_ASSERT_REGREPEAT;

    scan = *startposp;
    if (max == REG_INFTY)
	max = I32_MAX;
    else if (! utf8_target && loceol - scan > max)
	loceol = scan + max;

    /* Here, for the case of a non-UTF-8 target we have adjusted <loceol> down
     * to the maximum of how far we should go in it (leaving it set to the real
     * end, if the maximum permissible would take us beyond that).  This allows
     * us to make the loop exit condition that we haven't gone past <loceol> to
     * also mean that we haven't exceeded the max permissible count, saving a
     * test each time through the loop.  But it assumes that the OP matches a
     * single byte, which is true for most of the OPs below when applied to a
     * non-UTF-8 target.  Those relatively few OPs that don't have this
     * characteristic will have to compensate.
     *
     * There is no adjustment for UTF-8 targets, as the number of bytes per
     * character varies.  OPs will have to test both that the count is less
     * than the max permissible (using <hardcount> to keep track), and that we
     * are still within the bounds of the string (using <loceol>.  A few OPs
     * match a single byte no matter what the encoding.  They can omit the max
     * test if, for the UTF-8 case, they do the adjustment that was skipped
     * above.
     *
     * Thus, the code above sets things up for the common case; and exceptional
     * cases need extra work; the common case is to make sure <scan> doesn't
     * go past <loceol>, and for UTF-8 to also use <hardcount> to make sure the
     * count doesn't exceed the maximum permissible */

    switch (OP(p)) {
    case REG_ANY:
	if (utf8_target) {
	    while (scan < loceol && hardcount < max && *scan != '\n') {
		scan += UTF8SKIP(scan);
		hardcount++;
	    }
	} else {
	    while (scan < loceol && *scan != '\n')
		scan++;
	}
	break;
    case SANY:
        if (utf8_target) {
	    while (scan < loceol && hardcount < max) {
	        scan += UTF8SKIP(scan);
		hardcount++;
	    }
	}
	else
	    scan = loceol;
	break;
    case EXACTL:
        _CHECK_AND_WARN_PROBLEMATIC_LOCALE;
        if (utf8_target && UTF8_IS_ABOVE_LATIN1(*scan)) {
            _CHECK_AND_OUTPUT_WIDE_LOCALE_UTF8_MSG(scan, loceol);
        }
        /* FALLTHROUGH */
    case EXACT:
        assert(STR_LEN(p) == reginfo->is_utf8_pat ? UTF8SKIP(STRING(p)) : 1);

	c = (U8)*STRING(p);

        /* Can use a simple loop if the pattern char to match on is invariant
         * under UTF-8, or both target and pattern aren't UTF-8.  Note that we
         * can use UTF8_IS_INVARIANT() even if the pattern isn't UTF-8, as it's
         * true iff it doesn't matter if the argument is in UTF-8 or not */
        if (UTF8_IS_INVARIANT(c) || (! utf8_target && ! reginfo->is_utf8_pat)) {
            if (utf8_target && loceol - scan > max) {
                /* We didn't adjust <loceol> because is UTF-8, but ok to do so,
                 * since here, to match at all, 1 char == 1 byte */
                loceol = scan + max;
            }
	    while (scan < loceol && UCHARAT(scan) == c) {
		scan++;
	    }
	}
	else if (reginfo->is_utf8_pat) {
            if (utf8_target) {
                STRLEN scan_char_len;

                /* When both target and pattern are UTF-8, we have to do
                 * string EQ */
                while (hardcount < max
                       && scan < loceol
                       && (scan_char_len = UTF8SKIP(scan)) <= STR_LEN(p)
                       && memEQ(scan, STRING(p), scan_char_len))
                {
                    scan += scan_char_len;
                    hardcount++;
                }
            }
            else if (! UTF8_IS_ABOVE_LATIN1(c)) {

                /* Target isn't utf8; convert the character in the UTF-8
                 * pattern to non-UTF8, and do a simple loop */
                c = EIGHT_BIT_UTF8_TO_NATIVE(c, *(STRING(p) + 1));
                while (scan < loceol && UCHARAT(scan) == c) {
                    scan++;
                }
            } /* else pattern char is above Latin1, can't possibly match the
                 non-UTF-8 target */
        }
        else {

            /* Here, the string must be utf8; pattern isn't, and <c> is
             * different in utf8 than not, so can't compare them directly.
             * Outside the loop, find the two utf8 bytes that represent c, and
             * then look for those in sequence in the utf8 string */
	    U8 high = UTF8_TWO_BYTE_HI(c);
	    U8 low = UTF8_TWO_BYTE_LO(c);

	    while (hardcount < max
		    && scan + 1 < loceol
		    && UCHARAT(scan) == high
		    && UCHARAT(scan + 1) == low)
	    {
		scan += 2;
		hardcount++;
	    }
	}
	break;

    case EXACTFA_NO_TRIE:   /* This node only generated for non-utf8 patterns */
        assert(! reginfo->is_utf8_pat);
        /* FALLTHROUGH */
    case EXACTFA:
        utf8_flags = FOLDEQ_UTF8_NOMIX_ASCII;
	goto do_exactf;

    case EXACTFL:
        _CHECK_AND_WARN_PROBLEMATIC_LOCALE;
	utf8_flags = FOLDEQ_LOCALE;
	goto do_exactf;

    case EXACTF:   /* This node only generated for non-utf8 patterns */
        assert(! reginfo->is_utf8_pat);
        utf8_flags = 0;
        goto do_exactf;

    case EXACTFLU8:
        if (! utf8_target) {
            break;
        }
        utf8_flags =  FOLDEQ_LOCALE | FOLDEQ_S2_ALREADY_FOLDED
                                    | FOLDEQ_S2_FOLDS_SANE;
        goto do_exactf;

    case EXACTFU_SS:
    case EXACTFU:
	utf8_flags = reginfo->is_utf8_pat ? FOLDEQ_S2_ALREADY_FOLDED : 0;

      do_exactf: {
        int c1, c2;
        U8 c1_utf8[UTF8_MAXBYTES+1], c2_utf8[UTF8_MAXBYTES+1];

        assert(STR_LEN(p) == reginfo->is_utf8_pat ? UTF8SKIP(STRING(p)) : 1);

        if (S_setup_EXACTISH_ST_c1_c2(aTHX_ p, &c1, c1_utf8, &c2, c2_utf8,
                                        reginfo))
        {
            if (c1 == CHRTEST_VOID) {
                /* Use full Unicode fold matching */
                char *tmpeol = reginfo->strend;
                STRLEN pat_len = reginfo->is_utf8_pat ? UTF8SKIP(STRING(p)) : 1;
                while (hardcount < max
                        && foldEQ_utf8_flags(scan, &tmpeol, 0, utf8_target,
                                             STRING(p), NULL, pat_len,
                                             reginfo->is_utf8_pat, utf8_flags))
                {
                    scan = tmpeol;
                    tmpeol = reginfo->strend;
                    hardcount++;
                }
            }
            else if (utf8_target) {
                if (c1 == c2) {
                    while (scan < loceol
                           && hardcount < max
                           && memEQ(scan, c1_utf8, UTF8SKIP(scan)))
                    {
                        scan += UTF8SKIP(scan);
                        hardcount++;
                    }
                }
                else {
                    while (scan < loceol
                           && hardcount < max
                           && (memEQ(scan, c1_utf8, UTF8SKIP(scan))
                               || memEQ(scan, c2_utf8, UTF8SKIP(scan))))
                    {
                        scan += UTF8SKIP(scan);
                        hardcount++;
                    }
                }
            }
            else if (c1 == c2) {
                while (scan < loceol && UCHARAT(scan) == c1) {
                    scan++;
                }
            }
            else {
                while (scan < loceol &&
                    (UCHARAT(scan) == c1 || UCHARAT(scan) == c2))
                {
                    scan++;
                }
            }
	}
	break;
    }
    case ANYOFL:
        _CHECK_AND_WARN_PROBLEMATIC_LOCALE;

        if ((FLAGS(p) & ANYOF_LOC_REQ_UTF8) && ! IN_UTF8_CTYPE_LOCALE) {
            Perl_ck_warner(aTHX_ packWARN(WARN_LOCALE), utf8_locale_required);
        }
        /* FALLTHROUGH */
    case ANYOFD:
    case ANYOF:
	if (utf8_target) {
	    while (hardcount < max
                   && scan < loceol
		   && reginclass(prog, p, (U8*)scan, (U8*) loceol, utf8_target))
	    {
		scan += UTF8SKIP(scan);
		hardcount++;
	    }
	} else {
	    while (scan < loceol && REGINCLASS(prog, p, (U8*)scan))
		scan++;
	}
	break;

    /* The argument (FLAGS) to all the POSIX node types is the class number */

    case NPOSIXL:
        to_complement = 1;
        /* FALLTHROUGH */

    case POSIXL:
        _CHECK_AND_WARN_PROBLEMATIC_LOCALE;
	if (! utf8_target) {
	    while (scan < loceol && to_complement ^ cBOOL(isFOO_lc(FLAGS(p),
                                                                   *scan)))
            {
		scan++;
            }
	} else {
	    while (hardcount < max && scan < loceol
                   && to_complement ^ cBOOL(isFOO_utf8_lc(FLAGS(p),
                                                                  (U8 *) scan)))
            {
                scan += UTF8SKIP(scan);
		hardcount++;
	    }
	}
	break;

    case POSIXD:
        if (utf8_target) {
            goto utf8_posix;
        }
        /* FALLTHROUGH */

    case POSIXA:
        if (utf8_target && loceol - scan > max) {

            /* We didn't adjust <loceol> at the beginning of this routine
             * because is UTF-8, but it is actually ok to do so, since here, to
             * match, 1 char == 1 byte. */
            loceol = scan + max;
        }
        while (scan < loceol && _generic_isCC_A((U8) *scan, FLAGS(p))) {
	    scan++;
	}
	break;

    case NPOSIXD:
        if (utf8_target) {
            to_complement = 1;
            goto utf8_posix;
        }
        /* FALLTHROUGH */

    case NPOSIXA:
        if (! utf8_target) {
            while (scan < loceol && ! _generic_isCC_A((U8) *scan, FLAGS(p))) {
                scan++;
            }
        }
        else {

            /* The complement of something that matches only ASCII matches all
             * non-ASCII, plus everything in ASCII that isn't in the class. */
	    while (hardcount < max && scan < loceol
                   && (! isASCII_utf8(scan)
                       || ! _generic_isCC_A((U8) *scan, FLAGS(p))))
            {
                scan += UTF8SKIP(scan);
		hardcount++;
	    }
        }
        break;

    case NPOSIXU:
        to_complement = 1;
        /* FALLTHROUGH */

    case POSIXU:
	if (! utf8_target) {
            while (scan < loceol && to_complement
                                ^ cBOOL(_generic_isCC((U8) *scan, FLAGS(p))))
            {
                scan++;
            }
	}
	else {
          utf8_posix:
            classnum = (_char_class_number) FLAGS(p);
            if (classnum < _FIRST_NON_SWASH_CC) {

                /* Here, a swash is needed for above-Latin1 code points.
                 * Process as many Latin1 code points using the built-in rules.
                 * Go to another loop to finish processing upon encountering
                 * the first Latin1 code point.  We could do that in this loop
                 * as well, but the other way saves having to test if the swash
                 * has been loaded every time through the loop: extra space to
                 * save a test. */
                while (hardcount < max && scan < loceol) {
                    if (UTF8_IS_INVARIANT(*scan)) {
                        if (! (to_complement ^ cBOOL(_generic_isCC((U8) *scan,
                                                                   classnum))))
                        {
                            break;
                        }
                        scan++;
                    }
                    else if (UTF8_IS_DOWNGRADEABLE_START(*scan)) {
                        if (! (to_complement
                              ^ cBOOL(_generic_isCC(EIGHT_BIT_UTF8_TO_NATIVE(*scan,
                                                                     *(scan + 1)),
                                                    classnum))))
                        {
                            break;
                        }
                        scan += 2;
                    }
                    else {
                        goto found_above_latin1;
                    }

                    hardcount++;
                }
            }
            else {
                /* For these character classes, the knowledge of how to handle
                 * every code point is compiled in to Perl via a macro.  This
                 * code is written for making the loops as tight as possible.
                 * It could be refactored to save space instead */
                switch (classnum) {
                    case _CC_ENUM_SPACE:
                        while (hardcount < max
                               && scan < loceol
                               && (to_complement ^ cBOOL(isSPACE_utf8(scan))))
                        {
                            scan += UTF8SKIP(scan);
                            hardcount++;
                        }
                        break;
                    case _CC_ENUM_BLANK:
                        while (hardcount < max
                               && scan < loceol
                               && (to_complement ^ cBOOL(isBLANK_utf8(scan))))
                        {
                            scan += UTF8SKIP(scan);
                            hardcount++;
                        }
                        break;
                    case _CC_ENUM_XDIGIT:
                        while (hardcount < max
                               && scan < loceol
                               && (to_complement ^ cBOOL(isXDIGIT_utf8(scan))))
                        {
                            scan += UTF8SKIP(scan);
                            hardcount++;
                        }
                        break;
                    case _CC_ENUM_VERTSPACE:
                        while (hardcount < max
                               && scan < loceol
                               && (to_complement ^ cBOOL(isVERTWS_utf8(scan))))
                        {
                            scan += UTF8SKIP(scan);
                            hardcount++;
                        }
                        break;
                    case _CC_ENUM_CNTRL:
                        while (hardcount < max
                               && scan < loceol
                               && (to_complement ^ cBOOL(isCNTRL_utf8(scan))))
                        {
                            scan += UTF8SKIP(scan);
                            hardcount++;
                        }
                        break;
                    default:
                        Perl_croak(aTHX_ "panic: regrepeat() node %d='%s' has an unexpected character class '%d'", OP(p), PL_reg_name[OP(p)], classnum);
                }
            }
	}
        break;

      found_above_latin1:   /* Continuation of POSIXU and NPOSIXU */

        /* Load the swash if not already present */
        if (! PL_utf8_swash_ptrs[classnum]) {
            U8 flags = _CORE_SWASH_INIT_ACCEPT_INVLIST;
            PL_utf8_swash_ptrs[classnum] = _core_swash_init(
                                        "utf8",
                                        "",
                                        &PL_sv_undef, 1, 0,
                                        PL_XPosix_ptrs[classnum], &flags);
        }

        while (hardcount < max && scan < loceol
               && to_complement ^ cBOOL(_generic_utf8(
                                       classnum,
                                       scan,
                                       swash_fetch(PL_utf8_swash_ptrs[classnum],
                                                   (U8 *) scan,
                                                   TRUE))))
        {
            scan += UTF8SKIP(scan);
            hardcount++;
        }
        break;

    case LNBREAK:
        if (utf8_target) {
	    while (hardcount < max && scan < loceol &&
                    (c=is_LNBREAK_utf8_safe(scan, loceol))) {
		scan += c;
		hardcount++;
	    }
	} else {
            /* LNBREAK can match one or two latin chars, which is ok, but we
             * have to use hardcount in this situation, and throw away the
             * adjustment to <loceol> done before the switch statement */
            loceol = reginfo->strend;
	    while (scan < loceol && (c=is_LNBREAK_latin1_safe(scan, loceol))) {
		scan+=c;
		hardcount++;
	    }
	}
	break;

    case BOUNDL:
    case NBOUNDL:
        _CHECK_AND_WARN_PROBLEMATIC_LOCALE;
        /* FALLTHROUGH */
    case BOUND:
    case BOUNDA:
    case BOUNDU:
    case EOS:
    case GPOS:
    case KEEPS:
    case NBOUND:
    case NBOUNDA:
    case NBOUNDU:
    case OPFAIL:
    case SBOL:
    case SEOL:
        /* These are all 0 width, so match right here or not at all. */
        break;

    default:
        Perl_croak(aTHX_ "panic: regrepeat() called with unrecognized node type %d='%s'", OP(p), PL_reg_name[OP(p)]);
        /* NOTREACHED */
        NOT_REACHED; /* NOTREACHED */

    }

    if (hardcount)
	c = hardcount;
    else
	c = scan - *startposp;
    *startposp = scan;

    DEBUG_r({
	GET_RE_DEBUG_FLAGS_DECL;
	DEBUG_EXECUTE_r({
	    SV * const prop = sv_newmortal();
            regprop(prog, prop, p, reginfo, NULL);
	    PerlIO_printf(Perl_debug_log,
			"%*s  %s can match %"IVdf" times out of %"IVdf"...\n",
			REPORT_CODE_OFF + depth*2, "", SvPVX_const(prop),(IV)c,(IV)max);
	});
    });

    return(c);