meet difficult problem on COUNT node
This commit is contained in:
@@ -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 ({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("}");
|
||||||
|
}
|
||||||
|
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($"while ({remain_span}.{starts_with}({expected_literal}))")
|
_ = sc.AppendLine($"if ({additional_count} == 0)")
|
||||||
.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
|
} 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(
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
{
|
|
||||||
if (args.Length > 1) {
|
|
||||||
throw new ArgumentException("Expected at most one regex pattern.", nameof(args));
|
|
||||||
}
|
|
||||||
if (args.Length == 1)
|
|
||||||
{
|
|
||||||
Print(args[0]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true)
|
public static int Run(
|
||||||
{
|
string arg
|
||||||
Console.Write("regex> ");
|
)
|
||||||
string? pattern = Console.ReadLine();
|
{
|
||||||
if (pattern is null or ":exit") {
|
Console.WriteLine($"Text regex to be tested: {arg} :\n\n\n");
|
||||||
return 0;
|
Print(arg);
|
||||||
}
|
return 0;
|
||||||
Print(pattern);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Print(string 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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user