process cases of ALT as special rules

This commit is contained in:
Youmu Liu
2026-09-10 18:17:09 +08:00
parent 8755185e78
commit f372f83171
18 changed files with 486 additions and 166 deletions
@@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// file="TextRegexSourceGen.GenSpecial"
// Project: Nitload
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
@@ -24,11 +24,162 @@ namespace Nitload.RegularAnalysis.Text
// to the general path; returning true prevents that subtree from being emitted twice.
return node.Label switch
{
RegexElementLabel.CONCAT => TryGenerateFindConcat(node, sc, func_list, context, exit_label),
RegexElementLabel.CONCAT => TryGenerateFindConcat(node, sc, func_list, context,
exit_label),
_ => false,
};
}
#region ALT candidate rules
private static bool TryGetSimpleAltNode(
RegexAstNode node,
out RegexAstNode simple
)
{
simple = node;
while (simple.Label == RegexElementLabel.CONCAT)
{
bool left_empty = RegexAstNode.IsNullOrEmpty(simple.Left);
bool right_empty = RegexAstNode.IsNullOrEmpty(simple.Right);
if (left_empty == right_empty) {
return false;
}
simple = left_empty ? simple.Right : simple.Left;
}
// Inspect transparent wrappers without changing ownership or the candidate Next link.
// COUNT and GROUP are never peeled: repetition and capture semantics must survive.
return simple.Label is RegexElementLabel.UNIT or RegexElementLabel.UNIT_SET or
RegexElementLabel.UNIT_SPAN;
}
private static void GenerateAltCandidate(
RegexAstNode node,
in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list,
in SourceGenContext context,
in ExitLabel success
)
{
if (TryGetSimpleAltNode(node, out RegexAstNode simple)) {
node = simple;
}
switch (node.Label)
{
case RegexElementLabel.UNIT:
GenerateAltUnit(node, sc, context, success);
break;
case RegexElementLabel.UNIT_SET:
GenerateAltUnitSet(node, sc, context, success);
break;
case RegexElementLabel.UNIT_SPAN:
GenerateAltUnitSpan(node, sc, context, success);
break;
case RegexElementLabel.CONCAT when RegexAstNode.IsNullOrEmpty(node.Left) &&
RegexAstNode.IsNullOrEmpty(node.Right):
case RegexElementLabel.EPSILON:
// An empty expression succeeds without consuming input.
_ = sc.AppendLine($"goto {success.GetLabelName()};");
break;
case RegexElementLabel.CONCAT:
case RegexElementLabel.COUNT:
case RegexElementLabel.GROUP_BEGIN:
case RegexElementLabel.ALT:
GenerateAltOperator(node, sc, func_list, context, success);
break;
default:
throw new NotSupportedException($"Unsupported ALT candidate: {node.Label}.");
}
}
private static void GenerateAltUnit(
RegexAstNode node,
in SequenceEmitter<string> sc,
in SourceGenContext context,
in ExitLabel success
)
{
string literal = node.Value;
if (literal.Length != 1) {
throw new InvalidOperationException("ALT UNIT requires one decoded character.");
}
_ = sc.AppendLine($"if (!{context.RemainSpan}.IsEmpty && {context.RemainSpan}[0] == {FormatCharLiteral(literal[0])})")
.AppendLine("{")
.AppendLine($"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
.AppendLine($"goto {success.GetLabelName()};")
.AppendLine("}");
}
private static void GenerateAltUnitSet(
RegexAstNode node,
in SequenceEmitter<string> sc,
in SourceGenContext context,
in ExitLabel success
)
{
RegexCharSetPayload payload = node.PayloadAs<RegexCharSetPayload>();
_ = sc.AppendLine($"if (!{context.RemainSpan}.IsEmpty &&")
.AppendLine(payload.Accept ? " (" : " !(");
if (payload.Set.Count == 0) {
_ = sc.AppendLine(" false");
}
for (int i = 0; i < payload.Set.Count; i++)
{
string suffix = i + 1 < payload.Set.Count ? " ||" : string.Empty;
_ = sc.AppendLine($" {GenerateCharSetExpression(payload.Set[i])}{suffix}");
}
_ = sc.AppendLine(" ))")
.AppendLine("{")
.AppendLine($"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
.AppendLine($"goto {success.GetLabelName()};")
.AppendLine("}");
}
private static void GenerateAltUnitSpan(
RegexAstNode node,
in SequenceEmitter<string> sc,
in SourceGenContext context,
in ExitLabel success
)
{
string literal = node.Value;
if (literal.Length == 0) {
throw new InvalidOperationException("ALT UNIT_SPAN requires a non-empty literal.");
}
string predicate = $"{context.RemainSpan}.{nameof(MemoryExtensions.StartsWith)}({FormatStringLiteral(literal)})";
_ = sc.AppendLine($"if ({predicate})")
.AppendLine("{")
.AppendLine($"{context.RemainSpan} = {context.RemainSpan}.Slice({literal.Length});")
.AppendLine($"goto {success.GetLabelName()};")
.AppendLine("}");
}
private static void GenerateAltOperator(
RegexAstNode node,
in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list,
in SourceGenContext context,
in ExitLabel success
)
{
ExitLabel failure = context.RequestExitLabel(ExitMode.Goto);
string entry = $"__alt_entry_{context.RequestScratchScopeIndex()}";
_ = sc.AppendLine($"{ROS_char} {entry} = {context.RemainSpan};");
GenerateMethodSource(node, sc, func_list, context, failure);
_ = sc.AppendLine($"goto {success.GetLabelName()};")
.AppendLine($"{failure.GetLabelName()}:")
.AppendLine($"{context.RemainSpan} = {entry};");
// TODO(NGRA): Restore capture state once GROUP emission supports captures.
// Retrying an already successful candidate after a later continuation fails is separate.
}
#endregion
#region FIND concatenation
private static bool TryGenerateFindConcat(
@@ -78,7 +229,8 @@ namespace Nitload.RegularAnalysis.Text
{
// (?<name>.*)abc
if (RegexAstNode.IsNullOrEmpty(node) || node.Label != RegexElementLabel.CONCAT) {
if (RegexAstNode.IsNullOrEmpty(node) || node.Label != RegexElementLabel.CONCAT)
{
return false;
}
RegexAstNode group = node.Left;
@@ -106,6 +258,5 @@ namespace Nitload.RegularAnalysis.Text
// An escaped star is a literal and must not be classified as dot-star repetition.
#endregion
}
}