specific file for special regex rule
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# StgSharp.Native
|
||||
# Nitload.Native
|
||||
|
||||
## COMPILE
|
||||
|
||||
|
||||
@@ -101,6 +101,27 @@ namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
return node is null || ReferenceEquals(node, Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recognizes zero-or-more repetitions of one non-negated Any atom, including lazy
|
||||
/// repetition. Callers inspect the COUNT payload's IsGreedy flag separately.
|
||||
/// </summary>
|
||||
public static bool IsDotStar(RegexAstNode? node)
|
||||
{
|
||||
if (node?.Payload is not RegexCountPayload { IsStar: true }) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool left_empty = IsNullOrEmpty(node.Left);
|
||||
bool right_empty = IsNullOrEmpty(node.Right);
|
||||
if (left_empty == right_empty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RegexAstNode operand = left_empty ? node.Right : node.Left;
|
||||
return operand.Payload is RegexCharSetPayload { IsAny: true, Accept: true } set &&
|
||||
set.Set[0].Accept;
|
||||
}
|
||||
/**/
|
||||
|
||||
public void PrependNode(RegexAstNode previousNode)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="TextRegexSourceGen.GenSpecial"
|
||||
// Project: Nitload
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.RegularAnalysis.Abstraction;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal partial class TextRegexSourceGen
|
||||
{
|
||||
|
||||
private static bool TryGenerateSpecialSource(
|
||||
RegexAstNode node,
|
||||
in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context,
|
||||
in ExitLabel exit_label
|
||||
)
|
||||
{
|
||||
// A special rule owns the entire matched subtree. Returning false leaves emission
|
||||
// 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),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
#region FIND concatenation
|
||||
|
||||
private static bool TryGenerateFindConcat(
|
||||
RegexAstNode node,
|
||||
in SequenceEmitter<string> sc,
|
||||
in List<SequenceEmitter<string>> func_list,
|
||||
in SourceGenContext context,
|
||||
in ExitLabel exit_label
|
||||
)
|
||||
{
|
||||
RegexAstNode left = node.Left;
|
||||
RegexAstNode right = node.Right;
|
||||
if (RegexAstNode.IsNullOrEmpty(left)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Existing FIND dispatch scaffold; candidate search and retries are not implemented.
|
||||
// TODO(NGRA): Use the COUNT payload's IsGreedy flag to order FIND candidates.
|
||||
if (RegexAstNode.IsDotStar(left) && !RegexAstNode.IsNullOrEmpty(right))
|
||||
{
|
||||
// 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, exit_label, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsNamedFind(node))
|
||||
{
|
||||
if (!RegexAstNode.IsNullOrEmpty(right))
|
||||
{
|
||||
// TODO(NGRA): Emit named FIND retry and capture the text preceding the
|
||||
// successful anchor candidate.
|
||||
GenerateMethodSource(right, sc, func_list, context, exit_label, true);
|
||||
} else
|
||||
{
|
||||
// TODO(NGRA): Capture the remaining input for a terminal named dot-star.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsNamedFind(
|
||||
RegexAstNode node
|
||||
)
|
||||
{
|
||||
// (?<name>.*)abc
|
||||
|
||||
if (RegexAstNode.IsNullOrEmpty(node) || node.Label != RegexElementLabel.CONCAT) {
|
||||
return false;
|
||||
}
|
||||
RegexAstNode group = node.Left;
|
||||
if (RegexAstNode.IsNullOrEmpty(group) ||
|
||||
group.Payload is not RegexGroupPayload { IsCapturing: true } config ||
|
||||
string.IsNullOrEmpty(config.Name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool left_empty = RegexAstNode.IsNullOrEmpty(group.Left);
|
||||
bool right_empty = RegexAstNode.IsNullOrEmpty(group.Right);
|
||||
return left_empty != right_empty &&
|
||||
RegexAstNode.IsDotStar(left_empty ? group.Right : group.Left);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Repeated capture rules
|
||||
|
||||
// TODO(NGRA): Recognize and implement repeated captures such as (?<name>.*){m,n}
|
||||
// and (?<name>.*a){m,n}. Keep these distinct from (?<name>.*)a FIND concatenation.
|
||||
// A later repetition may need to reopen an earlier repetition's inner search, restoring
|
||||
// its cursor, candidate position, and capture state. Do not claim these subtrees in
|
||||
// TryGenerateSpecialSource until the complete retry/rollback strategy is implemented.
|
||||
// An escaped star is a literal and must not be classified as dot-star repetition.
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -1,22 +1,22 @@
|
||||
# Source Projects
|
||||
|
||||
This directory contains the production source code, native runtime, supporting applications, and benchmarks for StgSharp. Most managed projects target .NET 8. The native project is built separately with CMake and provides platform and SIMD functionality consumed by managed projects.
|
||||
This directory contains the production source code, native runtime, supporting applications, and benchmarks for Nitload. Most managed projects target .NET 8. The native project is built separately with CMake and provides platform and SIMD functionality consumed by managed projects.
|
||||
|
||||
## Directory map
|
||||
|
||||
| Directory | Purpose |
|
||||
| --- | --- |
|
||||
| `StgSharp.Common` | Shared collections, memory management, threading, pipelines, platform abstractions, and other foundational APIs. |
|
||||
| `StgSharp.Mathematics` | Numeric types, vectors, matrices, geometry, and managed access to accelerated math operations. |
|
||||
| `StgSharp.Graphics` | OpenGL bindings, rendering infrastructure, shaders, textures, windows, and input integration. |
|
||||
| `StgSharp.Native` | C native runtime, GLFW/STBI integration, allocators, platform code, and optimized SIMD/matrix kernels. See its own `README.md` for native build details. |
|
||||
| `StgSharp.RegularAnalysis` | NGRA regular-language analysis, regex processing, intermediate representations, and source-generation infrastructure. |
|
||||
| `StgSharp.UserInterface` | State-oriented user-interface framework and its incremental source generator. |
|
||||
| `StgSharp.TerminalDialogue` | Terminal user-interface components and an interactive terminal application. |
|
||||
| `StgSharp.Benchmark` | BenchmarkDotNet workloads for measuring selected runtime and allocation behavior. |
|
||||
| `StgSharp.Script` | Retained sources from the earlier EXPRESS implementation. The planned Script layer has not started and is expected to build on NGRA. |
|
||||
| `StgSharp.Model` | Placeholder project whose design is being reconsidered. |
|
||||
| `StgSharp.Tools` | Small retained comparison and inspection artifacts; it is not currently a buildable project. |
|
||||
| `Nitload.Common` | Shared collections, memory management, threading, pipelines, platform abstractions, and other foundational APIs. |
|
||||
| `Nitload.Mathematics` | Numeric types, vectors, matrices, geometry, and managed access to accelerated math operations. |
|
||||
| `Nitload.Graphics` | OpenGL bindings, rendering infrastructure, shaders, textures, windows, and input integration. |
|
||||
| `Nitload.Native` | C native runtime, GLFW/STBI integration, allocators, platform code, and optimized SIMD/matrix kernels. See its own `README.md` for native build details. |
|
||||
| `Nitload.RegularAnalysis` | NGRA regular-language analysis, regex processing, intermediate representations, and source-generation infrastructure. |
|
||||
| `Nitload.UserInterface` | State-oriented user-interface framework and its incremental source generator. |
|
||||
| `Nitload.TerminalDialogue` | Terminal user-interface components and an interactive terminal application. |
|
||||
| `Nitload.Benchmark` | BenchmarkDotNet workloads for measuring selected runtime and allocation behavior. |
|
||||
| `Nitload.Script` | Retained sources from the earlier EXPRESS implementation. The planned Script layer has not started and is expected to build on NGRA. |
|
||||
| `Nitload.Model` | Placeholder project whose design is being reconsidered. |
|
||||
| `Nitload.Tools` | Small retained comparison and inspection artifacts; it is not currently a buildable project. |
|
||||
|
||||
## Build notes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user