Graphic mat 44 is now generic; TextRegex source gen will use goto instead of do{}while()

This commit is contained in:
Youmu Liu
2026-09-07 17:51:15 +08:00
parent ee67dfbde1
commit 90c7538639
27 changed files with 805 additions and 713 deletions
@@ -183,6 +183,8 @@ namespace Nitload.RegularAnalysis.Text
)
{
RegexAstNode current = node.Right;
// TODO(NGRA-NAMING): Reserve exclusive names for future ALT cursor/capture snapshots;
// their lifetime extends through retry labels, not just the candidate's emitted block.
string exit_label = context.RequestExitLabel();
// TODO(NGRA): Walk every normalized ALT candidate exactly once. The final traversal
@@ -229,9 +231,10 @@ namespace Nitload.RegularAnalysis.Text
// TODO(NGRA): Advance by code.Length rather than one character and
// preserve the ALT rollback cursor on failure.
_ = sc.AppendLine(@$"if({context.RemainSpan}.{MemoryExtensions.StartsWith}({FormatStringLiteral(code)}))")
string liteal = FormatStringLiteral(code);
_ = sc.AppendLine(@$"if({context.RemainSpan}.{MemoryExtensions.StartsWith}({liteal}))")
.AppendLine("{")
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice({code.Length});")
.AppendLine($@"goto {exit_label};")
.AppendLine("}");
@@ -269,6 +272,9 @@ namespace Nitload.RegularAnalysis.Text
RegexGroupPayload config = node.PayloadAs<RegexGroupPayload>();
// TODO(NGRA-NAMING): Reserve exclusive names for generated group snapshots and capture
// temporaries. A capture's user-provided name is not a unique generated local name.
// TODO(NGRA): Select the owned group body using Empty-sentinel checks, snapshot its
// start cursor, and restore it when the child matcher fails.
if (left is not null)
@@ -343,7 +349,8 @@ namespace Nitload.RegularAnalysis.Text
int max,
bool is_greedy,
[NotNull] in SequenceEmitter<string> sc,
in SourceGenContext context
in SourceGenContext context,
string exit_label
)
{
if (min < 0 || (max >= 0 && max < min)) {
@@ -359,16 +366,16 @@ namespace Nitload.RegularAnalysis.Text
char expected = literal[0];
string expected_literal = FormatCharLiteral(expected);
string remain_span = context.RemainSpan;
int scratch_scope_index = context.RentScratchScopeIndex();
// TODO(NGRA-NAMING): Allocate required_rest, required_matched, required_chunk_index,
// and additional_count through the exclusive-name pool, including loop-local names.
int scratch_scope_index = context.RequestScratchScopeIndex();
string required_rest = $"__required_rest_{scratch_scope_index}";
string required_matched = $"__required_matched_{scratch_scope_index}";
string required_chunk_index = $"__required_chunk_index_{scratch_scope_index}";
string additional_count = $"__additional_count_{scratch_scope_index}";
// TODO(NGRA): Move ownership of this work region to the enclosing CONCAT or match
// method. Until then, break demonstrates the local failure path but cannot prevent a
// later sibling node from being emitted and executed.
_ = sc.AppendLine("do").AppendLine("{");
// TODO(NGRA): Replace this local COUNT exit with the inherited failure ExitLabel.
// The temporary goto preserves the former local exit but still lets siblings execute.
if (min <= flattened_unit_count_threshold)
{
@@ -377,7 +384,7 @@ namespace Nitload.RegularAnalysis.Text
string required_prefix = new(expected, min);
_ = sc.AppendLine($"if (!{remain_span}.{nameof(MemoryExtensions.StartsWith)}({FormatStringLiteral(required_prefix)}))")
.AppendLine("{")
.AppendLine("break;")
.AppendLine($"goto {exit_label};")
.AppendLine("}")
.AppendLine($"{remain_span} = {remain_span}.Slice({min});");
}
@@ -392,7 +399,7 @@ namespace Nitload.RegularAnalysis.Text
_ = sc.AppendLine($"if ({remain_span}.Length < {min})")
.AppendLine("{")
.AppendLine("break;")
.AppendLine($"goto {exit_label};")
.AppendLine("}")
.AppendLine($"{ROS_char} {required_rest} = {remain_span};")
.AppendLine($"bool {required_matched} = true;")
@@ -417,7 +424,7 @@ namespace Nitload.RegularAnalysis.Text
_ = sc.AppendLine($"if (!{required_matched})")
.AppendLine("{")
.AppendLine("break;")
.AppendLine($"goto {exit_label};")
.AppendLine("}")
.AppendLine($"{remain_span} = {remain_span}.Slice({min});");
}
@@ -445,8 +452,6 @@ namespace Nitload.RegularAnalysis.Text
_ = sc.AppendLine("// TODO(NGRA): Retry the lazy COUNT tail from its continuation.");
}
_ = sc.AppendLine("} while (false);");
context.ReturnScratchScopeIndex(scratch_scope_index);
}
private static void GenerateUnitSetCount(
@@ -472,34 +477,53 @@ namespace Nitload.RegularAnalysis.Text
IReadOnlyList<RegexCharSet> charset = payload.Set;
string remain_span = context.RemainSpan;
int scratch_scope_index = context.RentScratchScopeIndex();
// TODO(NGRA-NAMING): Allocate required_matched and additional_count through the
// exclusive-name pool. Reserve required_rest/required_chunk_index if emitted later.
int scratch_scope_index = context.RequestScratchScopeIndex();
string required_rest = $"__required_rest_{scratch_scope_index}";
string required_matched = $"__required_matched_{scratch_scope_index}";
string required_chunk_index = $"__required_chunk_index_{scratch_scope_index}";
string additional_count = $"__additional_count_{scratch_scope_index}";
// TODO(NGRA): Move ownership of this work region to the enclosing CONCAT or match
// method. Until then, break demonstrates the local failure path but cannot prevent a
// later sibling node from being emitted and executed.
// TODO(NGRA): Route a failed minimum check through the inherited failure ExitLabel.
// The break below only stops charset scanning and must remain local to that loop.
bool accept = payload.Accept;
RegexCharSet _set = charset[0];
_ = sc.AppendLine($"{ROS_char} {required_matched} = {remain_span}")
.AppendLine($"int {additional_count} = 0;")
.AppendLine()
.AppendLine() //TODO loop head
.AppendLine($"if({(accept ? string.Empty : "!(")} {GenerateCharSetExpression(_set)} ||");
for (int i = 1; i < charset.Count - 1; i++)
.AppendLine($"for(; {additional_count} < {max}; {additional_count} ++)")
.AppendLine("{");
if (charset.Count == 1)
{
_set = charset[i];
_ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ||");
// single charset rule
_ = sc.AppendLine($"if({(accept ? string.Empty : "!(")} {GenerateCharSetExpression(_set)})");
} else
{
// multi charset rule
_ = sc.AppendLine($"if({(accept ? string.Empty : "!(")} {GenerateCharSetExpression(_set)} ||");
for (int i = 1; i < charset.Count - 1; i++)
{
_set = charset[i];
_ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ||");
}
_set = charset[^1];
_ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ){(accept ? string.Empty : ')')}");
}
_set = charset[^1];
_ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ){(accept ? string.Empty : ')')}")
.AppendLine("{")
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
.AppendLine($@"break;")
.AppendLine("}");
_ = sc .AppendLine("{")
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
.AppendLine("}")
.AppendLine("else")
.AppendLine("{")
.AppendLine($@"break;")
.AppendLine("}")
.AppendLine("}")
.AppendLine($"if({additional_count} < {min})")
.AppendLine("{")
.AppendLine("}")
.AppendLine("else")
.AppendLine("{")
.AppendLine("}");
}
private static void GenerateUnitSpanCount(
@@ -508,7 +532,8 @@ namespace Nitload.RegularAnalysis.Text
int max,
bool is_greedy,
[NotNull] in SequenceEmitter<string> sc,
in SourceGenContext context
in SourceGenContext context,
string exit_label
)
{
if (min < 0 || (max >= 0 && max < min)) {
@@ -523,7 +548,10 @@ namespace Nitload.RegularAnalysis.Text
string expected_literal = FormatStringLiteral(literal);
string remain_span = context.RemainSpan;
int scratch_scope_index = context.RentScratchScopeIndex();
// TODO(NGRA-NAMING): Allocate required_rest, required_matched, required_chunk_index,
// required_count, and additional_count through the exclusive-name pool. Flattened
// sibling COUNT nodes share the declaration space even after their exit labels.
int scratch_scope_index = context.RequestScratchScopeIndex();
string required_rest = $"__required_rest_{scratch_scope_index}";
string required_matched = $"__required_matched_{scratch_scope_index}";
string required_chunk_index = $"__required_chunk_index_{scratch_scope_index}";
@@ -531,10 +559,8 @@ namespace Nitload.RegularAnalysis.Text
string additional_count = $"__additional_count_{scratch_scope_index}";
string starts_with = $"{nameof(MemoryExtensions.StartsWith)}";
// TODO(NGRA): Move ownership of this work region to the enclosing CONCAT or match
// method. Until then, break demonstrates the local failure path but cannot prevent a
// later sibling node from being emitted and executed.
_ = sc.AppendLine("do").AppendLine("{");
// TODO(NGRA): Replace this local COUNT exit with the inherited failure ExitLabel.
// The temporary goto preserves the former local exit but still lets siblings execute.
if (literal.Length > flattened_unit_count_threshold)
{
@@ -549,7 +575,7 @@ namespace Nitload.RegularAnalysis.Text
.AppendLine("}")
.AppendLine($"if ({required_count} != {min})")
.AppendLine("{")
.AppendLine("break;")
.AppendLine($"goto {exit_label};")
.AppendLine("}")
.AppendLine($"{remain_span} = {required_rest};");
} else if (min <= flattened_unit_count_threshold / literal.Length)
@@ -563,7 +589,7 @@ namespace Nitload.RegularAnalysis.Text
string required_prefix = required_prefix_span.ToString();
_ = sc.AppendLine($"if (!{remain_span}.{starts_with}({FormatStringLiteral(required_prefix)}))")
.AppendLine("{")
.AppendLine("break;")
.AppendLine($"goto {exit_label};")
.AppendLine("}")
.AppendLine($"{remain_span} = {remain_span}.Slice({min * literal.Length});");
}
@@ -612,7 +638,7 @@ namespace Nitload.RegularAnalysis.Text
_ = sc.AppendLine($"if (!{required_matched})")
.AppendLine("{")
.AppendLine("break;")
.AppendLine($"goto {exit_label};")
.AppendLine("}")
.AppendLine($"{remain_span} = {required_rest};");
}
@@ -640,8 +666,6 @@ namespace Nitload.RegularAnalysis.Text
_ = sc.AppendLine("// TODO(NGRA): Retry the lazy COUNT tail from its continuation.");
}
_ = sc.AppendLine("} while (false);");
context.ReturnScratchScopeIndex(scratch_scope_index);
}
private static void GenerateCount(
@@ -659,6 +683,8 @@ namespace Nitload.RegularAnalysis.Text
int min = payload.Min;
int max = payload.Max;
bool is_greedy = payload.IsGreedy;
// TODO(NGRA-NAMING): Reserve exclusive names for future repetition/rollback state
// across complex COUNT retries; recursive children must use the same method pool.
string exit_label = context.RequestExitLabel();
if ((value.Label & RegexElementLabel.SEQUENCE) == 0)
{
@@ -670,15 +696,16 @@ namespace Nitload.RegularAnalysis.Text
switch (value.Label & RegexElementLabel.SEQUENCE)
{
case RegexElementLabel.UNIT:
GenerateUnitCount(value, min, max, is_greedy, sc, context);
GenerateUnitCount(value, min, max, is_greedy, sc, context, exit_label);
break;
case RegexElementLabel.UNIT_SET:
GenerateUnitSetCount(value, min, max, is_greedy, sc, context);
// TODO(NGRA): Emit a bounded/unbounded charset counting loop, preferably
// using IndexOfAnyExcept where the charset shape permits it.
break;
case RegexElementLabel.UNIT_SPAN:
GenerateUnitSpanCount(value, min, max, is_greedy, sc, context);
GenerateUnitSpanCount(value, min, max, is_greedy, sc, context, exit_label);
break;
default: