specific file for special regex rule
This commit is contained in:
@@ -7,15 +7,7 @@
|
||||
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Nitload.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
@@ -27,21 +19,24 @@ namespace Nitload.RegularAnalysis.Text
|
||||
in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context,
|
||||
in ExitLabel exit_label = default,
|
||||
bool is_find = false
|
||||
|
||||
)
|
||||
{
|
||||
RegexAstNode root = ast;
|
||||
if (root.Label == RegexElementLabel.EPSILON) {
|
||||
return;
|
||||
}
|
||||
if (TryGenerateSpecialSource(root, sc, func_list, context, exit_label)) {
|
||||
return;
|
||||
}
|
||||
RegexElementLabel op = root.Label & (RegexElementLabel.VAST_OPERATOR);
|
||||
if (op != 0)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case RegexElementLabel.CONCAT:
|
||||
GenerateConcat(root, sc, func_list, context);
|
||||
GenerateConcat(root, sc, func_list, context, exit_label);
|
||||
break;
|
||||
case RegexElementLabel.COUNT:
|
||||
GenerateCount(root, sc, func_list, context);
|
||||
@@ -64,25 +59,20 @@ namespace Nitload.RegularAnalysis.Text
|
||||
switch (op)
|
||||
{
|
||||
case RegexElementLabel.UNIT:
|
||||
|
||||
// TODO(NGRA): Emit the remaining-span length guard, literal comparison,
|
||||
// cursor advance, and failure transfer for one character.
|
||||
GenerateUnit(root, sc, context, exit_label);
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SET:
|
||||
|
||||
// TODO(NGRA): Emit the remaining-span length guard, charset predicate,
|
||||
// cursor advance, and failure transfer.
|
||||
GenerateUnitSet(root, sc, context, exit_label);
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SPAN:
|
||||
|
||||
// TODO(NGRA): Emit StartsWith for the complete literal span and advance by
|
||||
// the literal length; integrate is_find candidate retry semantics.
|
||||
GenerateUnitSpan(root, sc, context, exit_label);
|
||||
break;
|
||||
default:
|
||||
|
||||
// TODO(NGRA): Diagnose malformed leaf nodes instead of generating no code.
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(NGRA): Integrate is_find candidate search and continuation retries.
|
||||
// Leaf emission currently matches only at the active cursor.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,228 +110,105 @@ namespace Nitload.RegularAnalysis.Text
|
||||
case RegexCharSetType.Single:
|
||||
return $"{accept}({FormatStringLiteral(set.Value)}.{nameof(MemoryExtensions.Contains)}({remain_span}[0]))";
|
||||
case RegexCharSetType.Range:
|
||||
if (set.Value.Length != 2) {
|
||||
throw new InvalidOperationException("A charset range requires two endpoints.");
|
||||
}
|
||||
return $"{accept}({remain_span}[0] >= {FormatCharLiteral(set.Value[0])} && {remain_span}[0] <= {FormatCharLiteral(set.Value[1])})";
|
||||
case RegexCharSetType.Set:
|
||||
switch (set.Value[0])
|
||||
switch (set.Value)
|
||||
{
|
||||
case 's':
|
||||
case "s":
|
||||
return $"{accept}char.IsWhiteSpace({remain_span}[0])";
|
||||
case 'w':
|
||||
return $"{accept}{nameof(TextRegex.IsCharWord)}({remain_span}[0])";
|
||||
case 'd':
|
||||
case "w":
|
||||
return $"{accept}{Text_Regex}.{nameof(TextRegex.IsCharWord)}({remain_span}[0])";
|
||||
case "d":
|
||||
return $"{accept}char.IsDigit({remain_span}[0])";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
goto default;
|
||||
case RegexCharSetType.Any:
|
||||
return set.Accept ? "true" : "false";
|
||||
default:
|
||||
|
||||
// TODO(NGRA): Unsupported/empty charset rules must become an analyzer
|
||||
// diagnostic; returning an empty expression produces invalid generated C#.
|
||||
return string.Empty;
|
||||
throw new NotSupportedException($"Unsupported charset rule: {set.Type} ({set.Value}).");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNamedFind(
|
||||
RegexAstNode node
|
||||
)
|
||||
{
|
||||
// (?<name>.*)abc
|
||||
|
||||
if (node is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((node.Label & RegexElementLabel.CONCAT) != 0) {
|
||||
return false;
|
||||
}
|
||||
RegexAstNode group = node.Left;
|
||||
|
||||
// TODO(NGRA): Validate the normalized CONCAT/GROUP/COUNT shape with Empty sentinels
|
||||
// before dereferencing children; this helper currently assumes every edge exists.
|
||||
if ((group.Label & RegexElementLabel.GROUP_BEGIN) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RegexAstNode count = group.Right;
|
||||
|
||||
// .*
|
||||
return (count.Label & RegexElementLabel.COUNT) != 0 &&
|
||||
(count.Right.Label & RegexElementLabel.UNIT_SINGLE) != 0 &&
|
||||
count.Right.Source.Source == ".";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region basic verb generate
|
||||
#region UNIT direct matching
|
||||
|
||||
private static void GenerateAlt(
|
||||
RegexAstNode node,
|
||||
private static void GenerateUnit(
|
||||
RegexAstNode value,
|
||||
[NotNull] in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context
|
||||
in SourceGenContext context,
|
||||
in ExitLabel exit
|
||||
)
|
||||
{
|
||||
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.
|
||||
ExitLabel exit_label = context.RequestExitLabel(ExitMode.Goto);
|
||||
|
||||
// TODO(NGRA): Walk every normalized ALT candidate exactly once. The final traversal
|
||||
// shape and failure continuation are not wired yet.
|
||||
while (RegexAstNode.IsNullOrEmpty(current))
|
||||
{
|
||||
RegexElementLabel op = current.Label & RegexElementLabel.SEQUENCE;
|
||||
if (op != 0)
|
||||
{
|
||||
string code = current.SourceCode;
|
||||
switch (op)
|
||||
{
|
||||
case RegexElementLabel.UNIT:
|
||||
|
||||
// TODO(NGRA): Guard against an empty remaining span before indexing it.
|
||||
_ = sc.AppendLine(@$"if({context.RemainSpan}[0] == {FormatCharLiteral(code[0])})")
|
||||
.AppendLine("{")
|
||||
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
|
||||
.AppendLine($@"goto {exit_label};")
|
||||
.AppendLine("}");
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SET:
|
||||
|
||||
// TODO(NGRA): Guard the remaining span, handle empty/Any sets, and
|
||||
// validate negated-set composition before emitting this predicate.
|
||||
RegexCharSetPayload payload = current.PayloadAs<RegexCharSetPayload>();
|
||||
IReadOnlyList<RegexCharSet> split = payload.Set;
|
||||
bool accept = payload.Accept;
|
||||
RegexCharSet _set = split[0];
|
||||
_ = sc.AppendLine($"if({(accept ? string.Empty : "!(")} {GenerateCharSetExpression(_set)} ||");
|
||||
for (int i = 1; i < split.Count - 1; i++)
|
||||
{
|
||||
_set = split[i];
|
||||
_ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ||");
|
||||
}
|
||||
_set = split[^1];
|
||||
_ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ){(accept ? string.Empty : ')')}")
|
||||
.AppendLine("{")
|
||||
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
|
||||
.AppendLine($@"goto {exit_label};")
|
||||
.AppendLine("}");
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SPAN:
|
||||
|
||||
// TODO(NGRA): Advance by code.Length rather than one character and
|
||||
// preserve the ALT rollback cursor on failure.
|
||||
string liteal = FormatStringLiteral(code);
|
||||
_ = sc.AppendLine(@$"if({context.RemainSpan}.{MemoryExtensions.StartsWith}({liteal}))")
|
||||
.AppendLine("{")
|
||||
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice({code.Length});")
|
||||
.AppendLine($@"goto {exit_label};")
|
||||
.AppendLine("}");
|
||||
|
||||
break;
|
||||
default:
|
||||
|
||||
// TODO(NGRA): Route unsupported ALT candidates through the recursive
|
||||
// matcher or report a source-generation diagnostic.
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(NGRA): Advance to the next ALT candidate after a scalar candidate
|
||||
// misses; the current sequence branch does not update current.
|
||||
} else
|
||||
{
|
||||
GenerateMethodSource(current, sc, func_list, context);
|
||||
current = current.Next;
|
||||
}
|
||||
string literal = value.Value;
|
||||
if (literal.Length != 1) {
|
||||
throw new InvalidOperationException(
|
||||
$"UNIT requires one decoded character, but received {literal.Length}.");
|
||||
}
|
||||
|
||||
// TODO(NGRA): Emit the all-alternatives-failed continuation before the shared success
|
||||
// label, including restoration of the entry cursor and capture state.
|
||||
_ = sc.AppendLine($@"{exit_label}: {{ }}");
|
||||
string remain_span = context.RemainSpan;
|
||||
_ = sc.AppendLine($"if ({remain_span}.IsEmpty || {remain_span}[0] != {FormatCharLiteral(literal[0])})")
|
||||
.AppendLine("{")
|
||||
.AppendLine($"goto {exit.GetLabelName()};")
|
||||
.AppendLine("}")
|
||||
.AppendLine($"{remain_span} = {remain_span}.Slice(1);");
|
||||
}
|
||||
|
||||
private static void GenerateGroup(
|
||||
RegexAstNode node,
|
||||
private static void GenerateUnitSet(
|
||||
RegexAstNode value,
|
||||
[NotNull] in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context
|
||||
in SourceGenContext context,
|
||||
in ExitLabel exit
|
||||
)
|
||||
{
|
||||
RegexAstNode left = node.Left;
|
||||
RegexAstNode right = node.Right;
|
||||
|
||||
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)
|
||||
{
|
||||
GenerateMethodSource(left, sc, func_list, context);
|
||||
} else
|
||||
if (right is not null) {
|
||||
GenerateMethodSource(right, sc, func_list, context);
|
||||
RegexCharSetPayload payload = value.PayloadAs<RegexCharSetPayload>();
|
||||
string[] rules = new string[payload.Set.Count];
|
||||
for (int i = 0; i < rules.Length; i++) {
|
||||
rules[i] = GenerateCharSetExpression(payload.Set[i]);
|
||||
}
|
||||
|
||||
string group_name = config.Name;
|
||||
|
||||
// TODO(NGRA): Allocate numeric/named capture slots and emit the successful capture
|
||||
// span. Non-capturing groups still need a rollback scope but no stored capture.
|
||||
throw new NotImplementedException();
|
||||
// Combine rule-level complements before applying the enclosing charset complement.
|
||||
// An empty union accepts nothing; its complement accepts any available character.
|
||||
string predicate = rules.Length == 0 ? "false" : string.Join(" || ", rules);
|
||||
string mismatch = payload.Accept ? $"!({predicate})" : $"({predicate})";
|
||||
string remain_span = context.RemainSpan;
|
||||
_ = sc.AppendLine($"if ({remain_span}.IsEmpty || {mismatch})")
|
||||
.AppendLine("{")
|
||||
.AppendLine($"goto {exit.GetLabelName()};")
|
||||
.AppendLine("}")
|
||||
.AppendLine($"{remain_span} = {remain_span}.Slice(1);");
|
||||
}
|
||||
|
||||
private static void GenerateConcat(
|
||||
RegexAstNode node,
|
||||
private static void GenerateUnitSpan(
|
||||
RegexAstNode value,
|
||||
[NotNull] in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context
|
||||
in SourceGenContext context,
|
||||
in ExitLabel exit
|
||||
)
|
||||
{
|
||||
RegexAstNode left = node.Left;
|
||||
RegexAstNode right = node.Right;
|
||||
|
||||
if (left is not null)
|
||||
{
|
||||
// find
|
||||
if ((left.Label & RegexElementLabel.COUNT) == RegexElementLabel.COUNT &&
|
||||
(left.Right.Label & RegexElementLabel.UNIT_SINGLE) == RegexElementLabel.UNIT_SINGLE &&
|
||||
left.Right.Source.Source == "." &&
|
||||
right is not null)
|
||||
{
|
||||
// TODO(NGRA): Emit the unnamed FIND candidate loop and retry the right subtree
|
||||
// until it succeeds or no anchor candidate remains.
|
||||
GenerateMethodSource(right, sc, func_list, context, true);
|
||||
} else if (IsNamedFind(node))
|
||||
{
|
||||
if (right is not null)
|
||||
{
|
||||
// TODO(NGRA): Emit named FIND retry and capture the text preceding the
|
||||
// successful anchor candidate.
|
||||
GenerateMethodSource(right, sc, func_list, context, true);
|
||||
} else
|
||||
{
|
||||
// TODO(NGRA): Capture the remaining input for a terminal named dot-star.
|
||||
}
|
||||
} else
|
||||
{
|
||||
GenerateMethodSource(left, sc, func_list, context);
|
||||
if (right is not null) {
|
||||
GenerateMethodSource(right, sc, func_list, context);
|
||||
}
|
||||
}
|
||||
string literal = value.Value;
|
||||
if (literal.Length == 0) {
|
||||
throw new InvalidOperationException("UNIT_SPAN requires a non-empty literal.");
|
||||
}
|
||||
|
||||
// TODO(NGRA): Normalize ownership so the right subtree is emitted exactly once. It is
|
||||
// currently reachable both inside the ordinary CONCAT branch and here.
|
||||
if (right is not null)
|
||||
{
|
||||
GenerateMethodSource(right, sc, func_list, context);
|
||||
}
|
||||
// StartsWith also rejects input shorter than the decoded literal.
|
||||
string remain_span = context.RemainSpan;
|
||||
_ = sc.AppendLine($"if (!{remain_span}.{nameof(MemoryExtensions.StartsWith)}({FormatStringLiteral(literal)}))")
|
||||
.AppendLine("{")
|
||||
.AppendLine($"goto {exit.GetLabelName()};")
|
||||
.AppendLine("}")
|
||||
.AppendLine($"{remain_span} = {remain_span}.Slice({literal.Length});");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UNIT count matching
|
||||
|
||||
private const int flattened_unit_count_threshold = 32;
|
||||
|
||||
private static void GenerateUnitCount(
|
||||
@@ -435,19 +302,15 @@ namespace Nitload.RegularAnalysis.Text
|
||||
int additional_limit = max < 0 ? -1 : max - min;
|
||||
if (is_greedy && additional_limit != 0)
|
||||
{
|
||||
if (additional_limit < 0)
|
||||
{
|
||||
_ = sc.AppendLine($"while (!{remain_span}.IsEmpty && {remain_span}[0] == {expected_literal})")
|
||||
.AppendLine("{")
|
||||
.AppendLine($"{remain_span} = {remain_span}.Slice(1);")
|
||||
.AppendLine("}");
|
||||
} else
|
||||
{
|
||||
_ = sc.AppendLine($"for (int {additional_count} = 0; {additional_count} < {additional_limit} && !{remain_span}.IsEmpty && {remain_span}[0] == {expected_literal}; {additional_count}++)")
|
||||
.AppendLine("{")
|
||||
.AppendLine($"{remain_span} = {remain_span}.Slice(1);")
|
||||
.AppendLine("}");
|
||||
}
|
||||
_ = additional_limit < 0 ?
|
||||
sc.AppendLine($"while (!{remain_span}.IsEmpty && {remain_span}[0] == {expected_literal})")
|
||||
.AppendLine("{")
|
||||
.AppendLine($"{remain_span} = {remain_span}.Slice(1);")
|
||||
.AppendLine("}") :
|
||||
sc.AppendLine($"for (int {additional_count} = 0; {additional_count} < {additional_limit} && !{remain_span}.IsEmpty && {remain_span}[0] == {expected_literal}; {additional_count}++)")
|
||||
.AppendLine("{")
|
||||
.AppendLine($"{remain_span} = {remain_span}.Slice(1);")
|
||||
.AppendLine("}");
|
||||
} else if (!is_greedy && additional_limit != 0)
|
||||
{
|
||||
// Lazy COUNT deliberately stops at Min. Its optional tail is consumed only when
|
||||
@@ -673,12 +536,156 @@ namespace Nitload.RegularAnalysis.Text
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateCount(
|
||||
#endregion
|
||||
|
||||
#region UNIT find matching
|
||||
|
||||
// TODO(NGRA): Add UNIT, UNIT_SET, and UNIT_SPAN candidate search emitters here.
|
||||
// Wire is_find dispatch and continuation retries when FIND matching is implemented.
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operator generation
|
||||
|
||||
private static void GenerateAlt(
|
||||
RegexAstNode node,
|
||||
[NotNull] in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context
|
||||
)
|
||||
{
|
||||
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.
|
||||
ExitLabel exit_label = context.RequestExitLabel(ExitMode.Goto);
|
||||
|
||||
// TODO(NGRA): Walk every normalized ALT candidate exactly once. The final traversal
|
||||
// shape and failure continuation are not wired yet.
|
||||
while (RegexAstNode.IsNullOrEmpty(current))
|
||||
{
|
||||
RegexElementLabel op = current.Label & RegexElementLabel.SEQUENCE;
|
||||
if (op != 0)
|
||||
{
|
||||
string code = current.SourceCode;
|
||||
switch (op)
|
||||
{
|
||||
case RegexElementLabel.UNIT:
|
||||
|
||||
// TODO(NGRA): Guard against an empty remaining span before indexing it.
|
||||
_ = sc.AppendLine(@$"if({context.RemainSpan}[0] == {FormatCharLiteral(code[0])})")
|
||||
.AppendLine("{")
|
||||
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
|
||||
.AppendLine($@"goto {exit_label};")
|
||||
.AppendLine("}");
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SET:
|
||||
|
||||
// TODO(NGRA): Guard the remaining span, handle empty/Any sets, and
|
||||
// validate negated-set composition before emitting this predicate.
|
||||
RegexCharSetPayload payload = current.PayloadAs<RegexCharSetPayload>();
|
||||
IReadOnlyList<RegexCharSet> split = payload.Set;
|
||||
bool accept = payload.Accept;
|
||||
RegexCharSet _set = split[0];
|
||||
_ = sc.AppendLine($"if({(accept ? string.Empty : "!(")} {GenerateCharSetExpression(_set)} ||");
|
||||
for (int i = 1; i < split.Count - 1; i++)
|
||||
{
|
||||
_set = split[i];
|
||||
_ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ||");
|
||||
}
|
||||
_set = split[^1];
|
||||
_ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ){(accept ? string.Empty : ')')}")
|
||||
.AppendLine("{")
|
||||
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
|
||||
.AppendLine($@"goto {exit_label};")
|
||||
.AppendLine("}");
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SPAN:
|
||||
|
||||
// TODO(NGRA): Advance by code.Length rather than one character and
|
||||
// preserve the ALT rollback cursor on failure.
|
||||
string literal = FormatStringLiteral(code);
|
||||
_ = sc.AppendLine(@$"if({context.RemainSpan}.{MemoryExtensions.StartsWith}({literal}))")
|
||||
.AppendLine("{")
|
||||
.AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice({code.Length});")
|
||||
.AppendLine($@"goto {exit_label};")
|
||||
.AppendLine("}");
|
||||
|
||||
break;
|
||||
default:
|
||||
|
||||
// TODO(NGRA): Route unsupported ALT candidates through the recursive
|
||||
// matcher or report a source-generation diagnostic.
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(NGRA): Advance to the next ALT candidate after a scalar candidate
|
||||
// misses; the current sequence branch does not update current.
|
||||
} else
|
||||
{
|
||||
GenerateMethodSource(current, sc, func_list, context);
|
||||
current = current.Next;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(NGRA): Emit the all-alternatives-failed continuation before the shared success
|
||||
// label, including restoration of the entry cursor and capture state.
|
||||
_ = sc.AppendLine($@"{exit_label}: {{ }}");
|
||||
}
|
||||
|
||||
private static void GenerateGroup(
|
||||
RegexAstNode node,
|
||||
[NotNull] in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context
|
||||
)
|
||||
{
|
||||
RegexAstNode left = node.Left;
|
||||
RegexAstNode right = node.Right;
|
||||
|
||||
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)
|
||||
{
|
||||
GenerateMethodSource(left, sc, func_list, context);
|
||||
} else
|
||||
if (right is not null) {
|
||||
GenerateMethodSource(right, sc, func_list, context);
|
||||
}
|
||||
|
||||
string group_name = config.Name;
|
||||
|
||||
// TODO(NGRA): Allocate numeric/named capture slots and emit the successful capture
|
||||
// span. Non-capturing groups still need a rollback scope but no stored capture.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static void GenerateConcat(
|
||||
RegexAstNode node,
|
||||
[NotNull] in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context,
|
||||
in ExitLabel exit
|
||||
in ExitLabel exit_label = default
|
||||
)
|
||||
{
|
||||
if (!RegexAstNode.IsNullOrEmpty(node.Left)) {
|
||||
GenerateMethodSource(node.Left, sc, func_list, context, exit_label);
|
||||
}
|
||||
if (!RegexAstNode.IsNullOrEmpty(node.Right)) {
|
||||
GenerateMethodSource(node.Right, sc, func_list, context, exit_label);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateCount(
|
||||
RegexAstNode node,
|
||||
[NotNull] in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context
|
||||
)
|
||||
{
|
||||
RegexAstNode value = RegexAstNode.IsNullOrEmpty(node.Left) ? node.Right : node.Left;
|
||||
|
||||
Reference in New Issue
Block a user