Graphic mat 44 is now generic; TextRegex source gen will use goto instead of do{}while()
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="TextRegexSourceGen.ExitLabel"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal partial class TextRegexSourceGen
|
||||
{
|
||||
|
||||
internal enum ExitMode
|
||||
{
|
||||
|
||||
Break,
|
||||
Continue,
|
||||
Goto,
|
||||
|
||||
}
|
||||
|
||||
internal readonly record struct ExitLabel
|
||||
{
|
||||
|
||||
public ExitLabel(
|
||||
ExitMode mode,
|
||||
int order
|
||||
)
|
||||
{
|
||||
Mode = mode;
|
||||
LabelOrder = order;
|
||||
}
|
||||
|
||||
public ExitMode Mode { get; init; }
|
||||
|
||||
public int LabelOrder { get; init; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ namespace Nitload.RegularAnalysis.Text
|
||||
/// <remarks>
|
||||
/// Method level state is limited to the active cursor and the result of the most recent
|
||||
/// instruction. Everything else is either a region slot, a line slot, a capture slot, or
|
||||
/// a block scoped scratch variable. Scratch indices remain unique while rented and are
|
||||
/// recycled after their generated lexical scope has closed.
|
||||
/// a scratch variable. Scratch indices remain unique throughout the generated method;
|
||||
/// flattened regions cannot release their names for reuse by later nodes.
|
||||
/// </remarks>
|
||||
internal sealed class SourceGenContext
|
||||
{
|
||||
@@ -40,8 +40,6 @@ namespace Nitload.RegularAnalysis.Text
|
||||
|
||||
private int _scratch_scope_count;
|
||||
|
||||
private readonly Stack<int> _free_scratch_scope_indices = [];
|
||||
|
||||
public string RemainSpan { get; } = _remain_span;
|
||||
|
||||
public string FirstOfRemain { get; } = $@"{_remain_span}[0]";
|
||||
@@ -57,36 +55,22 @@ namespace Nitload.RegularAnalysis.Text
|
||||
|
||||
public string RequestExitLabel()
|
||||
{
|
||||
// TODO(NGRA-NAMING): Route ALT and COUNT labels through the method-wide label pool.
|
||||
// Keep every emitted label exclusive until method generation is complete.
|
||||
string code = $@"_exit_alt_{_exit_alt_count}";
|
||||
_exit_alt_count++;
|
||||
return code;
|
||||
}
|
||||
|
||||
public int RentScratchScopeIndex()
|
||||
public int RequestScratchScopeIndex()
|
||||
{
|
||||
if (_free_scratch_scope_indices.TryPop(out int recycled_index)) {
|
||||
return recycled_index;
|
||||
}
|
||||
|
||||
// TODO(NGRA-NAMING): Integrate scratch names with the method-wide exclusive-name
|
||||
// allocator. Until then, reserve each suffix permanently for this method.
|
||||
int index = _scratch_scope_count;
|
||||
_scratch_scope_count++;
|
||||
return index;
|
||||
}
|
||||
|
||||
public void ReturnScratchScopeIndex(
|
||||
int index
|
||||
)
|
||||
{
|
||||
if ((uint)index >= (uint)_scratch_scope_count) {
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
if (_free_scratch_scope_indices.Contains(index)) {
|
||||
throw new InvalidOperationException($"Scratch scope {index} was returned twice.");
|
||||
}
|
||||
|
||||
_free_scratch_scope_indices.Push(index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Nitload.RegularAnalysis.Text
|
||||
string static_modifier;
|
||||
|
||||
int brace_count = 0;
|
||||
do
|
||||
while (type is not null)
|
||||
{
|
||||
_ = file_name_sb.Append(CultureInfo.InvariantCulture, $@"{type.TypeName}.");
|
||||
string declare_type = type.DeclType switch
|
||||
@@ -74,7 +74,7 @@ namespace Nitload.RegularAnalysis.Text
|
||||
.AppendLine("{");
|
||||
brace_count++;
|
||||
type = type.InnerType!;
|
||||
} while (type is not null);
|
||||
}
|
||||
|
||||
static_modifier = gen_source.IsMethodStatic ? "static" : string.Empty;
|
||||
_ = ce.AppendLine(@$"{ToKeyword(gen_source.MethodAccessibility)} {static_modifier} {Text_Regex} {gen_source.MethodName}({ROS_char} {regex_gen_context.InputString})")
|
||||
@@ -90,8 +90,7 @@ namespace Nitload.RegularAnalysis.Text
|
||||
brace_count = 0;
|
||||
foreach (string line in ce)
|
||||
{
|
||||
bool is_right_brace = (line == "}") ||
|
||||
line.StartsWith("} while", StringComparison.InvariantCulture);
|
||||
bool is_right_brace = line == "}";
|
||||
brace_count -= is_right_brace ? 1 : 0;
|
||||
for (int i = 0; i < brace_count; i++) {
|
||||
_ = code_source.Append(" ");
|
||||
|
||||
Reference in New Issue
Block a user