remove useless node from source gen pipeline. add source gen logic

This commit is contained in:
Youmu Liu
2026-08-21 18:31:31 +08:00
parent 968a6afe1a
commit f0b6a4038e
15 changed files with 219 additions and 334 deletions
+1 -1
View File
@@ -225,7 +225,7 @@ allocator.Free(handle);
### Technical Documentation ### Technical Documentation
- [TLSF Allocator](src/StgSharp.Common/HighPerformance/Memory/InroductionToTLSF.md) - Arena layout, allocation behavior, and benchmark methodology - [TLSF Allocator](doc/InroductionToTLSF.md) - Arena layout, allocation behavior, and benchmark methodology
- [Native Library Build](src/StgSharp.Native/README.md) - Native build and source-layout notes - [Native Library Build](src/StgSharp.Native/README.md) - Native build and source-layout notes
- [Native Library Naming](src/StgSharp.Native/naming.md) - Native library file naming conventions - [Native Library Naming](src/StgSharp.Native/naming.md) - Native library file naming conventions
@@ -124,24 +124,6 @@ namespace StgSharp.Collections
_index += span.Length; _index += span.Length;
} }
public void PushRange(
SpanAction<T, int> filling,
int count
)
{
if (count == 0) {
return;
}
if (_index + count > _values.Length) {
throw new InvalidOperationException("Not enough space in the stack to push the entire span.");
}
if (filling is not null)
{
filling(_values.AsSpan()[_index..(_index + count)], count);
_index += count;
}
}
public bool TryPop( public bool TryPop(
out T value out T value
) )
@@ -157,6 +139,18 @@ namespace StgSharp.Collections
return true; return true;
} }
internal void FillRange(
SpanAction<T, nuint> filling,
nuint buffer
)
{
if (filling is not null)
{
filling(_values.AsSpan()[_index..], buffer);
_index = _values.Length;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span<T> GetValueLayout() internal Span<T> GetValueLayout()
{ {
@@ -41,16 +41,16 @@ namespace StgSharp.HighPerformance.Memory
_Capacity = count; _Capacity = count;
_recycle = new((int)_Capacity); _recycle = new((int)_Capacity);
_recycle.PushRange(init, (int)count); _recycle.FillRange(init, buffer);
_currentIndex = _Capacity; _currentIndex = _Capacity;
static void init( static void init(
Span<nuint> arr, Span<nuint> arr,
int _ nuint buffer
) )
{ {
for (int i = 0; i < arr.Length; i++) { for (int i = 0; i < arr.Length; i++) {
arr[i] = (nuint)i; arr[i] = buffer + ((nuint)i * (nuint)Unsafe.SizeOf<T>());
} }
} }
} }
@@ -25,8 +25,6 @@ namespace StgSharp.Timing
private EventHandler? _refreshHandler, _missHandler; private EventHandler? _refreshHandler, _missHandler;
private int _size, _id; private int _size, _id;
private long _pendingSpans; // Number of unconsumed span ticks since last Wait. private long _pendingSpans; // Number of unconsumed span ticks since last Wait.
private readonly ManualResetEventSlim _event = new();
private readonly object _participantObject = new(); private readonly object _participantObject = new();
public event EventHandler MissTimeSpanRefreshed public event EventHandler MissTimeSpanRefreshed
@@ -57,15 +55,17 @@ namespace StgSharp.Timing
/// </summary> /// </summary>
public long PendingSpanCount => Volatile.Read(ref _pendingSpans); public long PendingSpanCount => Volatile.Read(ref _pendingSpans);
internal ManualResetEventSlim AwaitingEvent => _event; internal ManualResetEventSlim ResetEvent { get; } = new();
public void Dispose() public void Dispose()
{ {
#pragma warning disable CA1816
// mark provider as ended so external users can observe terminal state // mark provider as ended so external users can observe terminal state
Volatile.Write(ref _endedFlag, 1); Volatile.Write(ref _endedFlag, 1);
s_unusedID.Push(TokenID); s_unusedID.Push(TokenID);
_event.Set(); ResetEvent.Set();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
#pragma warning restore CA1816
} }
public override int GetHashCode() public override int GetHashCode()
@@ -84,7 +84,7 @@ namespace StgSharp.Timing
public void WaitNextSpan() public void WaitNextSpan()
{ {
_event.Wait(); ResetEvent.Wait();
// Consume one span slot (clamp at 0 so multiple subscribers won't make it negative). // Consume one span slot (clamp at 0 so multiple subscribers won't make it negative).
while (true) while (true)
@@ -99,7 +99,7 @@ namespace StgSharp.Timing
if (cur - 1 == 0) if (cur - 1 == 0)
{ {
// Last consumer resets the gate for the next tick. // Last consumer resets the gate for the next tick.
_event.Reset(); ResetEvent.Reset();
} }
return; return;
} }
@@ -108,7 +108,7 @@ namespace StgSharp.Timing
public async Task WaitNextSpanAsync() public async Task WaitNextSpanAsync()
{ {
await Task.Run(() => _event.Wait()).ConfigureAwait(false); await Task.Run(ResetEvent.Wait).ConfigureAwait(false);
while (true) while (true)
{ {
long cur = Volatile.Read(ref _pendingSpans); long cur = Volatile.Read(ref _pendingSpans);
@@ -120,7 +120,7 @@ namespace StgSharp.Timing
if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur) if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur)
{ {
if (cur - 1 == 0) { if (cur - 1 == 0) {
_event.Reset(); ResetEvent.Reset();
} }
break; break;
@@ -138,7 +138,7 @@ namespace StgSharp.Timing
) )
{ {
// Block the current thread until the event is signaled or cancellation requested. // Block the current thread until the event is signaled or cancellation requested.
_event.Wait(ct); ResetEvent.Wait(ct);
// consume one pending span (same logic as non-cancellable variant) // consume one pending span (same logic as non-cancellable variant)
while (true) while (true)
@@ -152,7 +152,7 @@ namespace StgSharp.Timing
if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur) if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur)
{ {
if (cur - 1 == 0) { if (cur - 1 == 0) {
_event.Reset(); ResetEvent.Reset();
} }
break; break;
@@ -203,7 +203,7 @@ namespace StgSharp.Timing
if (Interlocked.CompareExchange(ref _pendingSpans, toIssue, 0) == 0) if (Interlocked.CompareExchange(ref _pendingSpans, toIssue, 0) == 0)
{ {
// Open the gate for all current waiters (up to `_size`). // Open the gate for all current waiters (up to `_size`).
_event.Set(); ResetEvent.Set();
_refreshHandler?.Invoke(this, EventArgs.Empty); _refreshHandler?.Invoke(this, EventArgs.Empty);
} }
} else } else
@@ -47,7 +47,7 @@ namespace StgSharp.Timing
} }
TokenID = id; TokenID = id;
_size = maxWaitCount > 0 ? maxWaitCount : 1; _size = maxWaitCount > 0 ? maxWaitCount : 1;
_event = new ManualResetEventSlim(initialState:false, spinCount:0); ResetEvent = new ManualResetEventSlim(initialState:false, spinCount:0);
_pendingSpans = 0; _pendingSpans = 0;
} }
@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.Common.Timing
{
internal class TimeThreadProfile
{
}
}
@@ -20,8 +20,7 @@ namespace StgSharp.RegularAnalysis.Text
public RegexAstNode( public RegexAstNode(
Token<RegexElementLabel> source Token<RegexElementLabel> source
) )
: this(RegexCommonPayload.FromToken(source)) : this(RegexCommonPayload.FromToken(source)) { }
{ }
public RegexAstNode( public RegexAstNode(
RegexAstPayload payload RegexAstPayload payload
@@ -32,6 +31,8 @@ namespace StgSharp.RegularAnalysis.Text
public int EnumState { get; set; } public int EnumState { get; set; }
public ISyntaxPayload<RegexElementLabel> Source => Payload;
public long NodeFlag => (long)Label; public long NodeFlag => (long)Label;
public static RegexAstNode Empty { get; } public static RegexAstNode Empty { get; }
@@ -69,6 +70,8 @@ namespace StgSharp.RegularAnalysis.Text
} }
} }
public RegexAstPayload Payload { get; private protected set; }
public RegexElementLabel EqualityTypeConvert => Label; public RegexElementLabel EqualityTypeConvert => Label;
public RegexElementLabel Label => Payload.Flag; public RegexElementLabel Label => Payload.Flag;
@@ -80,13 +83,7 @@ namespace StgSharp.RegularAnalysis.Text
_ => Payload.Source _ => Payload.Source
}; };
public RegexAstPayload Payload { get; private protected set; } public string SourceCode => Source.Source;
public ISyntaxPayload<RegexElementLabel> Source => Payload;
internal TPayload PayloadAs<TPayload>() where TPayload : RegexAstPayload =>
Payload as TPayload ?? throw new InvalidOperationException(
$"Node {Label} does not carry {typeof(TPayload).Name}.");
public void AppendNode( public void AppendNode(
RegexAstNode nextToken RegexAstNode nextToken
@@ -148,6 +145,14 @@ namespace StgSharp.RegularAnalysis.Text
return new EmptyRegexAstNode(); return new EmptyRegexAstNode();
} }
internal TPayload PayloadAs<TPayload>() where TPayload : RegexAstPayload
{
return
Payload as TPayload ??
throw new InvalidOperationException(
$"Node {Label} does not carry {typeof(TPayload).Name}.");
}
internal void RemoveAndAsChild( internal void RemoveAndAsChild(
bool useLeft bool useLeft
) )
@@ -24,9 +24,9 @@ namespace StgSharp.RegularAnalysis.Text
category:"NGRA.TextRegex", category:"NGRA.TextRegex",
isEnabledByDefault:true); isEnabledByDefault:true);
private static readonly DiagnosticDescriptor CompileFailDiag = new(id:"NGRA005", private static readonly DiagnosticDescriptor CompileFailDiag = new(id:"NGRA006",
title:"Unexpected exception when compiling", title:"Regular expression compilation failed",
messageFormat:"Unexpected exception handles when compiling expression \"{0}\":{1}", messageFormat:"Failed to compile expression \"{0}\": {1}",
defaultSeverity:DiagnosticSeverity.Error, defaultSeverity:DiagnosticSeverity.Error,
category:"NGRA.TextRegex", category:"NGRA.TextRegex",
isEnabledByDefault:true); isEnabledByDefault:true);
@@ -39,8 +39,8 @@ namespace StgSharp.RegularAnalysis.Text
isEnabledByDefault:true); isEnabledByDefault:true);
private static readonly DiagnosticDescriptor IncorrectReturnTypeDiag = new(id:"NGRA003", private static readonly DiagnosticDescriptor IncorrectReturnTypeDiag = new(id:"NGRA003",
title:"Text regex binding must be partial", title:$"Returning type must be {typeof(TextRegex).FullName}",
messageFormat:"Method {0} must be partial to receive generated code", messageFormat:$"Method {{0}} must return {typeof(TextRegex).FullName}",
defaultSeverity:DiagnosticSeverity.Error, defaultSeverity:DiagnosticSeverity.Error,
category:"NGRA.TextRegex", category:"NGRA.TextRegex",
isEnabledByDefault:true); isEnabledByDefault:true);
@@ -53,8 +53,8 @@ namespace StgSharp.RegularAnalysis.Text
isEnabledByDefault:true); isEnabledByDefault:true);
private static readonly DiagnosticDescriptor NotPartialDiag = new(id:"NGRA001", private static readonly DiagnosticDescriptor NotPartialDiag = new(id:"NGRA001",
title:$"Returning type must be {typeof(TextRegex).FullName}", title:"Text regex binding must be partial",
messageFormat:$"Method {{0}} must return {typeof(TextRegex).FullName}", messageFormat:"Method {0} must be partial to receive generated code",
defaultSeverity:DiagnosticSeverity.Error, defaultSeverity:DiagnosticSeverity.Error,
category:"NGRA.TextRegex", category:"NGRA.TextRegex",
isEnabledByDefault:true); isEnabledByDefault:true);
@@ -24,6 +24,7 @@ namespace StgSharp.RegularAnalysis.Text
RegexAstNode ast, RegexAstNode ast,
in SequenceEmitter<string> sc, in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list, in List<SequenceEmitter<string>> func_list,
in SourceGenContext context,
bool is_find = false bool is_find = false
) )
@@ -38,16 +39,16 @@ namespace StgSharp.RegularAnalysis.Text
switch (op) switch (op)
{ {
case RegexElementLabel.CONCAT: case RegexElementLabel.CONCAT:
GenerateConcat(root, sc, func_list); GenerateConcat(root, sc, func_list, context);
break; break;
case RegexElementLabel.COUNT: case RegexElementLabel.COUNT:
GenerateCount(root, sc, func_list); GenerateCount(root, sc, func_list, context);
break; break;
case RegexElementLabel.GROUP_BEGIN: case RegexElementLabel.GROUP_BEGIN:
GenerateGroup(root, sc, func_list); GenerateGroup(root, sc, func_list, context);
break; break;
case RegexElementLabel.ALT: case RegexElementLabel.ALT:
GenerateAlt(root, sc, func_list); GenerateAlt(root, sc, func_list, context);
break; break;
} }
} else } else
@@ -67,6 +68,20 @@ namespace StgSharp.RegularAnalysis.Text
#region supporting method #region supporting method
private static string FormatStringLiteral(
string value
)
{
return SyntaxFactory.Literal(value).Text;
}
private static string FormatCharLiteral(
char value
)
{
return SyntaxFactory.Literal(value).Text;
}
private static bool IsNamedFind( private static bool IsNamedFind(
RegexAstNode node RegexAstNode node
) )
@@ -99,32 +114,48 @@ namespace StgSharp.RegularAnalysis.Text
private static void GenerateAlt( private static void GenerateAlt(
RegexAstNode node, RegexAstNode node,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list in List<SequenceEmitter<string>> func_list,
in SourceGenContext context
) )
{ {
RegexAstNode left = node.Left; RegexAstNode current = node.Right;
RegexAstNode right = node.Right; string exit_label = context.RequestExitAlt();
RegexAstNode current = left;
while (RegexAstNode.IsNullOrEmpty(current)) while (RegexAstNode.IsNullOrEmpty(current))
{ {
GenerateMethodSource(current, sc, func_list); RegexElementLabel op = current.Label & RegexElementLabel.SEQUENCE;
current = current.Next; if (op != 0)
}
current = right;
while (RegexAstNode.IsNullOrEmpty(current))
{ {
GenerateMethodSource(current, sc, func_list); string code = current.SourceCode;
switch (op)
{
case RegexElementLabel.UNIT:
_ = sc.AppendLine(@$"if({context.RemainSpan}[0] == {FormatCharLiteral(code[0])})")
.AppendLine("{")
.AppendLine($@"goto {exit_label};")
.AppendLine("}");
break;
case RegexElementLabel.UNIT_SET:
IReadOnlyList<RegexCharSet> split = current.PayloadAs<RegexCharSetPayload>()
.Set;
RegexCharSet first_set = split[0];
for (int i = 1; i < split.Count - 1; i++) { }
RegexCharSet last_set = split[^1];
break;
}
} else
{
GenerateMethodSource(current, sc, func_list, context);
current = current.Next; current = current.Next;
} }
} }
_ = sc.AppendLine($@"{exit_label}: {{ }}");
}
private static void GenerateGroup( private static void GenerateGroup(
RegexAstNode node, RegexAstNode node,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list in List<SequenceEmitter<string>> func_list,
in SourceGenContext context
) )
{ {
RegexAstNode left = node.Left; RegexAstNode left = node.Left;
@@ -134,10 +165,10 @@ namespace StgSharp.RegularAnalysis.Text
if (left is not null) if (left is not null)
{ {
GenerateMethodSource(left, sc, func_list); GenerateMethodSource(left, sc, func_list, context);
} else } else
if (right is not null) { if (right is not null) {
GenerateMethodSource(right, sc, func_list); GenerateMethodSource(right, sc, func_list, context);
} }
string group_name = config.Name; string group_name = config.Name;
@@ -147,7 +178,8 @@ namespace StgSharp.RegularAnalysis.Text
private static void GenerateConcat( private static void GenerateConcat(
RegexAstNode node, RegexAstNode node,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list in List<SequenceEmitter<string>> func_list,
in SourceGenContext context
) )
{ {
RegexAstNode left = node.Left; RegexAstNode left = node.Left;
@@ -162,40 +194,41 @@ namespace StgSharp.RegularAnalysis.Text
right is not null) right is not null)
{ {
// TODO use find // TODO use find
GenerateMethodSource(right, sc, func_list, true); GenerateMethodSource(right, sc, func_list, context, true);
} else if (IsNamedFind(node)) } else if (IsNamedFind(node))
{ {
if (right is not null) if (right is not null)
{ {
// TODO named find // TODO named find
GenerateMethodSource(right, sc, func_list, true); GenerateMethodSource(right, sc, func_list, context, true);
} else } else
{ {
// TODO pack rest of string to group // TODO pack rest of string to group
} }
} else } else
{ {
GenerateMethodSource(left, sc, func_list); GenerateMethodSource(left, sc, func_list, context);
if (right is not null) { if (right is not null) {
GenerateMethodSource(right, sc, func_list); GenerateMethodSource(right, sc, func_list, context);
} }
} }
} }
if (right is not null) { if (right is not null) {
GenerateMethodSource(right, sc, func_list); GenerateMethodSource(right, sc, func_list, context);
} }
} }
private static void GenerateCount( private static void GenerateCount(
RegexAstNode node, RegexAstNode node,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list in List<SequenceEmitter<string>> func_list,
in SourceGenContext context
) )
{ {
RegexAstNode value = node.Left ?? node.Right; RegexAstNode value = node.Left ?? node.Right;
if ((value.Label & RegexElementLabel.SEQUENCE) == 0) if ((value.Label & RegexElementLabel.SEQUENCE) == 0)
{ {
GenerateMethodSource(value, sc, func_list); GenerateMethodSource(value, sc, func_list, context);
} else } else
{ {
RegexCountPayload payload = value.PayloadAs<RegexCountPayload>(); RegexCountPayload payload = value.PayloadAs<RegexCountPayload>();
@@ -6,9 +6,11 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using StgSharp.PipeLine;
using StgSharp.RegularAnalysis.Abstraction; using StgSharp.RegularAnalysis.Abstraction;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -27,206 +29,33 @@ namespace StgSharp.RegularAnalysis.Text
/// a block scoped scratch variable. Scratch names are never reused, so a nested block can /// a block scoped scratch variable. Scratch names are never reused, so a nested block can
/// always declare its own without colliding with an enclosing one. /// always declare its own without colliding with an enclosing one.
/// </remarks> /// </remarks>
private sealed class SourceGenContext internal sealed class SourceGenContext
{ {
/// <summary> public const string __input_span = "input";
/// Name of the match method parameter holding the input sequence.
/// </summary>
public const string _inputSpan = "text";
/// <summary> public const string _remain_span = "__remain";
/// Result of the instruction emitted immediately before the current one.
/// </summary>
public const string _stepResult = "__ok";
private readonly Dictionary<string, int> _groupIndex = []; private int _exit_alt_count;
private readonly HashSet<int> _observedLines = [];
private int _anonymousGroup; public string RemainSpan { get; } = _remain_span;
private int _count_idx = 0; public string FirstOfRemain { get; } = $@"{_remain_span}[0]";
private int _scratchIndex;
private readonly SortedSet<int> _lineSlots = [];
private readonly SortedSet<int> _regionSlots = [];
public string RemainingSpan { get; } = "__remaining_span"; public string InputString { get; } = __input_span;
public string InputSpan { get; } = _inputSpan; public SequenceEmitter<string> DeclareGlobalFields()
public string StepResult { get; } = _stepResult;
// region id -> entry count from first to last occurrence, filled once per pattern
public int[] RegionExtent { get; set; } = [];
public string[] BeginRegion()
{ {
return [ SequenceEmitter<string> sc = new();
"do",
"{" return sc.AppendLine($@"{ROS_char} {RemainSpan};");
];
} }
public string[] CloseRegion() public string RequestExitAlt()
{ {
return [ string code = $@"_exit_alt_{_exit_alt_count}";
"} while(true);" _exit_alt_count++;
]; return code;
}
/// <summary>
/// Emits declarations for every slot requested during body generation.
/// </summary>
/// <remarks>
/// Must be called after the method body has been generated, but the produced lines
/// have to be placed before it.
/// </remarks>
public SequenceEmitter<string> EmitDeclarationsTo(
SequenceEmitter<string> emitter
)
{
if (emitter is null) {
return null!;
}
_ = emitter.AppendLine($@"{ROS_char} {RemainingSpan} = {_inputSpan};")
.AppendLine($@"bool {_stepResult} = true;");
foreach (int region in _regionSlots) {
_ = emitter.AppendLine($@"int {RegionAnchor(region)} = 0;")
.AppendLine($@"bool {RegionResult(region)} = false;");
}
foreach (int line in _lineSlots) {
_ = emitter.AppendLine($@"bool {LineResult(line)} = false;");
}
foreach (int group in _groupIndex.Values) {
_ = emitter.AppendLine($@"int __g{group}_begin = -1;")
.AppendLine($@"int __g{group}_end = -1;");
}
return emitter;
}
public string GetVarGroupBegin(
string name
)
{
return $"__g{ResolveGroup(name)}_begin";
}
public string GetVarGroupEnd(
string name
)
{
return $"__g{ResolveGroup(name)}_end";
}
/// <summary>
/// Indicates whether the instruction at line <paramref name="line" /> has to persist
/// its result into a line slot.
/// </summary>
public bool IsLineObserved(
int line
)
{
return _observedLines.Contains(line);
}
/// <summary>
/// Name of the slot holding the match result of the instruction at line <paramref
/// name="line" />. Only lines actually referenced by an <see
/// cref="RegexIRPrefixMask.Observe_Line" /> prefix get a slot.
/// </summary>
public string LineResult(
int line
)
{
_ = _lineSlots.Add(line);
return $"__l{line}_ok";
}
public string MakeRemainOffset(
string offset_name
)
{
return @$"{RemainingSpan} = {RemainingSpan}[{offset_name}..];";
}
public string MakeRemainOffset(
int offset
)
{
return @$"{RemainingSpan} = {RemainingSpan}[{offset}..];";
}
/// <summary>
/// Records that some prefix observes the result of the instruction at line <paramref
/// name="line" />.
/// </summary>
public void MarkLineObserved(
int line
)
{
_ = _observedLines.Add(line);
}
/// <summary>
/// Allocates a fresh block scoped scratch variable name.
/// </summary>
/// <param name="tag">
/// Short role marker, for example <c> n </c> for a counter or <c> ch </c> for a
/// character.
/// </param>
public string NewScratch(
string tag
)
{
return $"__{tag}{_scratchIndex++}";
}
/// <summary>
/// Name of the slot holding the cursor value captured when region <paramref
/// name="region" /> was entered.
/// </summary>
public string RegionAnchor(
int region
)
{
_ = _regionSlots.Add(region);
return $"__r{region}_begin";
}
/// <summary>
/// Name of the slot holding the match result of region <paramref name="region" />.
/// </summary>
public string RegionResult(
int region
)
{
_ = _regionSlots.Add(region);
return $"__r{region}_ok";
}
public string RequireNewCounter()
{
string counter_name = $"__count{_count_idx}";
_count_idx++;
return counter_name;
}
private int ResolveGroup(
string name
)
{
string key = string.IsNullOrEmpty(name) ? $"#{_anonymousGroup++}" : name;
if (_groupIndex.TryGetValue(key, out int index)) {
return index;
}
index = _groupIndex.Count;
_groupIndex[key] = index;
return index;
} }
} }
@@ -43,6 +43,8 @@ namespace StgSharp.RegularAnalysis.Text
SequenceEmitter<string> ce = new(); SequenceEmitter<string> ce = new();
SourceRecord gen_source = binding.Source; SourceRecord gen_source = binding.Source;
TypeSource type = binding.Source.ContainingType; TypeSource type = binding.Source.ContainingType;
SequenceEmitter<string> method_body = binding.GeneratedSource;
SourceGenContext regex_gen_context = binding.SourceContext;
_ = file_name_sb.Append(CultureInfo.InvariantCulture, $"{gen_source.NamespaceName}."); _ = file_name_sb.Append(CultureInfo.InvariantCulture, $"{gen_source.NamespaceName}.");
_ = ce.AppendLine($@"// <auto-generated/>") _ = ce.AppendLine($@"// <auto-generated/>")
@@ -75,15 +77,12 @@ namespace StgSharp.RegularAnalysis.Text
} while (type is not null); } while (type is not null);
static_modifier = gen_source.IsMethodStatic ? "static" : string.Empty; static_modifier = gen_source.IsMethodStatic ? "static" : string.Empty;
_ = ce.AppendLine(@$"{ToKeyword(gen_source.MethodAccessibility)} {static_modifier} {Text_Regex} {gen_source.MethodName}()") _ = ce.AppendLine(@$"{ToKeyword(gen_source.MethodAccessibility)} {static_modifier} {Text_Regex} {gen_source.MethodName}({ROS_char} {regex_gen_context.InputString})")
.AppendLine("{") .AppendLine("{")
/**/ //TODO return single instance of the TextRegex object .Append(method_body)
.AppendLine("}"); .AppendLine("}");
// TODO inject method source code here for (int i = 0; i < brace_count; i++) {
for (int i = 0; i < brace_count; i++)
{
_ = ce.AppendLine("}"); _ = ce.AppendLine("}");
} }
@@ -104,10 +103,6 @@ namespace StgSharp.RegularAnalysis.Text
_ = file_name_sb.Append(CultureInfo.InvariantCulture, $@"{binding.Source.MethodName}.g.cs"); _ = file_name_sb.Append(CultureInfo.InvariantCulture, $@"{binding.Source.MethodName}.g.cs");
string file_name = file_name_sb.ToString(); string file_name = file_name_sb.ToString();
context.AddSource(file_name, code_source.ToString()); context.AddSource(file_name, code_source.ToString());
foreach (Diagnostic diag in binding.RegexDiagnostic) {
context.ReportDiagnostic(diag);
}
} }
private static string ToKeyword( private static string ToKeyword(
@@ -76,12 +76,17 @@ namespace StgSharp.RegularAnalysis.Text
private TextRegexBinding( private TextRegexBinding(
List<Diagnostic> diagnostics, List<Diagnostic> diagnostics,
SequenceEmitter<string> generatedSource, SequenceEmitter<string> generatedSource,
SourceRecord source SourceRecord source,
SourceGenContext sourceContext,
TextRegexSource? analyzedSource
) )
{ {
_diagnostics = diagnostics ?? []; _diagnostics = diagnostics ?? [];
GeneratedSource = generatedSource ?? new(); GeneratedSource = generatedSource ?? new();
Source = source; Source = source;
AnalyzedSource = analyzedSource;
IsValid = !_diagnostics.Exists(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
SourceContext = sourceContext;
} }
public TextRegexBinding() public TextRegexBinding()
@@ -89,11 +94,15 @@ namespace StgSharp.RegularAnalysis.Text
_diagnostics = []; _diagnostics = [];
} }
public TextRegexSource? AnalyzedSource { get; set; }
public bool IsValid { get; private set; } = true; public bool IsValid { get; private set; } = true;
public IReadOnlyList<Diagnostic> RegexDiagnostic => _diagnostics; public IReadOnlyList<Diagnostic> RegexDiagnostic => _diagnostics;
public SequenceEmitter<string> GeneratedSource { get; set; } public SequenceEmitter<string> GeneratedSource { get; set; } = new();
public SourceGenContext SourceContext { get; set; } = null!;
public SourceRecord Source { get; set; } public SourceRecord Source { get; set; }
@@ -121,7 +130,8 @@ namespace StgSharp.RegularAnalysis.Text
SequenceEmitter<string> code = GeneratedSource.Clone(); SequenceEmitter<string> code = GeneratedSource.Clone();
List<Diagnostic> diag = new(_diagnostics.Count); List<Diagnostic> diag = new(_diagnostics.Count);
diag.AddRange(this._diagnostics); diag.AddRange(this._diagnostics);
TextRegexBinding binding = new(diag, code, Source with { }); TextRegexBinding binding = new(diag, code, Source with { }, SourceContext,
AnalyzedSource);
return binding; return binding;
} }
@@ -9,14 +9,8 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using StgSharp.RegularAnalysis.Abstraction; using StgSharp.RegularAnalysis.Abstraction;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.AccessControl;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.RegularAnalysis.Text namespace StgSharp.RegularAnalysis.Text
{ {
@@ -30,7 +24,10 @@ namespace StgSharp.RegularAnalysis.Text
IncrementalGeneratorInitializationContext context IncrementalGeneratorInitializationContext context
) )
{ {
IncrementalValuesProvider<TextRegexBinding> provider = context.SyntaxProvider IncrementalValuesProvider<TextRegexBinding> candidates =
//
context.SyntaxProvider
.ForAttributeWithMetadataName<TextRegexBinding>( .ForAttributeWithMetadataName<TextRegexBinding>(
TextRegexTypeName, TextRegexTypeName,
static( static(
@@ -38,13 +35,43 @@ namespace StgSharp.RegularAnalysis.Text
_ _
) => true, BindTextRegexCandidate); ) => true, BindTextRegexCandidate);
IncrementalValuesProvider<TextRegexBinding> match = RegisterDiagnosticBypass(context, provider); IncrementalValuesProvider<TextRegexBinding> validCandidates =
RegisterDiagnosticBypass(context, candidates);
IncrementalValuesProvider<TextRegexBinding> compile = match.Select(CompileRegexMethodSource); IncrementalValuesProvider<TextRegexBinding> analyzed =
validCandidates.Select(AnalyzeRegexMethod);
IncrementalValuesProvider<TextRegexBinding> compile_success = RegisterDiagnosticBypass(context, compile); IncrementalValuesProvider<TextRegexBinding> validAnalyzed =
RegisterDiagnosticBypass(context, analyzed);
context.RegisterSourceOutput(compile_success, GenerateRegexSourceFile); IncrementalValuesProvider<TextRegexBinding> generated =
validAnalyzed.Select(CompileRegexMethodSource);
IncrementalValuesProvider<TextRegexBinding> validGenerated =
RegisterDiagnosticBypass(context, generated);
context.RegisterSourceOutput(validGenerated, GenerateRegexSourceFile);
}
private static TextRegexBinding AnalyzeRegexMethod(
TextRegexBinding binding,
CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
if (!binding.IsValid) {
return binding;
}
TextRegexBinding result = binding.Clone();
string pattern = result.Source.Pattern;
TextRegexSource analyzedSource = RegexAnalyzer.Analyze(pattern);
result.AnalyzedSource = analyzedSource;
if (analyzedSource.AnalyseException is not null) {
result.AddDiagnostic(CompileFailDiag, result.Source.PatternDefineLocation, pattern,
analyzedSource.AnalyseException.Message);
}
return result;
} }
private static TextRegexBinding BindTextRegexCandidate( private static TextRegexBinding BindTextRegexCandidate(
@@ -71,7 +98,7 @@ namespace StgSharp.RegularAnalysis.Text
TextRegexBinding bind = new(); TextRegexBinding bind = new();
if (!method_decl.Modifiers.Any(SyntaxKind.PartialKeyword)) { if (!method_decl.Modifiers.Any(SyntaxKind.PartialKeyword)) {
bind.AddDiagnostic(NotPartialDiag, method_decl.GetLocation(), method_decl.GetText()); bind.AddDiagnostic(NotPartialDiag, method_decl.GetLocation(), method_symbol.Name);
} }
if (method_decl.ParameterList.Parameters.Count != 0) { if (method_decl.ParameterList.Parameters.Count != 0) {
bind.AddDiagnostic(NotParameterLessDiag, method_decl.GetLocation(), method_decl.GetText()); bind.AddDiagnostic(NotParameterLessDiag, method_decl.GetLocation(), method_decl.GetText());
@@ -97,9 +124,9 @@ namespace StgSharp.RegularAnalysis.Text
while (contain is not null) while (contain is not null)
{ {
if (contain.IsRecord) { if (contain.IsRecord) {
bind.AddDiagnostic(CanNotBeRecordDiag, method_decl.GetLocation(), method_symbol.Name); bind.AddDiagnostic(CanNotBeRecordDiag, method_decl.GetLocation(), contain.Name);
} }
if (contain.TypeKind is not TypeKind.Class or TypeKind.Struct) { if (contain.TypeKind is not (TypeKind.Class or TypeKind.Struct)) {
bind.AddDiagnostic(IncorrectContainingTypeDiag, method_decl.GetLocation(), bind.AddDiagnostic(IncorrectContainingTypeDiag, method_decl.GetLocation(),
method_symbol.Name, contain.TypeKind); method_symbol.Name, contain.TypeKind);
} }
@@ -133,23 +160,28 @@ namespace StgSharp.RegularAnalysis.Text
} }
private static TextRegexBinding CompileRegexMethodSource( private static TextRegexBinding CompileRegexMethodSource(
TextRegexBinding source, TextRegexBinding binding,
CancellationToken ct CancellationToken cancellationToken
) )
{ {
ct.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
TextRegexBinding result = source.Clone();
string pattern = result.Source.Pattern; TextRegexBinding result = binding.Clone();
TextRegexSource analyze_result = RegexAnalyzer.Analyze(pattern); RegexAstNode root = result.AnalyzedSource!.Ast.Root;
if (analyze_result.AnalyseException is not null) SourceGenContext generationContext = new();
{ SequenceEmitter<string> methodSource = new();
result.AddDiagnostic(CompileFailDiag, null, analyze_result.AnalyseException.Message); List<SequenceEmitter<string>> localFunctions = [];
} else
{ GenerateMethodSource(root, methodSource, localFunctions, generationContext);
SequenceEmitter<string> method_source = /**/ new()/*GenerateSource(analyze_result)/**/; foreach (SequenceEmitter<string> localFunction in localFunctions) {
result.GeneratedSource = method_source; _ = methodSource.AppendLine()
.AppendLine()
.AppendLine("// local method for matching")
.Append(localFunction);
} }
result.SourceContext = generationContext;
result.GeneratedSource = methodSource;
return result; return result;
} }
@@ -158,19 +190,18 @@ namespace StgSharp.RegularAnalysis.Text
IncrementalValuesProvider<TextRegexBinding> bindings IncrementalValuesProvider<TextRegexBinding> bindings
) )
{ {
IncrementalValuesProvider<Diagnostic> error_path = bindings.Where(static binding => !binding.IsValid) IncrementalValuesProvider<Diagnostic> errorPath =
bindings.Where(static binding => !binding.IsValid)
.SelectMany(static( .SelectMany(static(
binding, binding,
_ _
) => binding.RegexDiagnostic); ) => binding.RegexDiagnostic);
IncrementalValuesProvider<TextRegexBinding> correct_path = bindings.Where(static binding => binding.IsValid); context.RegisterSourceOutput(errorPath, static(
productionContext,
diagnostic
) => productionContext.ReportDiagnostic(diagnostic));
context.RegisterSourceOutput(error_path, ( return bindings.Where(static binding => binding.IsValid);
c,
diag
) => c.ReportDiagnostic(diag));
return correct_path;
} }
} }