diff --git a/ReadMe.md b/ReadMe.md index 955dea1b..2df546a0 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -225,7 +225,7 @@ allocator.Free(handle); ### 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 Naming](src/StgSharp.Native/naming.md) - Native library file naming conventions diff --git a/src/StgSharp.Common/HighPerformance/Memory/InroductionToTLSF.md b/doc/InroductionToTLSF.md similarity index 100% rename from src/StgSharp.Common/HighPerformance/Memory/InroductionToTLSF.md rename to doc/InroductionToTLSF.md diff --git a/src/StgSharp.Common/HighPerformance/Memory/L4_design.md b/doc/L4_design.md similarity index 100% rename from src/StgSharp.Common/HighPerformance/Memory/L4_design.md rename to doc/L4_design.md diff --git a/src/StgSharp.Common/Collections/CapacityFixedStack.cs b/src/StgSharp.Common/Collections/CapacityFixedStack.cs index 34871971..a8021e53 100644 --- a/src/StgSharp.Common/Collections/CapacityFixedStack.cs +++ b/src/StgSharp.Common/Collections/CapacityFixedStack.cs @@ -124,24 +124,6 @@ namespace StgSharp.Collections _index += span.Length; } - public void PushRange( - SpanAction 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( out T value ) @@ -157,6 +139,18 @@ namespace StgSharp.Collections return true; } + internal void FillRange( + SpanAction filling, + nuint buffer + ) + { + if (filling is not null) + { + filling(_values.AsSpan()[_index..], buffer); + _index = _values.Length; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Span GetValueLayout() { diff --git a/src/StgSharp.Common/HighPerformance/Memory/SlabAllocator.Fixed.cs b/src/StgSharp.Common/HighPerformance/Memory/SlabAllocator.Fixed.cs index 1486f784..0eb7a0c2 100644 --- a/src/StgSharp.Common/HighPerformance/Memory/SlabAllocator.Fixed.cs +++ b/src/StgSharp.Common/HighPerformance/Memory/SlabAllocator.Fixed.cs @@ -41,16 +41,16 @@ namespace StgSharp.HighPerformance.Memory _Capacity = count; _recycle = new((int)_Capacity); - _recycle.PushRange(init, (int)count); + _recycle.FillRange(init, buffer); _currentIndex = _Capacity; static void init( Span arr, - int _ + nuint buffer ) { for (int i = 0; i < arr.Length; i++) { - arr[i] = (nuint)i; + arr[i] = buffer + ((nuint)i * (nuint)Unsafe.SizeOf()); } } } diff --git a/src/StgSharp.Common/Timing/TimeSpanProvider.Awaiting.cs b/src/StgSharp.Common/Timing/TimeSpanProvider.Awaiting.cs index 947cb4c2..37d0731f 100644 --- a/src/StgSharp.Common/Timing/TimeSpanProvider.Awaiting.cs +++ b/src/StgSharp.Common/Timing/TimeSpanProvider.Awaiting.cs @@ -25,8 +25,6 @@ namespace StgSharp.Timing private EventHandler? _refreshHandler, _missHandler; private int _size, _id; private long _pendingSpans; // Number of unconsumed span ticks since last Wait. - - private readonly ManualResetEventSlim _event = new(); private readonly object _participantObject = new(); public event EventHandler MissTimeSpanRefreshed @@ -57,15 +55,17 @@ namespace StgSharp.Timing /// public long PendingSpanCount => Volatile.Read(ref _pendingSpans); - internal ManualResetEventSlim AwaitingEvent => _event; + internal ManualResetEventSlim ResetEvent { get; } = new(); public void Dispose() { +#pragma warning disable CA1816 // mark provider as ended so external users can observe terminal state Volatile.Write(ref _endedFlag, 1); s_unusedID.Push(TokenID); - _event.Set(); + ResetEvent.Set(); GC.SuppressFinalize(this); +#pragma warning restore CA1816 } public override int GetHashCode() @@ -84,7 +84,7 @@ namespace StgSharp.Timing public void WaitNextSpan() { - _event.Wait(); + ResetEvent.Wait(); // Consume one span slot (clamp at 0 so multiple subscribers won't make it negative). while (true) @@ -99,7 +99,7 @@ namespace StgSharp.Timing if (cur - 1 == 0) { // Last consumer resets the gate for the next tick. - _event.Reset(); + ResetEvent.Reset(); } return; } @@ -108,7 +108,7 @@ namespace StgSharp.Timing public async Task WaitNextSpanAsync() { - await Task.Run(() => _event.Wait()).ConfigureAwait(false); + await Task.Run(ResetEvent.Wait).ConfigureAwait(false); while (true) { long cur = Volatile.Read(ref _pendingSpans); @@ -120,7 +120,7 @@ namespace StgSharp.Timing if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur) { if (cur - 1 == 0) { - _event.Reset(); + ResetEvent.Reset(); } break; @@ -138,7 +138,7 @@ namespace StgSharp.Timing ) { // 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) while (true) @@ -152,7 +152,7 @@ namespace StgSharp.Timing if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur) { if (cur - 1 == 0) { - _event.Reset(); + ResetEvent.Reset(); } break; @@ -203,7 +203,7 @@ namespace StgSharp.Timing if (Interlocked.CompareExchange(ref _pendingSpans, toIssue, 0) == 0) { // Open the gate for all current waiters (up to `_size`). - _event.Set(); + ResetEvent.Set(); _refreshHandler?.Invoke(this, EventArgs.Empty); } } else diff --git a/src/StgSharp.Common/Timing/TimeSpanProvider.cs b/src/StgSharp.Common/Timing/TimeSpanProvider.cs index 0b9be884..1e796c10 100644 --- a/src/StgSharp.Common/Timing/TimeSpanProvider.cs +++ b/src/StgSharp.Common/Timing/TimeSpanProvider.cs @@ -47,7 +47,7 @@ namespace StgSharp.Timing } TokenID = id; _size = maxWaitCount > 0 ? maxWaitCount : 1; - _event = new ManualResetEventSlim(initialState:false, spinCount:0); + ResetEvent = new ManualResetEventSlim(initialState:false, spinCount:0); _pendingSpans = 0; } diff --git a/src/StgSharp.Common/Timing/TimeThreadProfile.cs b/src/StgSharp.Common/Timing/TimeThreadProfile.cs deleted file mode 100644 index 8929d33d..00000000 --- a/src/StgSharp.Common/Timing/TimeThreadProfile.cs +++ /dev/null @@ -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 - { - } -} diff --git a/src/StgSharp.RegularAnalysis/Text/RegexAstNode.cs b/src/StgSharp.RegularAnalysis/Text/RegexAstNode.cs index f77b10fa..53aa5368 100644 --- a/src/StgSharp.RegularAnalysis/Text/RegexAstNode.cs +++ b/src/StgSharp.RegularAnalysis/Text/RegexAstNode.cs @@ -20,8 +20,7 @@ namespace StgSharp.RegularAnalysis.Text public RegexAstNode( Token source ) - : this(RegexCommonPayload.FromToken(source)) - { } + : this(RegexCommonPayload.FromToken(source)) { } public RegexAstNode( RegexAstPayload payload @@ -32,6 +31,8 @@ namespace StgSharp.RegularAnalysis.Text public int EnumState { get; set; } + public ISyntaxPayload Source => Payload; + public long NodeFlag => (long)Label; 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 Label => Payload.Flag; @@ -80,13 +83,7 @@ namespace StgSharp.RegularAnalysis.Text _ => Payload.Source }; - public RegexAstPayload Payload { get; private protected set; } - - public ISyntaxPayload Source => Payload; - - internal TPayload PayloadAs() where TPayload : RegexAstPayload => - Payload as TPayload ?? throw new InvalidOperationException( - $"Node {Label} does not carry {typeof(TPayload).Name}."); + public string SourceCode => Source.Source; public void AppendNode( RegexAstNode nextToken @@ -106,7 +103,7 @@ namespace StgSharp.RegularAnalysis.Text right &= RegexElementLabel.OPERATOR; int v_left = (int)left, v_right = (int)right; if (v_left == 0 || v_right == 0) { - return (false,0); + return (false, 0); } return (true,v_left - v_right); } @@ -148,6 +145,14 @@ namespace StgSharp.RegularAnalysis.Text return new EmptyRegexAstNode(); } + internal TPayload PayloadAs() where TPayload : RegexAstPayload + { + return + Payload as TPayload ?? + throw new InvalidOperationException( + $"Node {Label} does not carry {typeof(TPayload).Name}."); + } + internal void RemoveAndAsChild( bool useLeft ) diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.Diagnostic.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.Diagnostic.cs index 77f3ddbf..1e6905fc 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.Diagnostic.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.Diagnostic.cs @@ -24,9 +24,9 @@ namespace StgSharp.RegularAnalysis.Text category:"NGRA.TextRegex", isEnabledByDefault:true); - private static readonly DiagnosticDescriptor CompileFailDiag = new(id:"NGRA005", - title:"Unexpected exception when compiling", - messageFormat:"Unexpected exception handles when compiling expression \"{0}\":{1}", + private static readonly DiagnosticDescriptor CompileFailDiag = new(id:"NGRA006", + title:"Regular expression compilation failed", + messageFormat:"Failed to compile expression \"{0}\": {1}", defaultSeverity:DiagnosticSeverity.Error, category:"NGRA.TextRegex", isEnabledByDefault:true); @@ -39,8 +39,8 @@ namespace StgSharp.RegularAnalysis.Text isEnabledByDefault:true); private static readonly DiagnosticDescriptor IncorrectReturnTypeDiag = new(id:"NGRA003", - title:"Text regex binding must be partial", - messageFormat:"Method {0} must be partial to receive generated code", + title:$"Returning type must be {typeof(TextRegex).FullName}", + messageFormat:$"Method {{0}} must return {typeof(TextRegex).FullName}", defaultSeverity:DiagnosticSeverity.Error, category:"NGRA.TextRegex", isEnabledByDefault:true); @@ -53,8 +53,8 @@ namespace StgSharp.RegularAnalysis.Text isEnabledByDefault:true); private static readonly DiagnosticDescriptor NotPartialDiag = new(id:"NGRA001", - title:$"Returning type must be {typeof(TextRegex).FullName}", - messageFormat:$"Method {{0}} must return {typeof(TextRegex).FullName}", + title:"Text regex binding must be partial", + messageFormat:"Method {0} must be partial to receive generated code", defaultSeverity:DiagnosticSeverity.Error, category:"NGRA.TextRegex", isEnabledByDefault:true); diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs index c035b73f..2c8eb44c 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs @@ -24,6 +24,7 @@ namespace StgSharp.RegularAnalysis.Text RegexAstNode ast, in SequenceEmitter sc, in List> func_list, + in SourceGenContext context, bool is_find = false ) @@ -38,16 +39,16 @@ namespace StgSharp.RegularAnalysis.Text switch (op) { case RegexElementLabel.CONCAT: - GenerateConcat(root, sc, func_list); + GenerateConcat(root, sc, func_list, context); break; case RegexElementLabel.COUNT: - GenerateCount(root, sc, func_list); + GenerateCount(root, sc, func_list, context); break; case RegexElementLabel.GROUP_BEGIN: - GenerateGroup(root, sc, func_list); + GenerateGroup(root, sc, func_list, context); break; case RegexElementLabel.ALT: - GenerateAlt(root, sc, func_list); + GenerateAlt(root, sc, func_list, context); break; } } else @@ -67,6 +68,20 @@ namespace StgSharp.RegularAnalysis.Text #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( RegexAstNode node ) @@ -99,32 +114,48 @@ namespace StgSharp.RegularAnalysis.Text private static void GenerateAlt( RegexAstNode node, [NotNull] in SequenceEmitter sc, - in List> func_list + in List> func_list, + in SourceGenContext context ) { - RegexAstNode left = node.Left; - RegexAstNode right = node.Right; - - RegexAstNode current = left; - + RegexAstNode current = node.Right; + string exit_label = context.RequestExitAlt(); while (RegexAstNode.IsNullOrEmpty(current)) { - GenerateMethodSource(current, sc, func_list); - current = current.Next; - } - current = right; - - while (RegexAstNode.IsNullOrEmpty(current)) - { - GenerateMethodSource(current, sc, func_list); - current = current.Next; + RegexElementLabel op = current.Label & RegexElementLabel.SEQUENCE; + if (op != 0) + { + 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 split = current.PayloadAs() + .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; + } } + _ = sc.AppendLine($@"{exit_label}: {{ }}"); } private static void GenerateGroup( RegexAstNode node, [NotNull] in SequenceEmitter sc, - in List> func_list + in List> func_list, + in SourceGenContext context ) { RegexAstNode left = node.Left; @@ -134,10 +165,10 @@ namespace StgSharp.RegularAnalysis.Text if (left is not null) { - GenerateMethodSource(left, sc, func_list); + GenerateMethodSource(left, sc, func_list, context); } else if (right is not null) { - GenerateMethodSource(right, sc, func_list); + GenerateMethodSource(right, sc, func_list, context); } string group_name = config.Name; @@ -147,7 +178,8 @@ namespace StgSharp.RegularAnalysis.Text private static void GenerateConcat( RegexAstNode node, [NotNull] in SequenceEmitter sc, - in List> func_list + in List> func_list, + in SourceGenContext context ) { RegexAstNode left = node.Left; @@ -162,40 +194,41 @@ namespace StgSharp.RegularAnalysis.Text right is not null) { // TODO use find - GenerateMethodSource(right, sc, func_list, true); + GenerateMethodSource(right, sc, func_list, context, true); } else if (IsNamedFind(node)) { if (right is not null) { // TODO named find - GenerateMethodSource(right, sc, func_list, true); + GenerateMethodSource(right, sc, func_list, context, true); } else { // TODO pack rest of string to group } } else { - GenerateMethodSource(left, sc, func_list); + GenerateMethodSource(left, sc, func_list, context); if (right is not null) { - GenerateMethodSource(right, sc, func_list); + GenerateMethodSource(right, sc, func_list, context); } } } if (right is not null) { - GenerateMethodSource(right, sc, func_list); + GenerateMethodSource(right, sc, func_list, context); } } private static void GenerateCount( RegexAstNode node, [NotNull] in SequenceEmitter sc, - in List> func_list + in List> func_list, + in SourceGenContext context ) { RegexAstNode value = node.Left ?? node.Right; if ((value.Label & RegexElementLabel.SEQUENCE) == 0) { - GenerateMethodSource(value, sc, func_list); + GenerateMethodSource(value, sc, func_list, context); } else { RegexCountPayload payload = value.PayloadAs(); diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs index 7525ea30..093838fa 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs @@ -6,9 +6,11 @@ // ----------------------------------------------------------------------------- using Microsoft.CodeAnalysis.Text; +using StgSharp.PipeLine; using StgSharp.RegularAnalysis.Abstraction; using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Linq; using System.Text; 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 /// always declare its own without colliding with an enclosing one. /// - private sealed class SourceGenContext + internal sealed class SourceGenContext { - /// - /// Name of the match method parameter holding the input sequence. - /// - public const string _inputSpan = "text"; + public const string __input_span = "input"; - /// - /// Result of the instruction emitted immediately before the current one. - /// - public const string _stepResult = "__ok"; + public const string _remain_span = "__remain"; - private readonly Dictionary _groupIndex = []; - private readonly HashSet _observedLines = []; + private int _exit_alt_count; - private int _anonymousGroup; + public string RemainSpan { get; } = _remain_span; - private int _count_idx = 0; - private int _scratchIndex; - private readonly SortedSet _lineSlots = []; - private readonly SortedSet _regionSlots = []; + public string FirstOfRemain { get; } = $@"{_remain_span}[0]"; - public string RemainingSpan { get; } = "__remaining_span"; + public string InputString { get; } = __input_span; - public string InputSpan { get; } = _inputSpan; - - 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() + public SequenceEmitter DeclareGlobalFields() { - return [ - "do", - "{" - ]; + SequenceEmitter sc = new(); + + return sc.AppendLine($@"{ROS_char} {RemainSpan};"); } - public string[] CloseRegion() + public string RequestExitAlt() { - return [ - "} while(true);" - ]; - } - - /// - /// Emits declarations for every slot requested during body generation. - /// - /// - /// Must be called after the method body has been generated, but the produced lines - /// have to be placed before it. - /// - public SequenceEmitter EmitDeclarationsTo( - SequenceEmitter 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"; - } - - /// - /// Indicates whether the instruction at line has to persist - /// its result into a line slot. - /// - public bool IsLineObserved( - int line - ) - { - return _observedLines.Contains(line); - } - - /// - /// Name of the slot holding the match result of the instruction at line . Only lines actually referenced by an prefix get a slot. - /// - 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}..];"; - } - - /// - /// Records that some prefix observes the result of the instruction at line . - /// - public void MarkLineObserved( - int line - ) - { - _ = _observedLines.Add(line); - } - - /// - /// Allocates a fresh block scoped scratch variable name. - /// - /// - /// Short role marker, for example n for a counter or ch for a - /// character. - /// - public string NewScratch( - string tag - ) - { - return $"__{tag}{_scratchIndex++}"; - } - - /// - /// Name of the slot holding the cursor value captured when region was entered. - /// - public string RegionAnchor( - int region - ) - { - _ = _regionSlots.Add(region); - return $"__r{region}_begin"; - } - - /// - /// Name of the slot holding the match result of region . - /// - 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; + string code = $@"_exit_alt_{_exit_alt_count}"; + _exit_alt_count++; + return code; } } diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenFile.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenFile.cs index 9d3eb518..ac6de4c9 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenFile.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenFile.cs @@ -43,6 +43,8 @@ namespace StgSharp.RegularAnalysis.Text SequenceEmitter ce = new(); SourceRecord gen_source = binding.Source; TypeSource type = binding.Source.ContainingType; + SequenceEmitter method_body = binding.GeneratedSource; + SourceGenContext regex_gen_context = binding.SourceContext; _ = file_name_sb.Append(CultureInfo.InvariantCulture, $"{gen_source.NamespaceName}."); _ = ce.AppendLine($@"// ") @@ -75,15 +77,12 @@ namespace StgSharp.RegularAnalysis.Text } while (type is not null); 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("{") - /**/ //TODO return single instance of the TextRegex object + .Append(method_body) .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("}"); } @@ -104,10 +103,6 @@ namespace StgSharp.RegularAnalysis.Text _ = file_name_sb.Append(CultureInfo.InvariantCulture, $@"{binding.Source.MethodName}.g.cs"); string file_name = file_name_sb.ToString(); context.AddSource(file_name, code_source.ToString()); - - foreach (Diagnostic diag in binding.RegexDiagnostic) { - context.ReportDiagnostic(diag); - } } private static string ToKeyword( diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.Source.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.Source.cs index 83154cb0..6b5329d3 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.Source.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.Source.cs @@ -76,12 +76,17 @@ namespace StgSharp.RegularAnalysis.Text private TextRegexBinding( List diagnostics, SequenceEmitter generatedSource, - SourceRecord source + SourceRecord source, + SourceGenContext sourceContext, + TextRegexSource? analyzedSource ) { _diagnostics = diagnostics ?? []; GeneratedSource = generatedSource ?? new(); Source = source; + AnalyzedSource = analyzedSource; + IsValid = !_diagnostics.Exists(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error); + SourceContext = sourceContext; } public TextRegexBinding() @@ -89,11 +94,15 @@ namespace StgSharp.RegularAnalysis.Text _diagnostics = []; } + public TextRegexSource? AnalyzedSource { get; set; } + public bool IsValid { get; private set; } = true; public IReadOnlyList RegexDiagnostic => _diagnostics; - public SequenceEmitter GeneratedSource { get; set; } + public SequenceEmitter GeneratedSource { get; set; } = new(); + + public SourceGenContext SourceContext { get; set; } = null!; public SourceRecord Source { get; set; } @@ -121,7 +130,8 @@ namespace StgSharp.RegularAnalysis.Text SequenceEmitter code = GeneratedSource.Clone(); List diag = new(_diagnostics.Count); diag.AddRange(this._diagnostics); - TextRegexBinding binding = new(diag, code, Source with { }); + TextRegexBinding binding = new(diag, code, Source with { }, SourceContext, + AnalyzedSource); return binding; } diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.cs index 526ad1fa..24c4e820 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.cs @@ -9,14 +9,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using StgSharp.RegularAnalysis.Abstraction; -using System; using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Security.AccessControl; -using System.Text; using System.Threading; -using System.Threading.Tasks; namespace StgSharp.RegularAnalysis.Text { @@ -30,21 +24,54 @@ namespace StgSharp.RegularAnalysis.Text IncrementalGeneratorInitializationContext context ) { - IncrementalValuesProvider provider = context.SyntaxProvider - .ForAttributeWithMetadataName( + IncrementalValuesProvider candidates = + + // + context.SyntaxProvider + .ForAttributeWithMetadataName( TextRegexTypeName, static( _, _ ) => true, BindTextRegexCandidate); - IncrementalValuesProvider match = RegisterDiagnosticBypass(context, provider); + IncrementalValuesProvider validCandidates = + RegisterDiagnosticBypass(context, candidates); - IncrementalValuesProvider compile = match.Select(CompileRegexMethodSource); + IncrementalValuesProvider analyzed = + validCandidates.Select(AnalyzeRegexMethod); - IncrementalValuesProvider compile_success = RegisterDiagnosticBypass(context, compile); + IncrementalValuesProvider validAnalyzed = + RegisterDiagnosticBypass(context, analyzed); - context.RegisterSourceOutput(compile_success, GenerateRegexSourceFile); + IncrementalValuesProvider generated = + validAnalyzed.Select(CompileRegexMethodSource); + + IncrementalValuesProvider 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( @@ -71,7 +98,7 @@ namespace StgSharp.RegularAnalysis.Text TextRegexBinding bind = new(); 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) { bind.AddDiagnostic(NotParameterLessDiag, method_decl.GetLocation(), method_decl.GetText()); @@ -97,9 +124,9 @@ namespace StgSharp.RegularAnalysis.Text while (contain is not null) { 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(), method_symbol.Name, contain.TypeKind); } @@ -133,23 +160,28 @@ namespace StgSharp.RegularAnalysis.Text } private static TextRegexBinding CompileRegexMethodSource( - TextRegexBinding source, - CancellationToken ct + TextRegexBinding binding, + CancellationToken cancellationToken ) { - ct.ThrowIfCancellationRequested(); - TextRegexBinding result = source.Clone(); - string pattern = result.Source.Pattern; - TextRegexSource analyze_result = RegexAnalyzer.Analyze(pattern); - if (analyze_result.AnalyseException is not null) - { - result.AddDiagnostic(CompileFailDiag, null, analyze_result.AnalyseException.Message); - } else - { - SequenceEmitter method_source = /**/ new()/*GenerateSource(analyze_result)/**/; - result.GeneratedSource = method_source; + cancellationToken.ThrowIfCancellationRequested(); + + TextRegexBinding result = binding.Clone(); + RegexAstNode root = result.AnalyzedSource!.Ast.Root; + SourceGenContext generationContext = new(); + SequenceEmitter methodSource = new(); + List> localFunctions = []; + + GenerateMethodSource(root, methodSource, localFunctions, generationContext); + foreach (SequenceEmitter localFunction in localFunctions) { + _ = methodSource.AppendLine() + .AppendLine() + .AppendLine("// local method for matching") + .Append(localFunction); } + result.SourceContext = generationContext; + result.GeneratedSource = methodSource; return result; } @@ -158,19 +190,18 @@ namespace StgSharp.RegularAnalysis.Text IncrementalValuesProvider bindings ) { - IncrementalValuesProvider error_path = bindings.Where(static binding => !binding.IsValid) - .SelectMany(static( - binding, - _ - ) => binding.RegexDiagnostic); - IncrementalValuesProvider correct_path = bindings.Where(static binding => binding.IsValid); + IncrementalValuesProvider errorPath = + bindings.Where(static binding => !binding.IsValid) + .SelectMany(static( + binding, + _ + ) => binding.RegexDiagnostic); + context.RegisterSourceOutput(errorPath, static( + productionContext, + diagnostic + ) => productionContext.ReportDiagnostic(diagnostic)); - context.RegisterSourceOutput(error_path, ( - c, - diag - ) => c.ReportDiagnostic(diag)); - - return correct_path; + return bindings.Where(static binding => binding.IsValid); } }