File: System\Text\RegularExpressions\RegexInterpreter.cs
Web Access
Project: src\src\libraries\System.Text.RegularExpressions\src\System.Text.RegularExpressions.csproj (System.Text.RegularExpressions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
 
namespace System.Text.RegularExpressions
{
    /// <summary>A <see cref="RegexRunnerFactory"/> for creating <see cref="RegexInterpreter"/>s.</summary>
    internal sealed class RegexInterpreterFactory(RegexTree tree) : RegexRunnerFactory
    {
        /// <summary>The RegexInterpretedCode for the RegexTree and the specified culture.</summary>
        private readonly RegexInterpreterCode _code = RegexWriter.Write(tree);
        /// <summary>
        /// CultureInfo field from the tree's culture which will only be set to an actual culture if the
        /// tree contains IgnoreCase backreferences. If the tree doesn't have IgnoreCase backreferences, then we keep _culture as null.
        /// </summary>
        private readonly CultureInfo? _culture = tree.Culture;
 
        protected internal override RegexRunner CreateInstance() =>
            // Create a new interpreter instance.
            new RegexInterpreter(_code, _culture);
    }
 
    /// <summary>Executes a block of regular expression codes while consuming input.</summary>
    internal sealed class RegexInterpreter : RegexRunner
    {
        private readonly RegexInterpreterCode _code;
        private readonly CultureInfo? _culture;
        private RegexCaseBehavior _caseBehavior;
 
        private RegexOpcode _operator;
        private int _codepos;
        private bool _rightToLeft;
 
        public RegexInterpreter(RegexInterpreterCode code, CultureInfo? culture)
        {
            Debug.Assert(code != null, "code must not be null.");
 
            _code = code;
            _culture = culture;
        }
 
        protected override void InitTrackCount() => runtrackcount = _code.TrackCount;
 
        private void Advance(int i)
        {
            _codepos += i + 1;
            SetOperator((RegexOpcode)_code.Codes[_codepos]);
        }
 
        private void Goto(int newpos)
        {
            // When branching backward, ensure storage.
            EnsureStorage();
 
            _codepos = newpos;
            SetOperator((RegexOpcode)_code.Codes[newpos]);
        }
 
        private void Trackto(int newpos) => runtrackpos = runtrack!.Length - newpos;
 
        private int Trackpos() => runtrack!.Length - runtrackpos;
 
        /// <summary>Push onto the backtracking stack.</summary>
        private void TrackPush() => runtrack![--runtrackpos] = _codepos;
 
        private void TrackPush(int i1)
        {
            int[] localruntrack = runtrack!;
            int localruntrackpos = runtrackpos;
 
            localruntrack[--localruntrackpos] = i1;
            localruntrack[--localruntrackpos] = _codepos;
 
            runtrackpos = localruntrackpos;
        }
 
        private void TrackPush(int i1, int i2)
        {
            int[] localruntrack = runtrack!;
            int localruntrackpos = runtrackpos;
 
            localruntrack[--localruntrackpos] = i1;
            localruntrack[--localruntrackpos] = i2;
            localruntrack[--localruntrackpos] = _codepos;
 
            runtrackpos = localruntrackpos;
        }
 
        private void TrackPush(int i1, int i2, int i3)
        {
            int[] localruntrack = runtrack!;
            int localruntrackpos = runtrackpos;
 
            localruntrack[--localruntrackpos] = i1;
            localruntrack[--localruntrackpos] = i2;
            localruntrack[--localruntrackpos] = i3;
            localruntrack[--localruntrackpos] = _codepos;
 
            runtrackpos = localruntrackpos;
        }
 
        private void TrackPush2(int i1)
        {
            int[] localruntrack = runtrack!;
            int localruntrackpos = runtrackpos;
 
            localruntrack[--localruntrackpos] = i1;
            localruntrack[--localruntrackpos] = -_codepos;
 
            runtrackpos = localruntrackpos;
        }
 
        private void TrackPush2(int i1, int i2)
        {
            int[] localruntrack = runtrack!;
            int localruntrackpos = runtrackpos;
 
            localruntrack[--localruntrackpos] = i1;
            localruntrack[--localruntrackpos] = i2;
            localruntrack[--localruntrackpos] = -_codepos;
 
            runtrackpos = localruntrackpos;
        }
 
        private void Backtrack()
        {
            CheckTimeout(); // to ensure that any backtracking operation has a timeout check
 
            int newpos = runtrack![runtrackpos];
            runtrackpos++;
 
            int back = (int)RegexOpcode.Backtracking;
            if (newpos < 0)
            {
                newpos = -newpos;
                back = (int)RegexOpcode.BacktrackingSecond;
            }
            SetOperator((RegexOpcode)(_code.Codes[newpos] | back));
 
            // When branching backward, ensure storage.
            EnsureStorage();
 
            _codepos = newpos;
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void SetOperator(RegexOpcode op)
        {
            _operator = op & ~RegexOpcode.RightToLeft;
            _rightToLeft = (op & RegexOpcode.RightToLeft) != 0;
        }
 
        private void TrackPop() => runtrackpos++;
 
        /// <summary>Pop framesize items from the backtracking stack.</summary>
        private void TrackPop(int framesize) => runtrackpos += framesize;
 
        /// <summary>Peek at the item popped from the stack.</summary>
        /// <remarks>
        /// If you want to get and pop the top item from the stack, you do `TrackPop(); TrackPeek();`.
        /// </remarks>
        private int TrackPeek() => runtrack![runtrackpos - 1];
 
        /// <summary>Get the ith element down on the backtracking stack.</summary>
        private int TrackPeek(int i) => runtrack![runtrackpos - i - 1];
 
        /// <summary>Push onto the grouping stack.</summary>
        private void StackPush(int i1) => runstack![--runstackpos] = i1;
 
        private void StackPush(int i1, int i2)
        {
            int[] localrunstack = runstack!;
            int localrunstackpos = runstackpos;
 
            localrunstack[--localrunstackpos] = i1;
            localrunstack[--localrunstackpos] = i2;
 
            runstackpos = localrunstackpos;
        }
 
        private void StackPop() => runstackpos++;
 
        // pop framesize items from the grouping stack
        private void StackPop(int framesize) => runstackpos += framesize;
 
        /// <summary>
        /// Technically we are actually peeking at items already popped.  So if you want to
        /// get and pop the top item from the stack, you do `StackPop(); StackPeek();`.
        /// </summary>
        private int StackPeek() => runstack![runstackpos - 1];
 
        /// <summary>Get the ith element down on the grouping stack.</summary>
        private int StackPeek(int i) => runstack![runstackpos - i - 1];
 
        private int Operand(int i) => _code.Codes[_codepos + i + 1];
 
        private int Bump() => _rightToLeft ? -1 : 1;
 
        private int Forwardchars() => _rightToLeft ? runtextpos : runtextend - runtextpos;
 
        private char Forwardcharnext(ReadOnlySpan<char> inputSpan)
        {
            int i = _rightToLeft ? --runtextpos : runtextpos++;
            return inputSpan[i];
        }
 
        private bool MatchString(string str, ReadOnlySpan<char> inputSpan)
        {
            int c = str.Length;
            int pos;
 
            if (!_rightToLeft)
            {
                if (inputSpan.Length - runtextpos < c)
                {
                    return false;
                }
 
                pos = runtextpos + c;
            }
            else
            {
                if (runtextpos < c)
                {
                    return false;
                }
 
                pos = runtextpos;
            }
 
            while (c != 0)
            {
                if (str[--c] != inputSpan[--pos])
                {
                    return false;
                }
            }
 
            if (!_rightToLeft)
            {
                pos += str.Length;
            }
 
            runtextpos = pos;
 
            return true;
        }
 
        private bool MatchRef(int index, int length, ReadOnlySpan<char> inputSpan, bool caseInsensitive)
        {
            int pos;
            if (!_rightToLeft)
            {
                if (inputSpan.Length - runtextpos < length)
                {
                    return false;
                }
 
                pos = runtextpos + length;
            }
            else
            {
                if (runtextpos < length)
                {
                    return false;
                }
 
                pos = runtextpos;
            }
 
            int cmpos = index + length;
            int c = length;
 
            if (!caseInsensitive)
            {
                while (c-- != 0)
                {
                    if (inputSpan[--cmpos] != inputSpan[--pos])
                    {
                        return false;
                    }
                }
            }
            else
            {
                while (c-- != 0)
                {
                    char backreferenceChar = inputSpan[--cmpos];
                    char currentChar = inputSpan[--pos];
 
                    // If we are evaluating a backreference case-insensitive match, we first check if the characters at the position
                    // are the same character.
                    if (backreferenceChar != currentChar)
                    {
                        // If they are not the same character, then we need to check if the backreference character participates in case conversion
                        // and if so, we need to fetch the case equivalences from our casing tables.
                        Debug.Assert(_culture != null, "If the pattern has backreferences and is IgnoreCase, then _culture must not be null.");
                        if (!RegexCaseEquivalences.TryFindCaseEquivalencesForCharWithIBehavior(backreferenceChar, _culture, ref _caseBehavior, out ReadOnlySpan<char> equivalences) ||
                            equivalences.IndexOf(inputSpan[pos]) < 0)
                        {
                            // The backreference character doesn't participate in case conversions, or it does but the input character
                            // doesn't match any of its equivalents.  Either way, we fail to match.
                            return false;
                        }
                    }
                }
            }
 
            if (!_rightToLeft)
            {
                pos += length;
            }
 
            runtextpos = pos;
 
            return true;
        }
 
        private void Backwardnext() => runtextpos += _rightToLeft ? 1 : -1;
 
        protected internal override void Scan(ReadOnlySpan<char> text)
        {
            Debug.Assert(runregex is not null);
            Debug.Assert(runtrack is not null);
            Debug.Assert(runstack is not null);
            Debug.Assert(runcrawl is not null);
 
            if (runregex.RightToLeft)
            {
                while (_code.FindOptimizations.TryFindNextStartingPositionRightToLeft(text, ref runtextpos, runtextstart))
                {
                    CheckTimeout();
 
                    if (TryMatchAtCurrentPosition(text) || runtextpos == 0)
                    {
                        return;
                    }
 
                    // Reset state for another iteration.
                    runtrackpos = runtrack.Length;
                    runstackpos = runstack.Length;
                    runcrawlpos = runcrawl.Length;
                    runtextpos--;
                }
            }
            else
            {
                while (_code.FindOptimizations.TryFindNextStartingPositionLeftToRight(text, ref runtextpos, runtextstart))
                {
                    CheckTimeout();
 
                    if (TryMatchAtCurrentPosition(text) || runtextpos == text.Length)
                    {
                        return;
                    }
 
                    // Reset state for another iteration.
                    runtrackpos = runtrack.Length;
                    runstackpos = runstack.Length;
                    runcrawlpos = runcrawl.Length;
                    runtextpos++;
                }
            }
        }
 
        private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
        {
            SetOperator((RegexOpcode)_code.Codes[0]);
            _codepos = 0;
            int advance = -1;
 
            while (true)
            {
                if (advance >= 0)
                {
                    // Single common Advance call to reduce method size; and single method inline point.
                    // Details at https://github.com/dotnet/corefx/pull/25096.
                    Advance(advance);
                    advance = -1;
                }
 
                switch (_operator)
                {
                    case RegexOpcode.Stop:
                        return runmatch!.FoundMatch;
 
                    case RegexOpcode.Nothing:
                        break;
 
                    case RegexOpcode.Goto:
                        Goto(Operand(0));
                        continue;
 
                    case RegexOpcode.TestBackreference:
                        if (!IsMatched(Operand(0)))
                        {
                            break;
                        }
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Lazybranch:
                        TrackPush(runtextpos);
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Lazybranch | RegexOpcode.Backtracking:
                        TrackPop();
                        runtextpos = TrackPeek();
                        Goto(Operand(0));
                        continue;
 
                    case RegexOpcode.Setmark:
                        StackPush(runtextpos);
                        TrackPush();
                        advance = 0;
                        continue;
 
                    case RegexOpcode.Nullmark:
                        StackPush(-1);
                        TrackPush();
                        advance = 0;
                        continue;
 
                    case RegexOpcode.Setmark | RegexOpcode.Backtracking:
                    case RegexOpcode.Nullmark | RegexOpcode.Backtracking:
                        StackPop();
                        break;
 
                    case RegexOpcode.Getmark:
                        StackPop();
                        TrackPush(StackPeek());
                        runtextpos = StackPeek();
                        advance = 0;
                        continue;
 
                    case RegexOpcode.Getmark | RegexOpcode.Backtracking:
                        TrackPop();
                        StackPush(TrackPeek());
                        break;
 
                    case RegexOpcode.Capturemark:
                        if (Operand(1) != -1 && !IsMatched(Operand(1)))
                        {
                            break;
                        }
                        StackPop();
                        if (Operand(1) != -1)
                        {
                            TransferCapture(Operand(0), Operand(1), StackPeek(), runtextpos);
                        }
                        else
                        {
                            Capture(Operand(0), StackPeek(), runtextpos);
                        }
                        TrackPush(StackPeek());
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Capturemark | RegexOpcode.Backtracking:
                        TrackPop();
                        StackPush(TrackPeek());
                        Uncapture();
                        if (Operand(0) != -1 && Operand(1) != -1)
                        {
                            Uncapture();
                        }
                        break;
 
                    case RegexOpcode.Branchmark:
                        StackPop();
                        if (runtextpos != StackPeek())
                        {
                            // Nonempty match -> loop now
                            TrackPush(StackPeek(), runtextpos); // Save old mark, textpos
                            StackPush(runtextpos);              // Make new mark
                            Goto(Operand(0));                   // Loop
                        }
                        else
                        {
                            // Empty match -> straight now
                            TrackPush2(StackPeek());            // Save old mark
                            advance = 1;                        // Straight
                        }
                        continue;
 
                    case RegexOpcode.Branchmark | RegexOpcode.Backtracking:
                        TrackPop(2);
                        StackPop();
                        runtextpos = TrackPeek(1); // Recall position
                        TrackPush2(TrackPeek());   // Save old mark
                        advance = 1;               // Straight
                        continue;
 
                    case RegexOpcode.Branchmark | RegexOpcode.BacktrackingSecond:
                        TrackPop();
                        StackPush(TrackPeek()); // Recall old mark
                        break;                  // Backtrack
 
                    case RegexOpcode.Lazybranchmark:
                        // We hit this the first time through a lazy loop and after each
                        // successful match of the inner expression.  It simply continues
                        // on and doesn't loop.
                        StackPop();
                        {
                            int oldMarkPos = StackPeek();
                            if (runtextpos != oldMarkPos)
                            {
                                // Nonempty match -> try to loop again by going to 'back' state
                                if (oldMarkPos != -1)
                                {
                                    TrackPush(oldMarkPos, runtextpos); // Save old mark, textpos
                                }
                                else
                                {
                                    TrackPush(runtextpos, runtextpos);
                                }
                            }
                            else
                            {
                                // The inner expression found an empty match, so we'll go directly to 'back2' if we
                                // backtrack.  In this case, we need to push something on the stack, since back2 pops.
                                // However, in the case of ()+? or similar, this empty match may be legitimate, so push the text
                                // position associated with that empty match.
                                StackPush(oldMarkPos);
                                TrackPush2(StackPeek()); // Save old mark
                            }
                        }
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Lazybranchmark | RegexOpcode.Backtracking:
                        {
                            // After the first time, Lazybranchmark | RegexOpcode.Back occurs
                            // with each iteration of the loop, and therefore with every attempted
                            // match of the inner expression.  We'll try to match the inner expression,
                            // then go back to Lazybranchmark if successful.  If the inner expression
                            // fails, we go to Lazybranchmark | RegexOpcode.Back2
                            TrackPop(2);
                            int pos = TrackPeek(1);
                            TrackPush2(TrackPeek()); // Save old mark
                            StackPush(pos);          // Make new mark
                            runtextpos = pos;        // Recall position
                            Goto(Operand(0));        // Loop
                        }
                        continue;
 
                    case RegexOpcode.Lazybranchmark | RegexOpcode.BacktrackingSecond:
                        // The lazy loop has failed.  We'll do a true backtrack and
                        // start over before the lazy loop.
                        StackPop();
                        TrackPop();
                        StackPush(TrackPeek()); // Recall old mark
                        break;
 
                    case RegexOpcode.Setcount:
                        StackPush(runtextpos, Operand(0));
                        TrackPush();
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Nullcount:
                        StackPush(-1, Operand(0));
                        TrackPush();
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Setcount | RegexOpcode.Backtracking:
                    case RegexOpcode.Nullcount | RegexOpcode.Backtracking:
                    case RegexOpcode.Setjump | RegexOpcode.Backtracking:
                        StackPop(2);
                        break;
 
                    case RegexOpcode.Branchcount:
                        // StackPush:
                        //  0: Mark
                        //  1: Count
                        StackPop(2);
                        {
                            int mark = StackPeek();
                            int count = StackPeek(1);
                            int matched = runtextpos - mark;
                            if (count >= Operand(1) || (matched == 0 && count >= 0))
                            {
                                // Max loops or empty match -> straight now
                                TrackPush2(mark, count); // Save old mark, count
                                advance = 2;             // Straight
                            }
                            else
                            {
                                // Nonempty match -> count+loop now
                                TrackPush(mark);                  // remember mark
                                StackPush(runtextpos, count + 1); // Make new mark, incr count
                                Goto(Operand(0));                 // Loop
                            }
                        }
                        continue;
 
                    case RegexOpcode.Branchcount | RegexOpcode.Backtracking:
                        // TrackPush:
                        //  0: Previous mark
                        // StackPush:
                        //  0: Mark (= current pos, discarded)
                        //  1: Count
                        TrackPop();
                        StackPop(2);
                        if (StackPeek(1) > 0)
                        {
                            // Positive -> can go straight
                            runtextpos = StackPeek();                  // Zap to mark
                            TrackPush2(TrackPeek(), StackPeek(1) - 1); // Save old mark, old count
                            advance = 2;                               // Straight
                            continue;
                        }
                        StackPush(TrackPeek(), StackPeek(1) - 1);      // Recall old mark, old count
                        break;
 
                    case RegexOpcode.Branchcount | RegexOpcode.BacktrackingSecond:
                        // TrackPush:
                        //  0: Previous mark
                        //  1: Previous count
                        TrackPop(2);
                        StackPush(TrackPeek(), TrackPeek(1)); // Recall old mark, old count
                        break;                                // Backtrack
 
                    case RegexOpcode.Lazybranchcount:
                        // StackPush:
                        //  0: Mark
                        //  1: Count
                        StackPop(2);
                        {
                            int mark = StackPeek();
                            int count = StackPeek(1);
                            if (count < 0)
                            {
                                // Negative count -> loop now
                                TrackPush2(mark);                 // Save old mark
                                StackPush(runtextpos, count + 1); // Make new mark, incr count
                                Goto(Operand(0));                 // Loop
                            }
                            else
                            {
                                // Nonneg count -> straight now
                                TrackPush(mark, count, runtextpos); // Save mark, count, position
                                advance = 2;                        // Straight
                            }
                        }
                        continue;
 
                    case RegexOpcode.Lazybranchcount | RegexOpcode.Backtracking:
                        // TrackPush:
                        //  0: Mark
                        //  1: Count
                        //  2: Textpos
                        TrackPop(3);
                        {
                            int mark = TrackPeek();
                            int textpos = TrackPeek(2);
                            if (TrackPeek(1) < Operand(1) && textpos != mark)
                            {
                                // Under limit and not empty match -> loop
                                runtextpos = textpos;                 // Recall position
                                StackPush(textpos, TrackPeek(1) + 1); // Make new mark, incr count
                                TrackPush2(mark);                     // Save old mark
                                Goto(Operand(0));                     // Loop
                                continue;
                            }
                            else
                            {
                                // Max loops or empty match -> backtrack
                                StackPush(TrackPeek(), TrackPeek(1)); // Recall old mark, count
                                break;                                // backtrack
                            }
                        }
 
                    case RegexOpcode.Lazybranchcount | RegexOpcode.BacktrackingSecond:
                        // TrackPush:
                        //  0: Previous mark
                        // StackPush:
                        //  0: Mark (== current pos, discarded)
                        //  1: Count
                        TrackPop();
                        StackPop(2);
                        StackPush(TrackPeek(), StackPeek(1) - 1); // Recall old mark, count
                        break;                                    // Backtrack
 
                    case RegexOpcode.Setjump:
                        CheckTimeout(); // to ensure that positive/negative lookarounds have a timeout check
                        StackPush(Trackpos(), Crawlpos());
                        TrackPush();
                        advance = 0;
                        continue;
 
                    case RegexOpcode.Backjump:
                        // StackPush:
                        //  0: Saved trackpos
                        //  1: Crawlpos
                        StackPop(2);
                        Trackto(StackPeek());
                        while (Crawlpos() != StackPeek(1))
                        {
                            Uncapture();
                        }
                        break;
 
                    case RegexOpcode.Forejump:
                        // StackPush:
                        //  0: Saved trackpos
                        //  1: Crawlpos
                        StackPop(2);
                        Trackto(StackPeek());
                        TrackPush(StackPeek(1));
                        advance = 0;
                        continue;
 
                    case RegexOpcode.Forejump | RegexOpcode.Backtracking:
                        // TrackPush:
                        //  0: Crawlpos
                        TrackPop();
                        while (Crawlpos() != TrackPeek())
                        {
                            Uncapture();
                        }
                        break;
 
                    case RegexOpcode.Bol:
                        {
                            int m1 = runtextpos - 1;
                            if ((uint)m1 < (uint)inputSpan.Length && inputSpan[m1] != '\n')
                            {
                                break;
                            }
                            advance = 0;
                            continue;
                        }
 
                    case RegexOpcode.Eol:
                        {
                            int runtextpos = this.runtextpos;
                            if ((uint)runtextpos < (uint)inputSpan.Length && inputSpan[runtextpos] != '\n')
                            {
                                break;
                            }
                            advance = 0;
                            continue;
                        }
 
                    case RegexOpcode.Boundary:
                        if (!IsBoundary(inputSpan, runtextpos))
                        {
                            break;
                        }
                        advance = 0;
                        continue;
 
                    case RegexOpcode.NonBoundary:
                        if (IsBoundary(inputSpan, runtextpos))
                        {
                            break;
                        }
                        advance = 0;
                        continue;
 
                    case RegexOpcode.ECMABoundary:
                        if (!IsECMABoundary(inputSpan, runtextpos))
                        {
                            break;
                        }
                        advance = 0;
                        continue;
 
                    case RegexOpcode.NonECMABoundary:
                        if (IsECMABoundary(inputSpan, runtextpos))
                        {
                            break;
                        }
                        advance = 0;
                        continue;
 
                    case RegexOpcode.Beginning:
                        if (runtextpos > 0)
                        {
                            break;
                        }
                        advance = 0;
                        continue;
 
                    case RegexOpcode.Start:
                        if (runtextpos != runtextstart)
                        {
                            break;
                        }
                        advance = 0;
                        continue;
 
                    case RegexOpcode.EndZ:
                        {
                            int runtextpos = this.runtextpos;
                            if (runtextpos < inputSpan.Length - 1 || ((uint)runtextpos < (uint)inputSpan.Length && inputSpan[runtextpos] != '\n'))
                            {
                                break;
                            }
                            advance = 0;
                            continue;
                        }
 
                    case RegexOpcode.End:
                        if (runtextpos < inputSpan.Length)
                        {
                            break;
                        }
                        advance = 0;
                        continue;
 
                    case RegexOpcode.One:
                        if (Forwardchars() < 1 || Forwardcharnext(inputSpan) != (char)Operand(0))
                        {
                            break;
                        }
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Notone:
                        if (Forwardchars() < 1 || Forwardcharnext(inputSpan) == (char)Operand(0))
                        {
                            break;
                        }
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Set:
                        if (Forwardchars() < 1)
                        {
                            break;
                        }
                        else
                        {
                            int operand = Operand(0);
                            if (!RegexCharClass.CharInClass(Forwardcharnext(inputSpan), _code.Strings[operand], ref _code.StringsAsciiLookup[operand]))
                            {
                                break;
                            }
                        }
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Multi:
                        if (!MatchString(_code.Strings[Operand(0)], inputSpan))
                        {
                            break;
                        }
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Backreference:
                    case RegexOpcode.Backreference | RegexOpcode.CaseInsensitive:
                        {
                            int capnum = Operand(0);
                            if (IsMatched(capnum))
                            {
                                if (!MatchRef(MatchIndex(capnum), MatchLength(capnum), inputSpan, (_operator & RegexOpcode.CaseInsensitive) != 0))
                                {
                                    break;
                                }
                            }
                            else
                            {
                                if ((runregex!.roptions & RegexOptions.ECMAScript) == 0)
                                {
                                    break;
                                }
                            }
                        }
                        advance = 1;
                        continue;
 
                    case RegexOpcode.Onerep:
                        {
                            int c = Operand(1);
                            if (Forwardchars() < c)
                            {
                                break;
                            }
 
                            char ch = (char)Operand(0);
                            while (c-- > 0)
                            {
                                if (Forwardcharnext(inputSpan) != ch)
                                {
                                    goto BreakBackward;
                                }
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Notonerep:
                        {
                            int c = Operand(1);
                            if (Forwardchars() < c)
                            {
                                break;
                            }
 
                            char ch = (char)Operand(0);
                            while (c-- > 0)
                            {
                                if (Forwardcharnext(inputSpan) == ch)
                                {
                                    goto BreakBackward;
                                }
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Setrep:
                        {
                            int c = Operand(1);
                            if (Forwardchars() < c)
                            {
                                break;
                            }
 
                            int operand0 = Operand(0);
                            string set = _code.Strings[operand0];
                            ref uint[]? setLookup = ref _code.StringsAsciiLookup[operand0];
 
                            while (c-- > 0)
                            {
                                if (!RegexCharClass.CharInClass(Forwardcharnext(inputSpan), set, ref setLookup))
                                {
                                    goto BreakBackward;
                                }
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Oneloop:
                    case RegexOpcode.Oneloopatomic:
                        {
                            int len = Math.Min(Operand(1), Forwardchars());
                            char ch = (char)Operand(0);
                            int i;
 
                            for (i = len; i > 0; i--)
                            {
                                if (Forwardcharnext(inputSpan) != ch)
                                {
                                    Backwardnext();
                                    break;
                                }
                            }
 
                            if (len > i && _operator == RegexOpcode.Oneloop)
                            {
                                TrackPush(len - i - 1, runtextpos - Bump());
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Notoneloop:
                    case RegexOpcode.Notoneloopatomic:
                        {
                            int len = Math.Min(Operand(1), Forwardchars());
                            char ch = (char)Operand(0);
                            int i;
 
                            if (!_rightToLeft)
                            {
                                // We're left-to-right, so we can employ the vectorized IndexOf
                                // to search for the character.
                                i = inputSpan.Slice(runtextpos, len).IndexOf(ch);
                                if (i == -1)
                                {
                                    runtextpos += len;
                                    i = 0;
                                }
                                else
                                {
                                    runtextpos += i;
                                    i = len - i;
                                }
                            }
                            else
                            {
                                for (i = len; i > 0; i--)
                                {
                                    if (Forwardcharnext(inputSpan) == ch)
                                    {
                                        Backwardnext();
                                        break;
                                    }
                                }
                            }
 
                            if (len > i && _operator == RegexOpcode.Notoneloop)
                            {
                                TrackPush(len - i - 1, runtextpos - Bump());
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Setloop:
                    case RegexOpcode.Setloopatomic:
                        {
                            int len = Math.Min(Operand(1), Forwardchars());
                            int operand0 = Operand(0);
                            string set = _code.Strings[operand0];
                            ref uint[]? setLookup = ref _code.StringsAsciiLookup[operand0];
                            int i;
 
                            for (i = len; i > 0; i--)
                            {
                                if (!RegexCharClass.CharInClass(Forwardcharnext(inputSpan), set, ref setLookup))
                                {
                                    Backwardnext();
                                    break;
                                }
                            }
 
                            if (len > i && _operator == RegexOpcode.Setloop)
                            {
                                TrackPush(len - i - 1, runtextpos - Bump());
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Oneloop | RegexOpcode.Backtracking:
                    case RegexOpcode.Notoneloop | RegexOpcode.Backtracking:
                    case RegexOpcode.Setloop | RegexOpcode.Backtracking:
                        TrackPop(2);
                        {
                            int i = TrackPeek();
                            int pos = TrackPeek(1);
                            runtextpos = pos;
                            if (i > 0)
                            {
                                TrackPush(i - 1, pos - Bump());
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Onelazy:
                    case RegexOpcode.Notonelazy:
                    case RegexOpcode.Setlazy:
                        {
                            int c = Math.Min(Operand(1), Forwardchars());
                            if (c > 0)
                            {
                                TrackPush(c - 1, runtextpos);
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Onelazy | RegexOpcode.Backtracking:
                        TrackPop(2);
                        {
                            int pos = TrackPeek(1);
                            runtextpos = pos;
 
                            if (Forwardcharnext(inputSpan) != (char)Operand(0))
                            {
                                break;
                            }
 
                            int i = TrackPeek();
                            if (i > 0)
                            {
                                TrackPush(i - 1, pos + Bump());
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Notonelazy | RegexOpcode.Backtracking:
                        TrackPop(2);
                        {
                            int pos = TrackPeek(1);
                            runtextpos = pos;
 
                            if (Forwardcharnext(inputSpan) == (char)Operand(0))
                            {
                                break;
                            }
 
                            int i = TrackPeek();
                            if (i > 0)
                            {
                                TrackPush(i - 1, pos + Bump());
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.Setlazy | RegexOpcode.Backtracking:
                        TrackPop(2);
                        {
                            int pos = TrackPeek(1);
                            runtextpos = pos;
 
                            int operand0 = Operand(0);
                            if (!RegexCharClass.CharInClass(Forwardcharnext(inputSpan), _code.Strings[operand0], ref _code.StringsAsciiLookup[operand0]))
                            {
                                break;
                            }
 
                            int i = TrackPeek();
                            if (i > 0)
                            {
                                TrackPush(i - 1, pos + Bump());
                            }
                        }
                        advance = 2;
                        continue;
 
                    case RegexOpcode.UpdateBumpalong:
                        // UpdateBumpalong should only exist in the code stream at such a point where the root
                        // of the backtracking stack contains the runtextpos from the start of this Go call. Replace
                        // that tracking value with the current runtextpos value if it's greater.
                        {
                            Debug.Assert(!_rightToLeft, "UpdateBumpalongs aren't added for RTL");
                            ref int trackingpos = ref runtrack![runtrack.Length - 1];
                            if (trackingpos < runtextpos)
                            {
                                trackingpos = runtextpos;
                            }
                            advance = 0;
                            continue;
                        }
 
                    default:
                        Debug.Fail($"Unimplemented state: {_operator:X8}");
                        break;
                }
 
            BreakBackward:
                Backtrack();
            }
        }
    }
}