From 57587bec160025805c904536f840cc0d2c36864c Mon Sep 17 00:00:00 2001
From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com>
Date: Mon, 14 Sep 2026 18:21:23 +0800
Subject: [PATCH] use new exit lable
---
.../Text/TextRegexSourceGen.ExitLabel.cs | 28 +-
.../Text/TextRegexSourceGen.GenClass.cs | 246 +++++++++---------
.../Text/TextRegexSourceGen.GenContext.cs | 10 +-
.../Text/TextRegexSourceGen.GenSpecial.cs | 62 +----
.../Text/TextRegexSourceGen.cs | 10 +-
test/StgSharpDebug/TestRegexGenerate.cs | 10 +-
6 files changed, 169 insertions(+), 197 deletions(-)
diff --git a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.ExitLabel.cs b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.ExitLabel.cs
index 766bd8e7..23a9b1db 100644
--- a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.ExitLabel.cs
+++ b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.ExitLabel.cs
@@ -16,12 +16,15 @@ namespace Nitload.RegularAnalysis.Text
internal partial class TextRegexSourceGen
{
- internal enum ExitMode
+ internal enum ExitKind
{
- Break,
- Continue,
- Goto,
+ MatchFailed,
+ MatchEnd,
+ CountRetry,
+ CountResume,
+ AltNext,
+ AltMatched,
}
@@ -29,25 +32,28 @@ namespace Nitload.RegularAnalysis.Text
{
public ExitLabel(
- ExitMode mode,
+ ExitKind kind,
int order
)
{
- Mode = mode;
+ Kind = kind;
LabelOrder = order;
}
- public ExitMode Mode { get; init; }
+ public ExitKind Kind { get; init; }
public int LabelOrder { get; init; }
public string GetLabelName()
{
- return Mode switch
+ return Kind switch
{
- ExitMode.Break => $@"__break_{LabelOrder}",
- ExitMode.Continue => $@"__continue_{LabelOrder}",
- ExitMode.Goto => $@"__goto_{LabelOrder}",
+ ExitKind.MatchFailed => $@"__match_failed_{LabelOrder}",
+ ExitKind.MatchEnd => $@"__match_end_{LabelOrder}",
+ ExitKind.CountRetry => $@"__count_retry_{LabelOrder}",
+ ExitKind.CountResume => $@"__count_resume_{LabelOrder}",
+ ExitKind.AltNext => $@"__alt_next_{LabelOrder}",
+ ExitKind.AltMatched => $@"__alt_matched_{LabelOrder}",
_ => throw new NotImplementedException(),
};
}
diff --git a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs
index 6791bf37..87fd828b 100644
--- a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs
+++ b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs
@@ -14,20 +14,31 @@ namespace Nitload.RegularAnalysis.Text
internal partial class TextRegexSourceGen
{
+ ///
+ /// Emits a subtree and updates the retry target for the rest of the current path.
+ ///
+ ///
+ /// local_fail is the owning region's fallback. backtrack starts at that fallback and
+ /// advances when a COUNT registers a retry point. Returning from recursion does not
+ /// discard it. Mutually exclusive branches must use separate backtrack variables.
+ ///
internal static void GenerateMethodSource(
RegexAstNode ast,
in SequenceEmitter sc,
in List> func_list,
in SourceGenContext context,
- in ExitLabel exit_label = default,
+ in ExitLabel local_fail,
+ ref ExitLabel backtrack,
bool is_find = false
)
{
+ // local_fail belongs to this region. COUNT updates only the current path's
+ // backtrack target; CONCAT passes that update on to every later sibling.
RegexAstNode root = ast;
if (root.Label == RegexElementLabel.EPSILON) {
return;
}
- if (TryGenerateSpecialSource(root, sc, func_list, context, exit_label)) {
+ if (TryGenerateSpecialSource(root, sc, func_list, context, local_fail, ref backtrack)) {
return;
}
RegexElementLabel op = root.Label & (RegexElementLabel.VAST_OPERATOR);
@@ -36,16 +47,16 @@ namespace Nitload.RegularAnalysis.Text
switch (op)
{
case RegexElementLabel.CONCAT:
- GenerateConcat(root, sc, func_list, context, exit_label);
+ GenerateConcat(root, sc, func_list, context, local_fail, ref backtrack);
break;
case RegexElementLabel.COUNT:
- GenerateCount(root, sc, func_list, context, exit_label);
+ GenerateCount(root, sc, func_list, context, local_fail, ref backtrack);
break;
case RegexElementLabel.GROUP_BEGIN:
- GenerateGroup(root, sc, func_list, context, exit_label);
+ GenerateGroup(root, sc, func_list, context, local_fail, ref backtrack);
break;
case RegexElementLabel.ALT:
- GenerateAlt(root, sc, func_list, context, exit_label);
+ GenerateAlt(root, sc, func_list, context, backtrack);
break;
default:
@@ -59,13 +70,13 @@ namespace Nitload.RegularAnalysis.Text
switch (op)
{
case RegexElementLabel.UNIT:
- GenerateUnit(root, sc, context, exit_label);
+ GenerateUnit(root, sc, context, backtrack);
break;
case RegexElementLabel.UNIT_SET:
- GenerateUnitSet(root, sc, context, exit_label);
+ GenerateUnitSet(root, sc, context, backtrack);
break;
case RegexElementLabel.UNIT_SPAN:
- GenerateUnitSpan(root, sc, context, exit_label);
+ GenerateUnitSpan(root, sc, context, backtrack);
break;
default:
break;
@@ -220,11 +231,9 @@ namespace Nitload.RegularAnalysis.Text
int max,
bool is_greedy,
[NotNull] in SequenceEmitter sc,
- in List> func_list,
in SourceGenContext context,
in ExitLabel exit,
- IReadOnlyList? continuation = null,
- int next = 0
+ ref ExitLabel backtrack
)
{
if (min < 0 || (max >= 0 && max < min)) {
@@ -303,61 +312,54 @@ namespace Nitload.RegularAnalysis.Text
}
int additional_limit = max < 0 ? -1 : max - min;
- bool has_continuation = continuation is not null && next < continuation.Count;
- bool can_retry = has_continuation && additional_limit != 0;
+ if (additional_limit == 0) {
+ return;
+ }
+
+ // Save the optional portion once. Its counter is shared by scanning and retries.
string additional_entry = $"__additional_entry_{scratch_scope_index}";
- if (can_retry) {
- _ = sc.AppendLine($"{ROS_char} {additional_entry} = {remain_span};");
- }
- if (can_retry || (is_greedy && additional_limit > 0)) {
- _ = sc.AppendLine($"int {additional_count} = 0;");
- }
- if (is_greedy && additional_limit != 0)
+ ExitLabel retry = context.RequestExitLabel(ExitKind.CountRetry);
+ ExitLabel resume = context.RequestExitLabel(ExitKind.CountResume);
+ _ = sc.AppendLine($"{ROS_char} {additional_entry} = {remain_span};")
+ .AppendLine($"int {additional_count} = 0;");
+ if (is_greedy)
{
string limit = additional_limit < 0 ? string.Empty :
$"{additional_count} < {additional_limit} && ";
_ = sc.AppendLine($"while ({limit}!{remain_span}.IsEmpty && {remain_span}[0] == {expected_literal})")
.AppendLine("{")
- .AppendLine($"{remain_span} = {remain_span}.Slice(1);");
- if (can_retry || additional_limit > 0) {
- _ = sc.AppendLine($"{additional_count}++;");
- }
- _ = sc.AppendLine("}");
+ .AppendLine($"{remain_span} = {remain_span}.Slice(1);")
+ .AppendLine($"{additional_count}++;")
+ .AppendLine("}");
}
- if (can_retry)
+
+ // Initial success skips retry handling. Later failures jump back here from anywhere
+ // in this path; exhaustion forwards to the target inherited before this COUNT.
+ _ = sc.AppendLine($"goto {resume.GetLabelName()};")
+ .AppendLine($"{retry.GetLabelName()}: {{}}");
+ if (is_greedy)
{
- // The same optional count drives scanning and retries. Required units stay consumed.
- ExitLabel retry = context.RequestExitLabel(ExitMode.Goto);
- _ = sc.AppendLine("while (true)")
+ _ = sc.AppendLine($"if ({additional_count} == 0)")
.AppendLine("{")
- .AppendLine($"{remain_span} = {additional_entry}.Slice({additional_count});");
- GenerateConcatTail(continuation!, next, sc, func_list, context, retry);
- _ = sc.AppendLine("break;")
- .AppendLine($"{retry.GetLabelName()}: {{}}");
- if (is_greedy)
- {
- _ = sc.AppendLine($"if ({additional_count} == 0)")
- .AppendLine("{")
- .AppendLine($"{remain_span} = {additional_entry};")
- .AppendLine($"goto {exit_label_node};")
- .AppendLine("}")
- .AppendLine($"{additional_count}--;");
- } else
- {
- string limit = additional_limit < 0 ? string.Empty :
- $"{additional_count} == {additional_limit} || ";
- _ = sc.AppendLine($"{remain_span} = {additional_entry}.Slice({additional_count});")
- .AppendLine($"if ({limit}{remain_span}.IsEmpty || {remain_span}[0] != {expected_literal})")
- .AppendLine("{")
- .AppendLine($"{remain_span} = {additional_entry};")
- .AppendLine($"goto {exit_label_node};")
- .AppendLine("}")
- .AppendLine($"{additional_count}++;");
- }
- _ = sc.AppendLine("}");
- } else if (has_continuation) {
- GenerateConcatTail(continuation!, next, sc, func_list, context, exit);
+ .AppendLine($"{remain_span} = {additional_entry};")
+ .AppendLine($"goto {exit_label_node};")
+ .AppendLine("}")
+ .AppendLine($"{additional_count}--;");
+ } else
+ {
+ string limit = additional_limit < 0 ? string.Empty :
+ $"{additional_count} == {additional_limit} || ";
+ _ = sc.AppendLine($"{remain_span} = {additional_entry}.Slice({additional_count});")
+ .AppendLine($"if ({limit}{remain_span}.IsEmpty || {remain_span}[0] != {expected_literal})")
+ .AppendLine("{")
+ .AppendLine($"{remain_span} = {additional_entry};")
+ .AppendLine($"goto {exit_label_node};")
+ .AppendLine("}")
+ .AppendLine($"{additional_count}++;");
}
+ _ = sc.AppendLine($"{remain_span} = {additional_entry}.Slice({additional_count});")
+ .AppendLine($"{resume.GetLabelName()}: {{}}");
+ backtrack = retry;
}
private static void GenerateUnitSetCount(
@@ -440,11 +442,9 @@ namespace Nitload.RegularAnalysis.Text
int max,
bool is_greedy,
[NotNull] in SequenceEmitter sc,
- in List> func_list,
in SourceGenContext context,
in ExitLabel exit,
- IReadOnlyList? continuation = null,
- int next = 0
+ ref ExitLabel backtrack
)
{
if (min < 0 || (max >= 0 && max < min)) {
@@ -554,61 +554,54 @@ namespace Nitload.RegularAnalysis.Text
}
int additional_limit = max < 0 ? -1 : max - min;
- bool has_continuation = continuation is not null && next < continuation.Count;
- bool can_retry = has_continuation && additional_limit != 0;
+ if (additional_limit == 0) {
+ return;
+ }
+
+ // Save the optional portion once. Its counter is shared by scanning and retries.
string additional_entry = $"__additional_entry_{scratch_scope_index}";
- if (can_retry) {
- _ = sc.AppendLine($"{ROS_char} {additional_entry} = {remain_span};");
- }
- if (can_retry || (is_greedy && additional_limit > 0)) {
- _ = sc.AppendLine($"int {additional_count} = 0;");
- }
- if (is_greedy && additional_limit != 0)
+ ExitLabel retry = context.RequestExitLabel(ExitKind.CountRetry);
+ ExitLabel resume = context.RequestExitLabel(ExitKind.CountResume);
+ _ = sc.AppendLine($"{ROS_char} {additional_entry} = {remain_span};")
+ .AppendLine($"int {additional_count} = 0;");
+ if (is_greedy)
{
string limit = additional_limit < 0 ? string.Empty :
$"{additional_count} < {additional_limit} && ";
_ = sc.AppendLine($"while ({limit}{remain_span}.{starts_with}({expected_literal}))")
.AppendLine("{")
- .AppendLine($"{remain_span} = {remain_span}.Slice({literal.Length});");
- if (can_retry || additional_limit > 0) {
- _ = sc.AppendLine($"{additional_count}++;");
- }
- _ = sc.AppendLine("}");
+ .AppendLine($"{remain_span} = {remain_span}.Slice({literal.Length});")
+ .AppendLine($"{additional_count}++;")
+ .AppendLine("}");
}
- if (can_retry)
+
+ // Initial success skips retry handling. Later failures jump back here from anywhere
+ // in this path; exhaustion forwards to the target inherited before this COUNT.
+ _ = sc.AppendLine($"goto {resume.GetLabelName()};")
+ .AppendLine($"{retry.GetLabelName()}: {{}}");
+ if (is_greedy)
{
- // Each retry moves by a complete literal using the scan's own optional counter.
- ExitLabel retry = context.RequestExitLabel(ExitMode.Goto);
- _ = sc.AppendLine("while (true)")
+ _ = sc.AppendLine($"if ({additional_count} == 0)")
.AppendLine("{")
- .AppendLine($"{remain_span} = {additional_entry}.Slice({additional_count} * {literal.Length});");
- GenerateConcatTail(continuation!, next, sc, func_list, context, retry);
- _ = sc.AppendLine("break;")
- .AppendLine($"{retry.GetLabelName()}: {{}}");
- if (is_greedy)
- {
- _ = sc.AppendLine($"if ({additional_count} == 0)")
- .AppendLine("{")
- .AppendLine($"{remain_span} = {additional_entry};")
- .AppendLine($"goto {exit_label_node};")
- .AppendLine("}")
- .AppendLine($"{additional_count}--;");
- } else
- {
- string limit = additional_limit < 0 ? string.Empty :
- $"{additional_count} == {additional_limit} || ";
- _ = sc.AppendLine($"{remain_span} = {additional_entry}.Slice({additional_count} * {literal.Length});")
- .AppendLine($"if ({limit}!{remain_span}.{starts_with}({expected_literal}))")
- .AppendLine("{")
- .AppendLine($"{remain_span} = {additional_entry};")
- .AppendLine($"goto {exit_label_node};")
- .AppendLine("}")
- .AppendLine($"{additional_count}++;");
- }
- _ = sc.AppendLine("}");
- } else if (has_continuation) {
- GenerateConcatTail(continuation!, next, sc, func_list, context, exit);
+ .AppendLine($"{remain_span} = {additional_entry};")
+ .AppendLine($"goto {exit_label_node};")
+ .AppendLine("}")
+ .AppendLine($"{additional_count}--;");
+ } else
+ {
+ string limit = additional_limit < 0 ? string.Empty :
+ $"{additional_count} == {additional_limit} || ";
+ _ = sc.AppendLine($"{remain_span} = {additional_entry}.Slice({additional_count} * {literal.Length});")
+ .AppendLine($"if ({limit}!{remain_span}.{starts_with}({expected_literal}))")
+ .AppendLine("{")
+ .AppendLine($"{remain_span} = {additional_entry};")
+ .AppendLine($"goto {exit_label_node};")
+ .AppendLine("}")
+ .AppendLine($"{additional_count}++;");
}
+ _ = sc.AppendLine($"{remain_span} = {additional_entry}.Slice({additional_count} * {literal.Length});")
+ .AppendLine($"{resume.GetLabelName()}: {{}}");
+ backtrack = retry;
}
#endregion
@@ -630,7 +623,7 @@ namespace Nitload.RegularAnalysis.Text
in ExitLabel exit_label
)
{
- ExitLabel success = context.RequestExitLabel(ExitMode.Goto);
+ ExitLabel success = context.RequestExitLabel(ExitKind.AltMatched);
RegexAstNode current = node.Right;
while (!RegexAstNode.IsNullOrEmpty(current))
{
@@ -639,7 +632,7 @@ namespace Nitload.RegularAnalysis.Text
}
_ = sc.AppendLine($"goto {exit_label.GetLabelName()};")
- .AppendLine($"{success.GetLabelName()}: ;");
+ .AppendLine($"{success.GetLabelName()}: {{}}");
}
private static void GenerateGroup(
@@ -647,7 +640,8 @@ namespace Nitload.RegularAnalysis.Text
[NotNull] in SequenceEmitter sc,
in List> func_list,
in SourceGenContext context,
- in ExitLabel exit_label
+ in ExitLabel local_fail,
+ ref ExitLabel backtrack
)
{
RegexAstNode left = node.Left;
@@ -661,10 +655,10 @@ namespace Nitload.RegularAnalysis.Text
// TODO(NGRA): Snapshot the group start cursor and restore it when the child fails.
if (!RegexAstNode.IsNullOrEmpty(left))
{
- GenerateMethodSource(left, sc, func_list, context, exit_label);
+ GenerateMethodSource(left, sc, func_list, context, local_fail, ref backtrack);
} else
if (!RegexAstNode.IsNullOrEmpty(right)) {
- GenerateMethodSource(right, sc, func_list, context, exit_label);
+ GenerateMethodSource(right, sc, func_list, context, local_fail, ref backtrack);
}
string group_name = config.Name;
@@ -679,13 +673,16 @@ namespace Nitload.RegularAnalysis.Text
[NotNull] in SequenceEmitter sc,
in List> func_list,
in SourceGenContext context,
- in ExitLabel exit_label = default
+ in ExitLabel local_fail,
+ ref ExitLabel backtrack
)
{
- // Keep the complete suffix visible even when CONCAT is left-associated.
- List sequence = [];
- AppendConcatNodes(node, sequence);
- GenerateConcatTail(sequence, 0, sc, func_list, context, exit_label);
+ if (!RegexAstNode.IsNullOrEmpty(node.Left)) {
+ GenerateMethodSource(node.Left, sc, func_list, context, local_fail, ref backtrack);
+ }
+ if (!RegexAstNode.IsNullOrEmpty(node.Right)) {
+ GenerateMethodSource(node.Right, sc, func_list, context, local_fail, ref backtrack);
+ }
}
private static void GenerateCount(
@@ -693,9 +690,8 @@ namespace Nitload.RegularAnalysis.Text
[NotNull] in SequenceEmitter sc,
in List> func_list,
in SourceGenContext context,
- in ExitLabel exit_label,
- IReadOnlyList? continuation = null,
- int next = 0
+ in ExitLabel local_fail,
+ ref ExitLabel backtrack
)
{
RegexAstNode value = RegexAstNode.IsNullOrEmpty(node.Left) ? node.Right : node.Left;
@@ -716,6 +712,7 @@ namespace Nitload.RegularAnalysis.Text
int min = payload.Min;
int max = payload.Max;
bool is_greedy = payload.IsGreedy;
+ ExitLabel failure = backtrack;
// TODO(NGRA-NAMING): Reserve exclusive names for future repetition/rollback state
// across complex COUNT retries; recursive children must use the same method pool.
@@ -723,21 +720,19 @@ namespace Nitload.RegularAnalysis.Text
{
// TODO(NGRA): Emit min/max repetition, zero-width progress protection, greedy vs.
// lazy ordering, and per-attempt rollback for a complex COUNT body.
- GenerateMethodSource(value, sc, func_list, context, exit_label);
+ GenerateMethodSource(value, sc, func_list, context, local_fail, ref backtrack);
} else
{
switch (value.Label & RegexElementLabel.SEQUENCE)
{
case RegexElementLabel.UNIT:
- GenerateUnitCount(value, min, max, is_greedy, sc, func_list, context,
- exit_label, continuation, next);
+ GenerateUnitCount(value, min, max, is_greedy, sc, context, failure, ref backtrack);
return;
case RegexElementLabel.UNIT_SET:
- GenerateUnitSetCount(value, min, max, is_greedy, sc, context, exit_label);
+ GenerateUnitSetCount(value, min, max, is_greedy, sc, context, failure);
break;
case RegexElementLabel.UNIT_SPAN:
- GenerateUnitSpanCount(value, min, max, is_greedy, sc, func_list, context,
- exit_label, continuation, next);
+ GenerateUnitSpanCount(value, min, max, is_greedy, sc, context, failure, ref backtrack);
return;
default:
@@ -746,9 +741,6 @@ namespace Nitload.RegularAnalysis.Text
break;
}
}
- if (continuation is not null) {
- GenerateConcatTail(continuation, next, sc, func_list, context, exit_label);
- }
}
#endregion
diff --git a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs
index b7642b11..c17f8dc4 100644
--- a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs
+++ b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs
@@ -36,7 +36,7 @@ namespace Nitload.RegularAnalysis.Text
public const string _remain_span = "__remain";
- private int _max_exit_lable;
+ private int _exit_label_count;
private int _scratch_scope_count;
@@ -54,12 +54,12 @@ namespace Nitload.RegularAnalysis.Text
}
public ExitLabel RequestExitLabel(
- ExitMode exitMode
+ ExitKind kind
)
{
- int index = _max_exit_lable;
- _max_exit_lable++;
- return new(exitMode, index);
+ int index = _exit_label_count;
+ _exit_label_count++;
+ return new(kind, index);
}
public int RequestScratchScopeIndex()
diff --git a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenSpecial.cs b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenSpecial.cs
index d0724085..ffc61839 100644
--- a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenSpecial.cs
+++ b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenSpecial.cs
@@ -17,7 +17,8 @@ namespace Nitload.RegularAnalysis.Text
in SequenceEmitter sc,
in List> func_list,
in SourceGenContext context,
- in ExitLabel exit_label
+ in ExitLabel local_fail,
+ ref ExitLabel backtrack
)
{
// A special rule owns the entire matched subtree. Returning false leaves emission
@@ -25,7 +26,7 @@ namespace Nitload.RegularAnalysis.Text
return node.Label switch
{
RegexElementLabel.CONCAT => TryGenerateFindConcat(node, sc, func_list, context,
- exit_label),
+ local_fail, ref backtrack),
_ => false,
};
}
@@ -166,10 +167,12 @@ namespace Nitload.RegularAnalysis.Text
in ExitLabel success
)
{
- ExitLabel failure = context.RequestExitLabel(ExitMode.Goto);
+ ExitLabel failure = context.RequestExitLabel(ExitKind.AltNext);
string entry = $"__alt_entry_{context.RequestScratchScopeIndex()}";
_ = sc.AppendLine($"{ROS_char} {entry} = {context.RemainSpan};");
- GenerateMethodSource(node, sc, func_list, context, failure);
+ // Keep branch-local retries isolated from both siblings and the caller's path.
+ ExitLabel branch_backtrack = failure;
+ GenerateMethodSource(node, sc, func_list, context, failure, ref branch_backtrack);
_ = sc.AppendLine($"goto {success.GetLabelName()};")
.AppendLine($"{failure.GetLabelName()}:")
.AppendLine($"{context.RemainSpan} = {entry};");
@@ -180,50 +183,6 @@ namespace Nitload.RegularAnalysis.Text
#endregion
- #region CONCAT continuation
-
- private static void AppendConcatNodes(
- RegexAstNode node,
- List sequence
- )
- {
- if (RegexAstNode.IsNullOrEmpty(node)) {
- return;
- }
- if (node.Label == RegexElementLabel.CONCAT &&
- !RegexAstNode.IsDotStar(node.Left) && !IsNamedFind(node))
- {
- AppendConcatNodes(node.Left, sequence);
- AppendConcatNodes(node.Right, sequence);
- } else {
- sequence.Add(node);
- }
- }
-
- private static void GenerateConcatTail(
- IReadOnlyList sequence,
- int start,
- in SequenceEmitter sc,
- in List> func_list,
- in SourceGenContext context,
- in ExitLabel exit_label
- )
- {
- for (int i = start; i < sequence.Count; i++)
- {
- RegexAstNode node = sequence[i];
- if (node.Label == RegexElementLabel.COUNT)
- {
- // COUNT owns both its repetition and the continuation that may retry it.
- GenerateCount(node, sc, func_list, context, exit_label, sequence, i + 1);
- return;
- }
- GenerateMethodSource(node, sc, func_list, context, exit_label);
- }
- }
-
- #endregion
-
#region FIND concatenation
private static bool TryGenerateFindConcat(
@@ -231,7 +190,8 @@ namespace Nitload.RegularAnalysis.Text
in SequenceEmitter sc,
in List> func_list,
in SourceGenContext context,
- in ExitLabel exit_label
+ in ExitLabel local_fail,
+ ref ExitLabel backtrack
)
{
RegexAstNode left = node.Left;
@@ -246,7 +206,7 @@ namespace Nitload.RegularAnalysis.Text
{
// TODO(NGRA): Emit the unnamed FIND candidate loop and retry the right subtree
// until it succeeds or no anchor candidate remains.
- GenerateMethodSource(right, sc, func_list, context, exit_label, true);
+ GenerateMethodSource(right, sc, func_list, context, local_fail, ref backtrack, true);
return true;
}
@@ -256,7 +216,7 @@ namespace Nitload.RegularAnalysis.Text
{
// TODO(NGRA): Emit named FIND retry and capture the text preceding the
// successful anchor candidate.
- GenerateMethodSource(right, sc, func_list, context, exit_label, true);
+ GenerateMethodSource(right, sc, func_list, context, local_fail, ref backtrack, true);
} else
{
// TODO(NGRA): Capture the remaining input for a terminal named dot-star.
diff --git a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.cs b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.cs
index bfe9cc54..eec6d7e3 100644
--- a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.cs
+++ b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.cs
@@ -172,7 +172,15 @@ namespace Nitload.RegularAnalysis.Text
SequenceEmitter methodSource = new();
List> localFunctions = [];
- GenerateMethodSource(root, methodSource, localFunctions, generationContext);
+ ExitLabel local_fail = generationContext.RequestExitLabel(ExitKind.MatchFailed);
+ ExitLabel backtrack = local_fail;
+ ExitLabel end = generationContext.RequestExitLabel(ExitKind.MatchEnd);
+ GenerateMethodSource(root, methodSource, localFunctions, generationContext,
+ local_fail, ref backtrack);
+ _ = methodSource.AppendLine($"goto {end.GetLabelName()};")
+ .AppendLine($"{local_fail.GetLabelName()}: {{}}")
+ .AppendLine("// TODO(NGRA): Produce the root failure result here.")
+ .AppendLine($"{end.GetLabelName()}: {{}}");
foreach (SequenceEmitter localFunction in localFunctions) {
_ = methodSource.AppendLine()
.AppendLine()
diff --git a/test/StgSharpDebug/TestRegexGenerate.cs b/test/StgSharpDebug/TestRegexGenerate.cs
index 4e09804b..92c6277d 100644
--- a/test/StgSharpDebug/TestRegexGenerate.cs
+++ b/test/StgSharpDebug/TestRegexGenerate.cs
@@ -38,8 +38,14 @@ namespace Nitload
SequenceEmitter body = new();
List> functions = [];
SourceGenContext context = new();
- ExitLabel exit = context.RequestExitLabel(ExitMode.Goto);
- GenerateMethodSource(analysis.Ast.Root, body, functions, context, exit);
+ ExitLabel local_fail = context.RequestExitLabel(ExitKind.MatchFailed);
+ ExitLabel backtrack = local_fail;
+ ExitLabel end = context.RequestExitLabel(ExitKind.MatchEnd);
+ GenerateMethodSource(analysis.Ast.Root, body, functions, context, local_fail, ref backtrack);
+ _ = body.AppendLine($"goto {end.GetLabelName()};")
+ .AppendLine($"{local_fail.GetLabelName()}: {{}}")
+ .AppendLine("// TODO(NGRA): Produce the root failure result here.")
+ .AppendLine($"{end.GetLabelName()}: {{}}");
foreach (string line in body.ToList()) {
Console.WriteLine(line);