meet difficult problem on COUNT node

This commit is contained in:
Youmu Liu
2026-09-11 17:29:42 +08:00
parent f372f83171
commit 5a3286dc21
4 changed files with 188 additions and 70 deletions
@@ -220,8 +220,11 @@ namespace Nitload.RegularAnalysis.Text
int max, int max,
bool is_greedy, bool is_greedy,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list,
in SourceGenContext context, in SourceGenContext context,
in ExitLabel exit in ExitLabel exit,
IReadOnlyList<RegexAstNode>? continuation = null,
int next = 0
) )
{ {
if (min < 0 || (max >= 0 && max < min)) { if (min < 0 || (max >= 0 && max < min)) {
@@ -300,22 +303,60 @@ namespace Nitload.RegularAnalysis.Text
} }
int additional_limit = max < 0 ? -1 : max - min; 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;
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) if (is_greedy && additional_limit != 0)
{ {
_ = additional_limit < 0 ? string limit = additional_limit < 0 ? string.Empty :
sc.AppendLine($"while (!{remain_span}.IsEmpty && {remain_span}[0] == {expected_literal})") $"{additional_count} < {additional_limit} && ";
_ = sc.AppendLine($"while ({limit}!{remain_span}.IsEmpty && {remain_span}[0] == {expected_literal})")
.AppendLine("{") .AppendLine("{")
.AppendLine($"{remain_span} = {remain_span}.Slice(1);") .AppendLine($"{remain_span} = {remain_span}.Slice(1);");
.AppendLine("}") : if (can_retry || additional_limit > 0) {
sc.AppendLine($"for (int {additional_count} = 0; {additional_count} < {additional_limit} && !{remain_span}.IsEmpty && {remain_span}[0] == {expected_literal}; {additional_count}++)") _ = sc.AppendLine($"{additional_count}++;");
.AppendLine("{") }
.AppendLine($"{remain_span} = {remain_span}.Slice(1);") _ = sc.AppendLine("}");
.AppendLine("}"); }
} else if (!is_greedy && additional_limit != 0) if (can_retry)
{ {
// Lazy COUNT deliberately stops at Min. Its optional tail is consumed only when // The same optional count drives scanning and retries. Required units stay consumed.
// a future continuation attempt fails and requests another repetition. ExitLabel retry = context.RequestExitLabel(ExitMode.Goto);
_ = sc.AppendLine("// TODO(NGRA): Retry the lazy COUNT tail from its continuation."); _ = sc.AppendLine("while (true)")
.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);
} }
} }
@@ -399,8 +440,11 @@ namespace Nitload.RegularAnalysis.Text
int max, int max,
bool is_greedy, bool is_greedy,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list,
in SourceGenContext context, in SourceGenContext context,
in ExitLabel exit in ExitLabel exit,
IReadOnlyList<RegexAstNode>? continuation = null,
int next = 0
) )
{ {
if (min < 0 || (max >= 0 && max < min)) { if (min < 0 || (max >= 0 && max < min)) {
@@ -510,26 +554,60 @@ namespace Nitload.RegularAnalysis.Text
} }
int additional_limit = max < 0 ? -1 : max - min; 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;
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) if (is_greedy && additional_limit != 0)
{ {
if (additional_limit < 0) string limit = additional_limit < 0 ? string.Empty :
{ $"{additional_count} < {additional_limit} && ";
_ = sc.AppendLine($"while ({remain_span}.{starts_with}({expected_literal}))") _ = sc.AppendLine($"while ({limit}{remain_span}.{starts_with}({expected_literal}))")
.AppendLine("{") .AppendLine("{")
.AppendLine($"{remain_span} = {remain_span}.Slice({literal.Length});") .AppendLine($"{remain_span} = {remain_span}.Slice({literal.Length});");
.AppendLine("}"); if (can_retry || additional_limit > 0) {
_ = sc.AppendLine($"{additional_count}++;");
}
_ = sc.AppendLine("}");
}
if (can_retry)
{
// Each retry moves by a complete literal using the scan's own optional counter.
ExitLabel retry = context.RequestExitLabel(ExitMode.Goto);
_ = sc.AppendLine("while (true)")
.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 } else
{ {
_ = sc.AppendLine($"for (int {additional_count} = 0; {additional_count} < {additional_limit} && {remain_span}.{starts_with}({expected_literal}); {additional_count}++)") 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("{")
.AppendLine($"{remain_span} = {remain_span}.Slice({literal.Length});") .AppendLine($"{remain_span} = {additional_entry};")
.AppendLine("}"); .AppendLine($"goto {exit_label_node};")
.AppendLine("}")
.AppendLine($"{additional_count}++;");
} }
} else if (!is_greedy && additional_limit != 0) _ = sc.AppendLine("}");
{ } else if (has_continuation) {
// Lazy COUNT deliberately stops at Min. Its optional tail is consumed only when GenerateConcatTail(continuation!, next, sc, func_list, context, exit);
// a future continuation attempt fails and requests another repetition.
_ = sc.AppendLine("// TODO(NGRA): Retry the lazy COUNT tail from its continuation.");
} }
} }
@@ -604,12 +682,10 @@ namespace Nitload.RegularAnalysis.Text
in ExitLabel exit_label = default in ExitLabel exit_label = default
) )
{ {
if (!RegexAstNode.IsNullOrEmpty(node.Left)) { // Keep the complete suffix visible even when CONCAT is left-associated.
GenerateMethodSource(node.Left, sc, func_list, context, exit_label); List<RegexAstNode> sequence = [];
} AppendConcatNodes(node, sequence);
if (!RegexAstNode.IsNullOrEmpty(node.Right)) { GenerateConcatTail(sequence, 0, sc, func_list, context, exit_label);
GenerateMethodSource(node.Right, sc, func_list, context, exit_label);
}
} }
private static void GenerateCount( private static void GenerateCount(
@@ -617,13 +693,25 @@ namespace Nitload.RegularAnalysis.Text
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list, in List<SequenceEmitter<string>> func_list,
in SourceGenContext context, in SourceGenContext context,
in ExitLabel exit_label in ExitLabel exit_label,
IReadOnlyList<RegexAstNode>? continuation = null,
int next = 0
) )
{ {
RegexAstNode value = RegexAstNode.IsNullOrEmpty(node.Left) ? node.Right : node.Left; RegexAstNode value = RegexAstNode.IsNullOrEmpty(node.Left) ? node.Right : node.Left;
if (RegexAstNode.IsNullOrEmpty(value)) { if (RegexAstNode.IsNullOrEmpty(value)) {
throw new InvalidOperationException("COUNT requires an operand."); throw new InvalidOperationException("COUNT requires an operand.");
} }
// Inspect transparent CONCAT wrappers without rewriting the repetition body.
while (value.Label == RegexElementLabel.CONCAT)
{
bool left_empty = RegexAstNode.IsNullOrEmpty(value.Left);
bool right_empty = RegexAstNode.IsNullOrEmpty(value.Right);
if (left_empty == right_empty) {
break;
}
value = left_empty ? value.Right : value.Left;
}
RegexCountPayload payload = node.PayloadAs<RegexCountPayload>(); RegexCountPayload payload = node.PayloadAs<RegexCountPayload>();
int min = payload.Min; int min = payload.Min;
int max = payload.Max; int max = payload.Max;
@@ -641,14 +729,16 @@ namespace Nitload.RegularAnalysis.Text
switch (value.Label & RegexElementLabel.SEQUENCE) switch (value.Label & RegexElementLabel.SEQUENCE)
{ {
case RegexElementLabel.UNIT: case RegexElementLabel.UNIT:
GenerateUnitCount(value, min, max, is_greedy, sc, context, exit_label); GenerateUnitCount(value, min, max, is_greedy, sc, func_list, context,
break; exit_label, continuation, next);
return;
case RegexElementLabel.UNIT_SET: case RegexElementLabel.UNIT_SET:
GenerateUnitSetCount(value, min, max, is_greedy, sc, context, exit_label); GenerateUnitSetCount(value, min, max, is_greedy, sc, context, exit_label);
break; break;
case RegexElementLabel.UNIT_SPAN: case RegexElementLabel.UNIT_SPAN:
GenerateUnitSpanCount(value, min, max, is_greedy, sc, context, exit_label); GenerateUnitSpanCount(value, min, max, is_greedy, sc, func_list, context,
break; exit_label, continuation, next);
return;
default: default:
// TODO(NGRA): Diagnose malformed COUNT children instead of emitting an // TODO(NGRA): Diagnose malformed COUNT children instead of emitting an
@@ -656,7 +746,9 @@ namespace Nitload.RegularAnalysis.Text
break; break;
} }
} }
if (continuation is not null) {
GenerateConcatTail(continuation, next, sc, func_list, context, exit_label);
}
} }
#endregion #endregion
@@ -180,6 +180,50 @@ namespace Nitload.RegularAnalysis.Text
#endregion #endregion
#region CONCAT continuation
private static void AppendConcatNodes(
RegexAstNode node,
List<RegexAstNode> 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<RegexAstNode> sequence,
int start,
in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> 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 #region FIND concatenation
private static bool TryGenerateFindConcat( private static bool TryGenerateFindConcat(
+2 -11
View File
@@ -24,17 +24,8 @@ namespace Nitload
private static string testExpPath = @"D:\Programmer\stepcode\data\ap203\ap203.exp"; private static string testExpPath = @"D:\Programmer\stepcode\data\ap203\ap203.exp";
internal static unsafe void Main( internal static unsafe void Main()
string[] args
)
{ {
// Select the managed-only emitter preview before initializing the normal runtime.
if (args.Length > 0 && args[0] == "--regex-preview")
{
Environment.ExitCode = TestRegexGenerate.Run(args[1..]);
return;
}
try try
{ {
World.Initialize(new() World.Initialize(new()
@@ -47,7 +38,7 @@ namespace Nitload
.InitializeModule(); .InitializeModule();
// TestRegexAnalyse.Run(); // TestRegexAnalyse.Run();
_ = TestRegexGenerate.Run("abc|123"); _ = TestRegexGenerate.Run("abc1{1,2}23");
// TestHlsf(); // TestHlsf();
+11 -20
View File
@@ -1,6 +1,6 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// file="TestRegexGenerate" // file="TestRegexGenerate"
// Project: Nitload // Project: StgSharp
// Copyright (c) Nitload. // Copyright (c) Nitload.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -16,29 +16,19 @@ namespace Nitload
{ {
internal static class TestRegexGenerate internal static class TestRegexGenerate
{ {
public static int Run(params string[] args)
public static int Run(
string arg
)
{ {
if (args.Length > 1) { Console.WriteLine($"Text regex to be tested: {arg} :\n\n\n");
throw new ArgumentException("Expected at most one regex pattern.", nameof(args)); Print(arg);
}
if (args.Length == 1)
{
Print(args[0]);
return 0; return 0;
} }
while (true) private static void Print(
{ string pattern
Console.Write("regex> "); )
string? pattern = Console.ReadLine();
if (pattern is null or ":exit") {
return 0;
}
Print(pattern);
}
}
private static void Print(string pattern)
{ {
TextRegexSource analysis = RegexAnalyzer.Analyze(pattern); TextRegexSource analysis = RegexAnalyzer.Analyze(pattern);
if (analysis.AnalyseException is not null) { if (analysis.AnalyseException is not null) {
@@ -61,5 +51,6 @@ namespace Nitload
} }
} }
} }
} }
} }