in src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexInterpreter.cs [369:1149]
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();
}
}