process cases of ALT as special rules
This commit is contained in:
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user