diff --git a/SS_error.log b/SS_error.log index 9991f49c..8d151b4f 100644 --- a/SS_error.log +++ b/SS_error.log @@ -3,3 +3,8 @@ [2026-05-13 11:33:26.040] [Info] Program StgSharpDebug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null on GF-CHEN-LIU Started. + + + + +[2026-07-06 16:49:29.987] [Info] Program StgSharpDebug, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null on GF-CHEN-LIU Started. diff --git a/StgSharp.RegularAnalysis/Text/RegexAnalyzerFrontEnd.PrefixEmit.cs b/StgSharp.RegularAnalysis/Text/RegexAnalyzerFrontEnd.PrefixEmit.cs new file mode 100644 index 00000000..b8675b86 --- /dev/null +++ b/StgSharp.RegularAnalysis/Text/RegexAnalyzerFrontEnd.PrefixEmit.cs @@ -0,0 +1,449 @@ +//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +// file="RegexAnalyzerFrontEnd.PrefixEmit" +// Project: StgSharp +// AuthorGroup: Nitload +// Copyright (c) Nitload. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace StgSharp.RegularAnalysis.Text +{ + public static partial class RegexAnalyzerFrontEnd + { + + internal static List EmitPrefix( + List irList + ) + { + List origin = irList; + List result = new(origin.Count); + Span origin_span = CollectionsMarshal.AsSpan(origin); + + int region_name = 1; + EmitPrefix(origin_span, result, 0, 0); + FixLineObservation(result); + + return result; + + void EmitPrefix( + ReadOnlySpan region, + List result, + int currentRegion, + int lineBase + ) + { + ReadOnlySpan emit_window; + int index = 0; + while (index < region.Length) + { + emit_window = region[index..]; + + int emitResult; + if ((emitResult = TryEmitGroupFindPack(emit_window, result, currentRegion, lineBase)) > 0) + { + index += emitResult; + } else if ((emitResult = TryEmitTryGroupTail(emit_window, result, currentRegion, lineBase)) > 0) + { + index += emitResult; + } else if ((emitResult = TryEmitAltChain(emit_window, result, currentRegion, lineBase)) > 0) + { + index += emitResult; + } else if ((emitResult = TryEmitAltSuccessTail(emit_window, result, currentRegion, lineBase)) > 0) + { + index += emitResult; + } else if ((emitResult = TryEmitAltFailTail(emit_window, result, currentRegion, lineBase)) > 0) + { + index += emitResult; + } else if ((emitResult = TryEmitTryIR(emit_window, result, currentRegion, lineBase)) > 0) + { + index += emitResult; + } else + { + result.Add(CloneIR(emit_window[0], currentRegion)); + index++; + } + } + } + + int TryEmitTryGroupTail( + ReadOnlySpan origin, + List result, + int currentRegion, + int lineBase + ) + { + if (origin.Length < 5 || origin[0] is not RegexTryIR _try) { + return -1; + } + + int tailOffset = _try.CommandCount + 1; + if (origin.Length < tailOffset + 3 || + origin[tailOffset] is not RegexConditionIR { SuccessCase: 2, FailCase: 1 } condition || + origin[tailOffset + 1] is not RegexPopIR { IsSuccess: false } pop || + origin[tailOffset + 2] is not RegexPackIR { Condition: true } pack) { + return -1; + } + + int tryOutputIndex = result.Count; + int emitCount = TryEmitTryIR( + origin, + result, + currentRegion, + lineBase, + foldSingleInstructionTry: false); + if (emitCount <= 0 || result[tryOutputIndex] is not RegexTryIR emittedTry) { + return -1; + } + + _ = condition; + int regionId = emittedTry.Prefix.WorkingRegion; + + RegexIR prefixedPop = CloneIR(pop, regionId); + ApplyPrefix( + prefixedPop, + regionId, + RegexIRPrefixMask.Prev_Fail | RegexIRPrefixMask.Observe_Region, + regionId); + + RegexIR prefixedPack = CloneIR(pack, regionId); + ApplyPrefix( + prefixedPack, + regionId, + RegexIRPrefixMask.Prev_Success | RegexIRPrefixMask.Observe_Region, + regionId); + + result.Add(prefixedPop); + result.Add(prefixedPack); + return emitCount + 3; + } + + int TryEmitAltChain( + ReadOnlySpan origin, + List result, + int currentRegion, + int lineBase + ) + { + if (origin.Length < 3 || + origin[0] is not RegexConditionIR { SuccessCase: 1, FailCase: 2 } || + origin[1] is not RegexPopIR { IsSuccess: true } || + origin[2] is not RegexTryIR) { + return -1; + } + + int observeRegion = currentRegion; + RegexIRPrefixMask observeMask = RegexIRPrefixMask.Observe_Region; + int observeSource = currentRegion; + if (result.Count > 0) + { + RegexIR previous = result[^1]; + observeRegion = GetObservedRegion(previous, currentRegion); + GetObservation(previous, result, lineBase, currentRegion, out observeMask, out observeSource); + ApplyPrefix(previous, observeRegion, RegexIRPrefixMask.Pop_Success, observeRegion); + } + + RegexIRPrefixMask mask = RegexIRPrefixMask.Prev_Fail | observeMask; + int emit_count = TryEmitTryIR(origin[2..], result, currentRegion, lineBase, mask, observeSource, observeRegion); + return emit_count < 0 ? -1 : emit_count + 2; + } + + int TryEmitAltSuccessTail( + ReadOnlySpan origin, + List result, + int currentRegion, + int lineBase + ) + { + if (origin.Length < 2 || + origin[0] is not RegexConditionIR { SuccessCase: 1, FailCase: 2 } || + origin[1] is not RegexPopIR { IsSuccess: true } || + result.Count == 0) { + return -1; + } + + RegexIR previous = result[^1]; + int observeRegion = GetObservedRegion(previous, currentRegion); + ApplyPrefix(previous, observeRegion, RegexIRPrefixMask.Pop_Success, observeRegion); + return 2; + } + + int TryEmitAltFailTail( + ReadOnlySpan origin, + List result, + int currentRegion, + int lineBase + ) + { + if (origin.Length < 1 || + origin[0] is not RegexPopIR { IsSuccess: false } pop || + result.Count == 0) { + return -1; + } + + RegexIR previous = result[^1]; + if ((previous.Prefix.Mask & RegexIRPrefixMask.Pop_Success) == 0) { + return -1; + } + + GetObservation( + previous, + result, + lineBase, + currentRegion, + out RegexIRPrefixMask observeMask, + out int observeSource); + RegexIR prefixedPop = CloneIR(pop, currentRegion); + ApplyPrefix( + prefixedPop, + currentRegion, + RegexIRPrefixMask.Prev_Fail | observeMask, + observeSource); + result.Add(prefixedPop); + return 1; + } + + // convert TRY into region + int TryEmitTryIR( + ReadOnlySpan origin, + List result, + int currentRegion, + int lineBase, + RegexIRPrefixMask mask = 0, + int observeSource = 0, + int reuseRegion = 0, + bool foldSingleInstructionTry = true + ) + { + if (origin.Length == 0 || origin[0] is not RegexTryIR _try) { + return -1; + } + + int commandCount = _try.CommandCount; + if (origin.Length < commandCount + 1) { + return -1; + } + + int regionId = reuseRegion == 0 ? region_name : reuseRegion; + if (reuseRegion == 0) { + region_name++; + } + + if (foldSingleInstructionTry && + commandCount == 1 && + IsFoldableSingleTryInstruction(origin[1])) { + RegexIR folded = CloneIR(origin[1], regionId); + int foldedObserveSource = + (mask & RegexIRPrefixMask.ObserveMethod) != 0 ? + observeSource : + _try.Prefix.ObserveSource; + folded.Prefix = new RegexIRPrefix( + regionId, + folded.Prefix.Mask | _try.Prefix.Mask | mask, + foldedObserveSource); + result.Add(folded); + return commandCount + 1; + } + + List sub_ir = new(commandCount); + ReadOnlySpan try_region = origin.Slice(1, commandCount); + EmitPrefix(try_region, sub_ir, regionId, lineBase + result.Count + 1); + + RegexTryIR newTry = new(sub_ir.Count) + { + IsFullyOptimized = _try.IsFullyOptimized, + Prefix = new RegexIRPrefix(regionId, _try.Prefix.Mask | mask, observeSource) + }; + result.Add(newTry); + result.AddRange(sub_ir); + return commandCount + 1; + } + + int TryEmitGroupFindPack( + ReadOnlySpan origin, + List result, + int currentRegion, + int lineBase + ) + { + if (origin.Length < 5 || + origin[0] is not RegexTryIR { CommandCount: 1 } _try || + origin[1] is not RegexFindComplexIR find || + origin[2] is not RegexConditionIR { SuccessCase: 2, FailCase: 1 } || + origin[3] is not RegexPopIR { IsSuccess: false } || + origin[4] is not RegexPackIR { Condition: true } pack) { + return -1; + } + + int regionId = region_name; + region_name++; + + RegexIRPrefixMask findMask = find.Prefix.Mask | _try.Prefix.Mask; + int findObserveSource = + (find.Prefix.Mask & RegexIRPrefixMask.ObserveMethod) != 0 ? + find.Prefix.ObserveSource : + _try.Prefix.ObserveSource; + + RegexFindComplexIR newFind = new(find.IsNearest, find.CommandCount) + { + IsFullyOptimized = find.IsFullyOptimized, + Prefix = new RegexIRPrefix(regionId, findMask, findObserveSource) + }; + RegexPackIR newPack = new(pack.Condition, pack.GroupName) + { + Prefix = new RegexIRPrefix( + regionId, + pack.Prefix.Mask | RegexIRPrefixMask.Prev_Success | RegexIRPrefixMask.Observe_Line, + -1) + }; + + result.Add(newFind); + result.Add(newPack); + return 5; + } + + static int GetObservedRegion( + RegexIR ir, + int currentRegion + ) + { + return ir.Prefix.WorkingRegion == 0 ? currentRegion : ir.Prefix.WorkingRegion; + } + + static void GetObservation( + RegexIR ir, + List result, + int lineBase, + int currentRegion, + out RegexIRPrefixMask observeMask, + out int observeSource + ) + { + if (IsLineObservable(ir)) + { + observeMask = RegexIRPrefixMask.Observe_Line; + observeSource = -1; + } else + { + observeMask = RegexIRPrefixMask.Observe_Region; + observeSource = GetObservedRegion(ir, currentRegion); + } + } + + static void FixLineObservation( + List result + ) + { + for (int i = 0; i < result.Count; i++) + { + RegexIRPrefix prefix = result[i].Prefix; + if ((prefix.Mask & RegexIRPrefixMask.Observe_Line) == 0) { + continue; + } + + result[i].Prefix = new RegexIRPrefix( + prefix.WorkingRegion, + prefix.Mask, + i - 1); + } + } + + static bool IsFoldableSingleTryInstruction( + RegexIR ir + ) + { + return IsLineObservable(ir) && + (ir.Prefix.Mask & RegexIRPrefixMask.ExeCondition) == 0 && + (ir.Prefix.Mask & RegexIRPrefixMask.ObserveMethod) == 0; + } + + static bool IsLineObservable( + RegexIR ir + ) + { + return ir is RegexMatchSeqIR or + RegexMatchSetIR or + RegexCountSeqIR or + RegexCountSetIR or + RegexFindSeqIR or + RegexFindComplexIR; + } + + static void ApplyPrefix( + RegexIR ir, + int workingRegion, + RegexIRPrefixMask mask, + int observeSource + ) + { + ir.Prefix = new RegexIRPrefix( + workingRegion, + ir.Prefix.Mask | mask, + observeSource); + } + + static RegexIR CloneIR( + RegexIR ir, + int currentRegion + ) + { + RegexIR clone = ir switch + { + RegexFindComplexIR find => new RegexFindComplexIR(find.IsNearest, find.CommandCount) + { + IsFullyOptimized = find.IsFullyOptimized + }, + RegexFindSeqIR find => new RegexFindSeqIR(find.Pattern, find.IsFirst), + RegexMatchSeqIR match => new RegexMatchSeqIR(match.Pattern), + RegexMatchSetIR match => new RegexMatchSetIR(match.Charset), + RegexConditionIR condition => new RegexConditionIR(condition.SuccessCase, condition.FailCase), + RegexPackIR pack => new RegexPackIR(pack.Condition, pack.GroupName), + RegexCountComplexIR count => new RegexCountComplexIR(count.Min, count.Max, count.IsGreedy, count.CommandCount) + { + IsFullyOptimized = count.IsFullyOptimized + }, + RegexCountSeqIR count => new RegexCountSeqIR(count.Min, count.Max, count.IsGreedy, count.Sequence), + RegexCountSetIR count => new RegexCountSetIR(count.Min, count.Max, count.IsGreedy, count.CharSet), + RegexTryIR tryIR => new RegexTryIR(tryIR.CommandCount) + { + IsFullyOptimized = tryIR.IsFullyOptimized + }, + RegexPopIR pop => new RegexPopIR(pop.IsSuccess), + _ => throw new NotSupportedException($"Unsupported IR type: {ir.GetType().Name}") + }; + + clone.Prefix = new RegexIRPrefix( + ir.Prefix.WorkingRegion == 0 ? currentRegion : ir.Prefix.WorkingRegion, + ir.Prefix.Mask, + ir.Prefix.ObserveSource); + return clone; + } + } + + } +} diff --git a/StgSharp.RegularAnalysis/Text/RegexAnalyzerFrontEnd.cs b/StgSharp.RegularAnalysis/Text/RegexAnalyzerFrontEnd.cs index 92d26a0d..23b3ae77 100644 --- a/StgSharp.RegularAnalysis/Text/RegexAnalyzerFrontEnd.cs +++ b/StgSharp.RegularAnalysis/Text/RegexAnalyzerFrontEnd.cs @@ -1,6 +1,6 @@ //----------------------------------------------------------------------- // ----------------------------------------------------------------------- -// file="RegexAnalyzer" +// file="RegexAnalyzerFrontEnd" // Project: StgSharp // AuthorGroup: Nitload // Copyright (c) Nitload. All rights reserved. @@ -29,6 +29,7 @@ using StgSharp.RegularAnalysis.Abstraction; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; @@ -76,18 +77,35 @@ namespace StgSharp.RegularAnalysis.Text { // RegexAnalyzer interpreter = new(); // interpreter._source = regex; + AbstractSyntaxTree tree; try { - AbstractSyntaxTree tree = GenerateAST(regex); + tree = GenerateAST(regex); OptimizeTree(tree); - List base_ir = GenerateIR(tree); - ScanAndOptimizeIR(base_ir, out List opt_ir); - return new TextRegexSource(regex, tree, base_ir, opt_ir); } catch (Exception ex) { return new TextRegexSource(regex, null, null, null, ex); } + List base_ir; + try + { + base_ir = GenerateIR(tree); + } + catch (Exception ex) + { + return new TextRegexSource(regex, tree, null, null, ex); + } + try + { + ScanAndOptimizeIR(base_ir, out List opt_ir); + List prefix_ir = EmitPrefix(opt_ir); + return new TextRegexSource(regex, tree, base_ir, prefix_ir); + } + catch (Exception ex) + { + return new TextRegexSource(regex, tree, base_ir, null, ex); + } } } diff --git a/StgSharp.RegularAnalysis/Text/RegexIR.Prefix.cs b/StgSharp.RegularAnalysis/Text/RegexIR.Prefix.cs new file mode 100644 index 00000000..f9952d31 --- /dev/null +++ b/StgSharp.RegularAnalysis/Text/RegexIR.Prefix.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +// file="RegexIR.Prefix" +// Project: StgSharp +// AuthorGroup: Nitload +// Copyright (c) Nitload. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StgSharp.RegularAnalysis.Text +{ + internal enum RegexIRPrefixMask + { + + Prev_Success = 1 << 0, + Prev_Fail = 1 << 1, + Always = 1 << 2, + ExeCondition = Prev_Fail | Prev_Success | Always, + Pop_Fail = 1 << 3, + Pop_Success = 1 << 4, + PopCondition = Pop_Success | Pop_Fail, + Observe_Line = 1 << 5, + Observe_Region = 1 << 6, + ObserveMethod = Observe_Line | Observe_Region + + } + + internal record struct RegexIRPrefix( + int WorkingRegion, + RegexIRPrefixMask Mask, + int ObserveSource + ) + { + + public override string ToString() + { + return $"{WorkingRegion:D4}, {DisplayMask(Mask , RegexIRPrefixMask.ExeCondition):D1}, {DisplayMask(Mask , RegexIRPrefixMask.PopCondition):D1}, {DisplayMask(Mask ,RegexIRPrefixMask.ObserveMethod):D1}, {ObserveSource:D4}"; + } + + private int DisplayMask( + RegexIRPrefixMask mask, + RegexIRPrefixMask range + ) + { + return range switch + { + RegexIRPrefixMask.ExeCondition => (int)(mask & RegexIRPrefixMask.ExeCondition) >> 0, + RegexIRPrefixMask.PopCondition => (int)(mask & RegexIRPrefixMask.PopCondition) >> 3, + RegexIRPrefixMask.ObserveMethod => (int)(mask & RegexIRPrefixMask.ObserveMethod) >> 5, + _ => throw new ArgumentOutOfRangeException(nameof(range), range, null) + }; + } + + } +} diff --git a/StgSharp.RegularAnalysis/Text/RegexIR.cs b/StgSharp.RegularAnalysis/Text/RegexIR.cs index 3cf6e270..344a8c06 100644 --- a/StgSharp.RegularAnalysis/Text/RegexIR.cs +++ b/StgSharp.RegularAnalysis/Text/RegexIR.cs @@ -25,6 +25,7 @@ // // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- +using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; using System.Linq; @@ -55,19 +56,6 @@ namespace StgSharp.RegularAnalysis.Text } - internal enum RegexIRPrefix - { - - Prev_Success = 1 << 0, - Prev_Fail = 1 << 1, - Always = 1 << 2, - ExeCondition = Prev_Fail | Prev_Success | Always, - Pop_Fail = 1 << 3, - Pop_Success = 1 << 4, - Pop_Condition = Pop_Success | Pop_Fail, - - } - internal interface IRegexComplex { @@ -92,7 +80,10 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexFindComplexIR(bool nearest, int count) : RegexIR, IRegexComplex + internal sealed class RegexFindComplexIR( + bool nearest, + int count + ) : RegexIR, IRegexComplex { public bool IsFullyOptimized { get; set; } @@ -103,9 +94,11 @@ namespace StgSharp.RegularAnalysis.Text internal override RegexIRCommand Command => RegexIRCommand.Find_Complex; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t FIND_COMPLEX {(IsNearest ? "NEAREST" : "FURTHEST")} COUNT: {CommandCount}"; + return $"FIND_COMPLEX {(IsNearest ? "NEAREST" : "FURTHEST")} COUNT: {CommandCount}"; } } @@ -113,13 +106,25 @@ namespace StgSharp.RegularAnalysis.Text internal abstract class RegexIR { + public RegexIRPrefix Prefix { get; set; } + internal abstract RegexIRCommand Command { get; } - internal abstract string AsCommand(int lineNum); + internal string AsCommand( + int lineNum + ) + { + return $"[{lineNum}]\t{Prefix}\t{AsCommand()}"; + } + + protected abstract string AsCommand(); } - internal sealed class RegexFindSeqIR(string pattern, bool isFirst) : RegexIR + internal sealed class RegexFindSeqIR( + string pattern, + bool isFirst + ) : RegexIR { public bool IsFirst { get; } = isFirst; @@ -128,9 +133,10 @@ namespace StgSharp.RegularAnalysis.Text internal override RegexIRCommand Command => RegexIRCommand.Find_Seq; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + ) { - return $"[{lineNum}]\t FIND_SEQ \"{(string.IsNullOrEmpty(Pattern) ? "" : Pattern)}\""; + return $"FIND_SEQ \"{(string.IsNullOrEmpty(Pattern) ? "" : Pattern)}\""; } } @@ -138,35 +144,46 @@ namespace StgSharp.RegularAnalysis.Text /// /// Searches for a certain string pattern or charset from current position. /// - internal sealed class RegexMatchSeqIR(string pattern) : RegexIR + internal sealed class RegexMatchSeqIR( + string pattern + ) : RegexIR { public string Pattern { get; } = pattern; internal override RegexIRCommand Command => RegexIRCommand.Match_Seq; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t MATCH_SEQ \"{(string.IsNullOrEmpty(Pattern) ? "" : Pattern)}\""; + return $"MATCH_SEQ \"{(string.IsNullOrEmpty(Pattern) ? "" : Pattern)}\""; } } - internal sealed class RegexMatchSetIR(string charset) : RegexIR + internal sealed class RegexMatchSetIR( + string charset + ) : RegexIR { public string Charset { get; } = charset; internal override RegexIRCommand Command => RegexIRCommand.Match_Set; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t MATCH_SET \"{Charset}\""; + return $"MATCH_SET \"{Charset}\""; } } - internal sealed class RegexConditionIR(int success, int fail) : RegexIR + internal sealed class RegexConditionIR( + int success, + int fail + ) : RegexIR { public int SuccessCase { get; } = success; @@ -175,14 +192,19 @@ namespace StgSharp.RegularAnalysis.Text internal override RegexIRCommand Command => RegexIRCommand.Condition; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t CONDITION SUCCESS: {SuccessCase} FAIL: {FailCase}"; + return $"CONDITION SUCCESS: {SuccessCase} FAIL: {FailCase}"; } } - internal sealed class RegexPackIR(bool condition, string name = "") : RegexIR + internal sealed class RegexPackIR( + bool condition, + string name = "" + ) : RegexIR { public bool Condition { get; } = condition; @@ -191,14 +213,21 @@ namespace StgSharp.RegularAnalysis.Text internal override RegexIRCommand Command => RegexIRCommand.Pack; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t PACK {(Condition ? "SUCCESS" : "FAIL")} GROUP: \"{GroupName}\""; + return $"PACK {(Condition ? "SUCCESS" : "FAIL")} GROUP: \"{GroupName}\""; } } - internal sealed class RegexCountComplexIR(int min, int max, bool isGreedy, int operation) : RegexIR, IRegexCount, IRegexComplex + internal sealed class RegexCountComplexIR( + int min, + int max, + bool isGreedy, + int operation + ) : RegexIR, IRegexCount, IRegexComplex { public bool IsGreedy { get; } = isGreedy; @@ -217,14 +246,21 @@ namespace StgSharp.RegularAnalysis.Text internal override RegexIRCommand Command { get; } = RegexIRCommand.Count_Complex; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t COUNT {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} OPERATION: {CommandCount}"; + return $"COUNT {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} OPERATION: {CommandCount}"; } } - internal sealed class RegexCountSeqIR(int min, int max, bool isGreedy, string seq) : RegexIR, IRegexCount + internal sealed class RegexCountSeqIR( + int min, + int max, + bool isGreedy, + string seq + ) : RegexIR, IRegexCount { public bool IsGreedy { get; } = isGreedy; @@ -241,14 +277,21 @@ namespace StgSharp.RegularAnalysis.Text internal override RegexIRCommand Command { get; } = RegexIRCommand.Count_Seq; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t COUNT_SEQ {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} SEQ: \"{Sequence}\""; + return $"COUNT_SEQ {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} SEQ: \"{Sequence}\""; } } - internal sealed class RegexCountSetIR(int min, int max, bool isGreedy, string set) : RegexIR, IRegexCount + internal sealed class RegexCountSetIR( + int min, + int max, + bool isGreedy, + string set + ) : RegexIR, IRegexCount { public bool IsGreedy { get; } = isGreedy; @@ -267,14 +310,18 @@ namespace StgSharp.RegularAnalysis.Text internal override RegexIRCommand Command => RegexIRCommand.Count_Set; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t COUNT_SET {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} SET: \"{CharSet}\""; + return $"COUNT_SET {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} SET: \"{CharSet}\""; } } - internal sealed class RegexTryIR(int count) : RegexIR, IRegexComplex + internal sealed class RegexTryIR( + int count + ) : RegexIR, IRegexComplex { public bool IsFullyOptimized { get; set; } @@ -283,23 +330,29 @@ namespace StgSharp.RegularAnalysis.Text internal override RegexIRCommand Command => RegexIRCommand.Try; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t TRY {CommandCount}"; + return $"TRY {CommandCount}"; } } - internal sealed class RegexPopIR(bool isSuccess) : RegexIR + internal sealed class RegexPopIR( + bool isSuccess + ) : RegexIR { public bool IsSuccess { get; } = isSuccess; internal override RegexIRCommand Command => RegexIRCommand.Pop; - internal override string AsCommand(int lineNum) + protected override string AsCommand( + + ) { - return $"[{lineNum}]\t POP {(IsSuccess ? "SUCCESS" : "FAIL")}"; + return $"POP {(IsSuccess ? "SUCCESS" : "FAIL")}"; } } diff --git a/StgSharpDebug/TestRegexAnalyse.cs b/StgSharpDebug/TestRegexAnalyse.cs index 22b78fc4..e041d74b 100644 --- a/StgSharpDebug/TestRegexAnalyse.cs +++ b/StgSharpDebug/TestRegexAnalyse.cs @@ -41,13 +41,13 @@ namespace StgSharp private static string[] regexSource = [ @"[.]*", - /* + /**/ @"http|https|ftp|ftps", @"abc[0-9]x|abc[0-9]y|abd[0-9]x|abd[0-9]y|xyz", @"get_name|get_value|get_type|set_name|set_value|set_type", @"helix|hello|help|hero|hers|her", - @"a*.[abc]*(abcd|a(bc)c)+(?.*)234" -/**/ + @"a*.[abc]*(?abcd|a(bc)c)(?.*)234" + /**/ ]; public static void Run() @@ -75,6 +75,7 @@ namespace StgSharp Console.WriteLine(opt_ir[i].AsCommand(i)); } Console.WriteLine(); + Console.WriteLine(interpreterSource.AnalyseException); Console.WriteLine(); Console.WriteLine(); } diff --git a/future/ngra-regex-ir-review.md b/future/ngra-regex-ir-review.md deleted file mode 100644 index 84c2e96a..00000000 --- a/future/ngra-regex-ir-review.md +++ /dev/null @@ -1,1050 +0,0 @@ -# NGRA 字符串正则 IR 设计补充建议 - -基于当前 `ngra-regex-ir.md` 的设计,我认为整体方向是成立的:它已经不是“完全低级的执行字节码”,而是保留了 `COUNT`、`COUNT_SEQ`、`COUNT_SET`、`TRY`、`PACK` 等高层结构,因此可以继续承担后续 match-opt 的输入。 - -不过,为了让规则更容易扩展、避免语义误判,我建议把优化明确拆成三类,并把当前 IR 分成“语义 IR / HIR”和“计划 IR / LIR”。 - ---- - -## 1. 总体判断 - -当前 IR 最大的优点: - -1. **足够紧凑**:`COUNT_SEQ`、`COUNT_SET`、`TRY` 等复合指令可以避免过早打碎成 `LoadChar + Compare + Branch`。 -2. **保留 regex 结构**:量词、字符集、确定序列、组打包等信息仍然可见。 -3. **适合继续做 match planning**:后续可以从这些指令中识别 `.*literal`、`[set]*`、bounded repeat、固定长度 block 等结构。 - -当前 IR 最大的风险: - -1. `SEARCH` 这个名字容易误导。当前 HIR 中的 `SEARCH` 实际应当是 **MATCH_AT_CURRENT**,不是允许跳过字符的 `FIND`。 -2. 检查点目前只描述为锚点指针栈,但真正的 backtracking frame 至少还要保存 `pos`、capture watermark、symbol watermark、repeat state 和 continuation。 -3. `COUNT_*` 的语义必须明确是否保留 retry/backtracking,否则 `a*.`、`[abc]*abc` 等场景会被错误优化。 -4. 字符集的语义表示和物理表示要分开:语义上 `[0-9a-z]` 是一个 set,物理上可以用 inline set-region 的多条 range-test 表示,避免每个 set 挂 `List`。 - ---- - -## 2. 建议的分层 - -我建议仍然区分 HIR 和 LIR,不是因为机器必须分,而是因为人写规则时边界更清楚。 - -```text -AST - ↓ -AST Opt - ↓ -HIR Gen - ↓ -HIR Analysis - ↓ -HIR → LIR / MatchPlan Lowering - ↓ -LIR - ↓ -SourceGen / RuntimeGen / Interpreter -``` - -### HIR:表达 regex 语义 - -HIR 保留接近正则结构的指令: - -```text -SEARCH_SEQ // 语义上是当前位置 MATCH_SEQ -SEARCH_SET // 语义上是当前位置 MATCH_SET -SEARCH_DOT // 或 UNIT_DOT,不应和 literal "." 混淆 -COUNT -COUNT_SEQ -COUNT_SET -TRY -POP -PACK -``` - -注意:HIR 中可以继续叫 `SEARCH`,但文档里要明确: - -```text -HIR 的 SEARCH_* 不表示向后查找; -它表示从当前活动指针开始匹配。 -真正允许跳过字符的搜索操作只在 LIR / MatchPlan 阶段引入,命名为 FIND_*。 -``` - -### LIR:表达匹配计划 - -LIR 不只是更低级,而是“已经做过策略规划”的 IR: - -```text -MATCH_SEQ -MATCH_CHAR -MATCH_DOT -MATCH_SET - -FIND_SEQ -FIND_CHAR -FIND_SET -FIND_LAST_SEQ -FIND_LAST_SET - -CONSUME_WHILE_SET -CONSUME_UNTIL_CHAR -CONSUME_UNTIL_SET - -COUNT_SET -COUNT_SEQ -COUNT_BLOCK -CHECK_MIN_LENGTH -CHECK_FIXED_OFFSET - -PUSH_BACKTRACK -POP_BACKTRACK -COMMIT -FAIL - -CAPTURE_BEGIN -CAPTURE_END -PACK -``` - -LIR 中应该已经包含: - -```text -LoweredSetId -SearchMode -RepeatMode -RetryMode -CaptureMode -EmitHint -``` - ---- - -## 3. 三类优化策略 - -建议把所有优化分成三类: - -```text -1. Regex algebra rewrite - 改正则结构。 - -2. Match plan rewrite - 改匹配策略。 - -3. Backend / C# span trick - 改 C# 代码写法。 -``` - -这个分类很重要,因为它能直接决定优化应该放在哪个阶段。 - ---- - -# 4. Regex algebra rewrite - -这类优化应该放在 AST Opt 或 HIR 生成前后。它们改变的是正则结构,而不是 C# 写法。 - -## 4.1 Literal 合并 - -```regex -a b c -``` - -变成: - -```regex -abc -``` - -HIR: - -```text -SEARCH_SEQ "abc" -``` - -不要生成三条 `SEARCH_CHAR`。 - ---- - -## 4.2 Concat / Alternation flatten - -```regex -a(?:b(?:c)) -``` - -变成: - -```regex -abc -``` - -```regex -a|(?:b|c) -``` - -变成: - -```regex -a|b|c -``` - ---- - -## 4.3 Alternation factoring / trie - -这个你已经放在 AST Opt 做,位置是对的。 - -```regex -foobar|foobaz|foozip -``` - -变成: - -```regex -foo(?:bar|baz|zip) -``` - -或者 trie AST: - -```text -SEQ "foo" -ALT - SEQ "bar" - SEQ "baz" - SEQ "zip" -``` - -这类结构改写越早越好。后续 IR opt 只需要识别这个结构并决定是否 `switch` / first-set dispatch。 - ---- - -## 4.4 Bounded repeat 展开 - -```regex -a{2,3} -``` - -可以变成: - -```regex -aa a? -``` - -或: - -```text -SEARCH_SEQ "aa" -OPTIONAL SEARCH_CHAR 'a' -``` - -对于 lazy: - -```regex -a{2,3}?b -``` - -要注意尝试顺序。它等价于: - -```regex -aa(?:b|ab) -``` - -而不是: - -```regex -aa(?:a|ab) -``` - -Greedy 版本: - -```regex -a{2,3}b -``` - -如果展开成显式分支,尝试顺序更像: - -```regex -aa(?:ab|b) -``` - -语言集合相同,但 backtracking 尝试顺序不同。 - ---- - -## 4.5 `a+b` 不建议改成 `a*(ab|b)` - -当前文档中写: - -```text -a+b 可以优化为 a*(ab|b) -``` - -这个需要修正。 - -`a+b` 的语言是: - -```text -a^n b, n >= 1 -``` - -而 `a*(ab|b)` 会额外匹配: - -```text -b -``` - -因为第二个分支 `a*b` 可以在 `a*` 取 0 时匹配 `b`。 - -更安全的结构是: - -```regex -aa*b -``` - -或者: - -```text -SEARCH_CHAR 'a' -COUNT_SEQ 'a' 0..∞ -SEARCH_CHAR 'b' -``` - -如果要利用后继 `b` 做策略优化,应在 MatchPlan 阶段处理,而不是做这个不等价的 algebra rewrite。 - ---- - -## 4.6 Charset 合并 - -```regex -[abc]|[def] -``` - -可以变成: - -```regex -[abcdef] -``` - -```regex -[a-f]|[d-z] -``` - -可以变成: - -```regex -[a-z] -``` - -这属于 AST/HIR 层的字符类代数。 - ---- - -## 4.7 Charset 展开为 alternation 要谨慎 - -文档中写: - -```text -abc[de] 可以展开为 (abcd|abce) -``` - -这个是可行的,但不一定总是好。建议规则改成: - -```text -当 charset 很小,并且展开后能形成更长 literal / trie / branch dispatch 时,可以展开; -否则保留为 MATCH_SET。 -``` - -例如: - -```regex -abc[de] -``` - -既可以是: - -```text -SEARCH_SEQ "abc" -SEARCH_SET [de] -``` - -也可以是: - -```text -ALT - SEARCH_SEQ "abcd" - SEARCH_SEQ "abce" -``` - -前者代码更短,后者可能更适合 required literal 或 trie。需要 code size budget。 - ---- - -# 5. Match plan rewrite - -这类优化应该放在 HIR Analysis / HIR → LIR lowering。 - -它们不一定改成另一条正则,而是把匹配过程改成更高效的搜索/消费计划。 - ---- - -## 5.1 长度预测应该在 HIR Analysis 做,SourceGen 只负责发代码 - -当前文档写“长度预测在 source-gen 阶段做”。我建议改成: - -```text -minLength / maxLength 的计算在 HIR Analysis 做; -SourceGen 只负责把 CHECK_MIN_LENGTH 发成 C#。 -``` - -例如: - -```regex -abc[0-9]{2}xyz -``` - -分析结果: - -```text -MinLength = 8 -``` - -LIR: - -```text -CHECK_MIN_LENGTH 8 -MATCH_SEQ "abc" -... -``` - -SourceGen: - -```csharp -if ((uint)(end - pos) < 8) goto Fail; -``` - ---- - -## 5.2 `.*` 优化 - -HIR: - -```text -COUNT_DOT min=0 max=∞ greedy=true -MATCH_CHAR 'a' -``` - -可以 lower 成: - -```text -FIND_LAST_CHAR 'a' -``` - -lazy: - -```text -COUNT_DOT min=0 max=∞ greedy=false -MATCH_CHAR 'a' -``` - -lower 成: - -```text -FIND_CHAR 'a' -``` - -但要注意: - -1. 默认 `.` 不匹配 `\n`,搜索范围应限制在当前行。 -2. Singleline 模式下才可以跨整个 span。 -3. 如果 `.*` 内部或后继结构涉及 capture/backtracking,不能简单“删掉 COUNT”,而应转换为带 retry 的 find plan。 -4. 后继如果是 `MATCH_SEQ "abc"`,则变成 `FIND_SEQ` / `FIND_LAST_SEQ`。 -5. 后继如果是 `MATCH_SET`,则变成 `FIND_SET` / `FIND_LAST_SET`。 - ---- - -## 5.3 `[set]*` 优化 - -文档中写 `[set]*` 优化在 source-gen 阶段做。建议改成: - -```text -是否将 COUNT_SET 变成 CONSUME_WHILE / CONSUME_UNTIL,在 lowering 做; -具体使用 IndexOfAnyExcept / SearchValues / manual loop,在 SourceGen 做。 -``` - -例如: - -```regex -[a-f0-9]* -``` - -LIR: - -```text -CONSUME_WHILE_SET LowerHex -``` - -SourceGen: - -```csharp -int n = span.IndexOfAnyExcept(s_lowerHex); -``` - -或者手写 loop / range compare。 - ---- - -## 5.4 First-set 搜索 - -如果非锚定匹配: - -```regex -abc|defg|xyz -``` - -first-set 是: - -```text -'a', 'd', 'x' -``` - -LIR 可以是: - -```text -FIND_SET { 'a', 'd', 'x' } -VERIFY_BRANCH -``` - -你文档里说“这个不好做,因为 IR 直接把 ALT 打碎成 if-else 串了”。这说明 AST Opt / HIR 里最好保留 ALT region 或者至少保留 branch summary。 - -即使 AST 已经 trie 化,IR 也应该尽量不要把它完全打碎成不可恢复的 if-else 串。至少给每个 region 计算 summary: - -```text -FirstSet -MinLength -RequiredLiteral -CanBacktrack -CanCapture -``` - ---- - -## 5.5 Required literal 搜索 - -例如: - -```regex -.*ERROR:[0-9]+ -``` - -可以直接规划: - -```text -FIND_SEQ "ERROR:" -VERIFY_AFTER_ANCHOR -``` - -这通常比只做 first-set 更强。 - ---- - -## 5.6 Fixed-offset check - -```regex -[a-z]{20}XYZ -``` - -可以 lower 成: - -```text -CHECK_MIN_LENGTH 23 -CHECK_AT_OFFSET 20 MATCH_SEQ "XYZ" -MATCH_SET_REPEAT [a-z] count=20 -MATCH_SEQ "XYZ" -``` - -这个属于 match plan,不属于 sourcegen。 - ---- - -## 5.7 Auto-atomic / possessive 化 - -```regex -[0-9]*[A-Z] -``` - -由于: - -```text -[0-9] ∩ [A-Z] = ∅ -``` - -所以 `[0-9]*` 不需要回退,可以 lower 成: - -```text -COUNT_SET [0-9] greedy atomic -MATCH_SET [A-Z] -``` - -如果后继 first-set 和 repeat set 有交集,就不能这么做。 - ---- - -## 5.8 Repeat retry mode - -LIR 应该明确 repeat 的 retry 策略: - -```text -RetryMode.None -RetryMode.BackoffBy1 -RetryMode.BackoffByStride -RetryMode.SavedPositions -RetryMode.GeneralBacktrack -``` - -例如: - -```regex -(abc[cd])+ -``` - -每轮固定长度 4,所以可以: - -```text -RetryMode.BackoffByStride stride=4 -``` - -不需要保存每个内部指令的回溯点。 - ---- - -# 6. Backend / C# span trick - -这类优化只放 SourceGen。它不再改变 regex 或 match plan。 - ---- - -## 6.1 `StartsWith` - -LIR: - -```text -MATCH_SEQ "sample" -``` - -SourceGen 决定: - -```csharp -slice.StartsWith("sample", StringComparison.Ordinal) -``` - -还是短串展开: - -```csharp -slice.Length >= 3 && -slice[0] == 'a' && -slice[1] == 'b' && -slice[2] == 'c' -``` - -建议规则: - -```text -长度 1: - char compare - -长度 2~3: - 直接 char compare - -中长 literal: - StartsWith - -IgnoreCase: - StartsWith(..., OrdinalIgnoreCase) -``` - ---- - -## 6.2 `[0-9]` 不建议写成 `IndexOf("0123456789")` 用于当前位置测试 - -文档中写: - -```text -[0-9] 展开为 IndexOf("0123456789") -``` - -这个要分两种场景: - -### 当前位置匹配 - -如果是: - -```text -MATCH_SET [0-9] -``` - -更好是: - -```csharp -(uint)(ch - '0') <= 9 -``` - -### span 中搜索 - -如果是: - -```text -FIND_SET [0-9] -``` - -可以考虑: - -```csharp -span.IndexOfAnyInRange('0', '9') -``` - -或: - -```csharp -span.IndexOfAny(s_digits) -``` - -所以规则应写成: - -```text -MATCH_SET [0-9] → unsigned range compare -FIND_SET [0-9] → IndexOfAnyInRange / SearchValues -``` - ---- - -## 6.3 SearchValues - -对于多字符集合: - -```text -FIND_SET [0-9a-f] -``` - -SourceGen 可生成: - -```csharp -private static readonly SearchValues s_lowerHex = - SearchValues.Create("0123456789abcdef"); -``` - -然后: - -```csharp -span.IndexOfAny(s_lowerHex); -``` - -当前位置测试不一定要用 `SearchValues`,可能 range compare 或 bitmap 更快。 - ---- - -## 6.4 Inline vs helper - -复杂 Unicode set: - -```text -MATCH_SET UnicodeWord -``` - -SourceGen 决定: - -```csharp -if (!IsUnicodeWord(ch)) goto Fail; -``` - -还是: - -```csharp -if (ch < 128) -{ - if (!AsciiWordContains(ch)) goto Fail; -} -else -{ - if (!IsUnicodeWord(ch)) goto Fail; -} -``` - -这个应由代码尺寸、热度、是否 source-gen 决定。 - ---- - -## 6.5 Code size budget - -SourceGen 应负责最终代码膨胀控制: - -```text -单方法最大语句数 -literal 展开阈值 -bounded repeat 展开阈值 -是否生成 helper -是否抽 cold path -SearchValues 字段数量 -``` - ---- - -# 7. 字符集的物理表示建议 - -为了避免每个 set 挂 `List`,可以用线性 IR region 表示同一个字符测试。 - -例如语义: - -```regex -[0-9a-z] -``` - -物理 IR: - -```text -MATCH_SET_BEGIN default=false end=13 - TEST_RANGE '0' '9' accept=true - TEST_RANGE 'a' 'z' accept=true -MATCH_SET_END -``` - -语义: - -```regex -[^0-9a-z] -``` - -物理 IR: - -```text -MATCH_SET_BEGIN default=true end=13 - TEST_RANGE '0' '9' accept=false - TEST_RANGE 'a' 'z' accept=false -MATCH_SET_END -``` - -要点是: - -```text -这些 TEST_RANGE 测的是同一个字符; -整个 MATCH_SET 只推进 pos 一次。 -``` - -不能把它误解成连续匹配多个字符。 - -复杂 Unicode set 可以走: - -```text -MATCH_SET_REF setId=42 -``` - -`setId=42` 指向全局 pooled table,而不是每个节点一个 List。 - ---- - -# 8. 检查点语义建议 - -当前文档把检查点描述为锚点指针栈,这对 capture 起点有用,但不够覆盖 backtracking。 - -建议把 checkpoint / backtrack frame 定义成: - -```text -BacktrackFrame: - pos - anchor - captureTop - symbolTop - repeatState - continuation -``` - -其中: - -```text -pos: - 活动指针恢复位置 - -anchor: - 区间开始位置 - -captureTop: - 捕获记录栈回滚点 - -symbolTop: - 符号区回滚点 - -repeatState: - 当前 COUNT/loop 的计数、起止位置、retry mode - -continuation: - 失败后跳到哪里继续尝试 -``` - -如果暂时是 tokenizer / no-capture 模式,可以裁掉 captureTop,但 IR 语义最好预留。 - ---- - -# 9. 建议修改原文的几个点 - -## 9.1 “HIT” 应改为 “HIR” - -原文: - -```text -将HIT进行进一步优化 -``` - -应为: - -```text -将 HIR 进行进一步优化 -``` - -## 9.2 `SEARCH` 的定义应改写 - -原文说“向后搜索确定长度序列”。建议改为: - -```text -HIR 中 SEARCH_* 表示从当前活动指针开始匹配; -它不是允许跳过字符的 FIND。 -真正的 FIND_* 在 HIR→LIR 阶段由 match planner 引入。 -``` - -## 9.3 `a+b → a*(ab|b)` 不等价 - -建议删除或改成: - -```regex -a+b → aa*b -``` - -或者: - -```text -MATCH_CHAR 'a' -COUNT_SEQ 'a' 0..∞ -MATCH_CHAR 'b' -``` - -## 9.4 `[0-9] → IndexOf("0123456789")` 应细分 - -改成: - -```text -当前位置 MATCH_SET: - 用 unsigned range compare。 - -span 搜索 FIND_SET: - 用 IndexOfAnyInRange / SearchValues / IndexOfAny。 -``` - -## 9.5 `.*` 优化不要说 “COUNT 消失” - -建议改成: - -```text -COUNT_DOT 与后继匹配项合并为 FIND / FIND_LAST 计划。 -``` - -这样更准确,因为有时还需要保留 retry / range 限制 / newline 限制。 - ---- - -# 10. 推荐最终管线 - -```text -Tokenize - - 区分 Dot 与 EscapedLiteralDot - - 区分 raw charset / literal / quantifier / group - -AST Build - - 建立 regex 语义树 - -AST Opt - - literal merge - - concat flatten - - alternation factoring / trie - - bounded repeat normalization - - simple group elimination - - charset algebra merge - -HIR Gen - - SEARCH_* as MATCH_AT_CURRENT - - COUNT_* - - TRY / POP - - PACK - - raw set / raw dot - -HIR Analysis - - minLength / maxLength - - nullable - - firstSet - - lastSet - - required literal - - fixed length - - capture effect - - repeat overlap - -HIR → LIR / MatchPlan - - raw set → lowered set - - dot → dot mode - - introduce FIND - - introduce CONSUME - - choose retry mode - - choose atomic/possessive - - emit check-min-length / fixed-offset checks - -LIR - - explicit MATCH / FIND / CONSUME - - explicit lowered set - - explicit backtracking plan - - explicit capture plan - -SourceGen - - StartsWith / SequenceEqual - - IndexOf / IndexOfAny / IndexOfAnyExcept - - SearchValues - - range compare / bitmap - - inline/helper split - - code size budget -``` - ---- - -# 11. 总结 - -我建议保留你当前 IR 的核心设计,但把文档里的边界收紧: - -```text -AST Opt: - 做 Regex algebra rewrite。 - -HIR: - 表示从当前位置开始的 regex 语义。 - -HIR Analysis: - 提取结构属性。 - -HIR → LIR: - 做 Match plan rewrite。 - -LIR: - 表示明确的匹配计划。 - -SourceGen: - 做 C# span trick。 -``` - -最重要的改动是: - -```text -SEARCH 在 HIR 中应被定义为 MATCH_AT_CURRENT; -FIND 只能由后续优化阶段引入。 -``` - -以及: - -```text -字符集可以用线性 set-region 表示,避免 List 分配; -但 set-region 内的多条 range-test 必须测试同一个字符,不能变成多个连续 MATCH。 -``` - -这样你的 IR 会同时满足三个目标: - -1. 对人来说可读; -2. 对优化规则来说容易扩展; -3. 对 GC 和 sourcegen 来说物理表示足够紧凑。 diff --git a/future/ngra-regex-ir-sample.md b/future/ngra-regex-ir-sample.md new file mode 100644 index 00000000..2fdf9c63 --- /dev/null +++ b/future/ngra-regex-ir-sample.md @@ -0,0 +1,453 @@ +result of "[.]*" +| * COUNT +|| [.] UNIT_SET +-----IR----- +[0] 0000,0,0,0,0000 COUNT_SET GREEDY MIN: 0 MAX: -1 SET: "[.]" +-----Optimized once----- +[0] 0000,0,0,0,0000 COUNT_SET GREEDY MIN: 0 MAX: -1 SET: "[.]" + + + + +result of "http|https|ftp|ftps" +| ALT +|| CONCAT +||| http UNIT_SPAN +||| ALT +|||| UNIT +|||| s UNIT +|| CONCAT +||| ftp UNIT_SPAN +||| ALT +|||| UNIT +|||| s UNIT +-----IR----- +[0] 0000,0,0,0,0000 TRY 10 +[1] 0000,0,0,0,0000 MATCH_SEQ "http" +[2] 0000,0,0,0,0000 TRY 1 +[3] 0000,0,0,0,0000 MATCH_SEQ "" +[4] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[5] 0000,0,0,0,0000 POP SUCCESS +[6] 0000,0,0,0,0000 TRY 1 +[7] 0000,0,0,0,0000 MATCH_SEQ "s" +[8] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[9] 0000,0,0,0,0000 POP SUCCESS +[10] 0000,0,0,0,0000 POP FAIL +[11] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[12] 0000,0,0,0,0000 POP SUCCESS +[13] 0000,0,0,0,0000 TRY 10 +[14] 0000,0,0,0,0000 MATCH_SEQ "ftp" +[15] 0000,0,0,0,0000 TRY 1 +[16] 0000,0,0,0,0000 MATCH_SEQ "" +[17] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[18] 0000,0,0,0,0000 POP SUCCESS +[19] 0000,0,0,0,0000 TRY 1 +[20] 0000,0,0,0,0000 MATCH_SEQ "s" +[21] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[22] 0000,0,0,0,0000 POP SUCCESS +[23] 0000,0,0,0,0000 POP FAIL +[24] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[25] 0000,0,0,0,0000 POP SUCCESS +[26] 0000,0,0,0,0000 POP FAIL +-----Optimized once----- +[0] 0000,0,0,0,0000 TRY 10 +[1] 0000,0,0,0,0000 MATCH_SEQ "http" +[2] 0000,0,0,0,0000 TRY 1 +[3] 0000,0,0,0,0000 MATCH_SEQ "" +[4] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[5] 0000,0,0,0,0000 POP SUCCESS +[6] 0000,0,0,0,0000 TRY 1 +[7] 0000,0,0,0,0000 MATCH_SEQ "s" +[8] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[9] 0000,0,0,0,0000 POP SUCCESS +[10] 0000,0,0,0,0000 POP FAIL +[11] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[12] 0000,0,0,0,0000 POP SUCCESS +[13] 0000,0,0,0,0000 TRY 10 +[14] 0000,0,0,0,0000 MATCH_SEQ "ftp" +[15] 0000,0,0,0,0000 TRY 1 +[16] 0000,0,0,0,0000 MATCH_SEQ "" +[17] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[18] 0000,0,0,0,0000 POP SUCCESS +[19] 0000,0,0,0,0000 TRY 1 +[20] 0000,0,0,0,0000 MATCH_SEQ "s" +[21] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[22] 0000,0,0,0,0000 POP SUCCESS +[23] 0000,0,0,0,0000 POP FAIL +[24] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[25] 0000,0,0,0,0000 POP SUCCESS +[26] 0000,0,0,0,0000 POP FAIL + + + + +result of "abc[0-9]x|abc[0-9]y|abd[0-9]x|abd[0-9]y|xyz" +| ALT +|| CONCAT +||| ab UNIT_SPAN +||| ALT +|||| CONCAT +||||| c UNIT +||||| CONCAT +|||||| [0-9] UNIT_SET +|||||| ALT +||||||| x UNIT +||||||| y UNIT +|||| CONCAT +||||| d UNIT +||||| CONCAT +|||||| [0-9] UNIT_SET +|||||| ALT +||||||| x UNIT +||||||| y UNIT +|| CONCAT +||| xyz UNIT_SPAN +-----IR----- +[0] 0000,0,0,0,0000 TRY 30 +[1] 0000,0,0,0,0000 MATCH_SEQ "ab" +[2] 0000,0,0,0,0000 TRY 11 +[3] 0000,0,0,0,0000 MATCH_SEQ "c" +[4] 0000,0,0,0,0000 MATCH_SET "[0-9]" +[5] 0000,0,0,0,0000 TRY 1 +[6] 0000,0,0,0,0000 MATCH_SEQ "x" +[7] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[8] 0000,0,0,0,0000 POP SUCCESS +[9] 0000,0,0,0,0000 TRY 1 +[10] 0000,0,0,0,0000 MATCH_SEQ "y" +[11] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[12] 0000,0,0,0,0000 POP SUCCESS +[13] 0000,0,0,0,0000 POP FAIL +[14] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[15] 0000,0,0,0,0000 POP SUCCESS +[16] 0000,0,0,0,0000 TRY 11 +[17] 0000,0,0,0,0000 MATCH_SEQ "d" +[18] 0000,0,0,0,0000 MATCH_SET "[0-9]" +[19] 0000,0,0,0,0000 TRY 1 +[20] 0000,0,0,0,0000 MATCH_SEQ "x" +[21] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[22] 0000,0,0,0,0000 POP SUCCESS +[23] 0000,0,0,0,0000 TRY 1 +[24] 0000,0,0,0,0000 MATCH_SEQ "y" +[25] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[26] 0000,0,0,0,0000 POP SUCCESS +[27] 0000,0,0,0,0000 POP FAIL +[28] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[29] 0000,0,0,0,0000 POP SUCCESS +[30] 0000,0,0,0,0000 POP FAIL +[31] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[32] 0000,0,0,0,0000 POP SUCCESS +[33] 0000,0,0,0,0000 TRY 1 +[34] 0000,0,0,0,0000 MATCH_SEQ "xyz" +[35] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[36] 0000,0,0,0,0000 POP SUCCESS +[37] 0000,0,0,0,0000 POP FAIL +-----Optimized once----- +[0] 0000,0,0,0,0000 TRY 30 +[1] 0000,0,0,0,0000 MATCH_SEQ "ab" +[2] 0000,0,0,0,0000 TRY 11 +[3] 0000,0,0,0,0000 MATCH_SEQ "c" +[4] 0000,0,0,0,0000 MATCH_SET "[0-9]" +[5] 0000,0,0,0,0000 TRY 1 +[6] 0000,0,0,0,0000 MATCH_SEQ "x" +[7] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[8] 0000,0,0,0,0000 POP SUCCESS +[9] 0000,0,0,0,0000 TRY 1 +[10] 0000,0,0,0,0000 MATCH_SEQ "y" +[11] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[12] 0000,0,0,0,0000 POP SUCCESS +[13] 0000,0,0,0,0000 POP FAIL +[14] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[15] 0000,0,0,0,0000 POP SUCCESS +[16] 0000,0,0,0,0000 TRY 11 +[17] 0000,0,0,0,0000 MATCH_SEQ "d" +[18] 0000,0,0,0,0000 MATCH_SET "[0-9]" +[19] 0000,0,0,0,0000 TRY 1 +[20] 0000,0,0,0,0000 MATCH_SEQ "x" +[21] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[22] 0000,0,0,0,0000 POP SUCCESS +[23] 0000,0,0,0,0000 TRY 1 +[24] 0000,0,0,0,0000 MATCH_SEQ "y" +[25] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[26] 0000,0,0,0,0000 POP SUCCESS +[27] 0000,0,0,0,0000 POP FAIL +[28] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[29] 0000,0,0,0,0000 POP SUCCESS +[30] 0000,0,0,0,0000 POP FAIL +[31] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[32] 0000,0,0,0,0000 POP SUCCESS +[33] 0000,0,0,0,0000 TRY 1 +[34] 0000,0,0,0,0000 MATCH_SEQ "xyz" +[35] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[36] 0000,0,0,0,0000 POP SUCCESS +[37] 0000,0,0,0,0000 POP FAIL + + + + +result of "get_name|get_value|get_type|set_name|set_value|set_type" +| ALT +|| CONCAT +||| get_ UNIT_SPAN +||| ALT +|||| CONCAT +||||| name UNIT_SPAN +|||| CONCAT +||||| value UNIT_SPAN +|||| CONCAT +||||| type UNIT_SPAN +|| CONCAT +||| set_ UNIT_SPAN +||| ALT +|||| CONCAT +||||| name UNIT_SPAN +|||| CONCAT +||||| value UNIT_SPAN +|||| CONCAT +||||| type UNIT_SPAN +-----IR----- +[0] 0000,0,0,0,0000 TRY 14 +[1] 0000,0,0,0,0000 MATCH_SEQ "get_" +[2] 0000,0,0,0,0000 TRY 1 +[3] 0000,0,0,0,0000 MATCH_SEQ "name" +[4] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[5] 0000,0,0,0,0000 POP SUCCESS +[6] 0000,0,0,0,0000 TRY 1 +[7] 0000,0,0,0,0000 MATCH_SEQ "value" +[8] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[9] 0000,0,0,0,0000 POP SUCCESS +[10] 0000,0,0,0,0000 TRY 1 +[11] 0000,0,0,0,0000 MATCH_SEQ "type" +[12] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[13] 0000,0,0,0,0000 POP SUCCESS +[14] 0000,0,0,0,0000 POP FAIL +[15] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[16] 0000,0,0,0,0000 POP SUCCESS +[17] 0000,0,0,0,0000 TRY 14 +[18] 0000,0,0,0,0000 MATCH_SEQ "set_" +[19] 0000,0,0,0,0000 TRY 1 +[20] 0000,0,0,0,0000 MATCH_SEQ "name" +[21] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[22] 0000,0,0,0,0000 POP SUCCESS +[23] 0000,0,0,0,0000 TRY 1 +[24] 0000,0,0,0,0000 MATCH_SEQ "value" +[25] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[26] 0000,0,0,0,0000 POP SUCCESS +[27] 0000,0,0,0,0000 TRY 1 +[28] 0000,0,0,0,0000 MATCH_SEQ "type" +[29] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[30] 0000,0,0,0,0000 POP SUCCESS +[31] 0000,0,0,0,0000 POP FAIL +[32] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[33] 0000,0,0,0,0000 POP SUCCESS +[34] 0000,0,0,0,0000 POP FAIL +-----Optimized once----- +[0] 0000,0,0,0,0000 TRY 14 +[1] 0000,0,0,0,0000 MATCH_SEQ "get_" +[2] 0000,0,0,0,0000 TRY 1 +[3] 0000,0,0,0,0000 MATCH_SEQ "name" +[4] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[5] 0000,0,0,0,0000 POP SUCCESS +[6] 0000,0,0,0,0000 TRY 1 +[7] 0000,0,0,0,0000 MATCH_SEQ "value" +[8] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[9] 0000,0,0,0,0000 POP SUCCESS +[10] 0000,0,0,0,0000 TRY 1 +[11] 0000,0,0,0,0000 MATCH_SEQ "type" +[12] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[13] 0000,0,0,0,0000 POP SUCCESS +[14] 0000,0,0,0,0000 POP FAIL +[15] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[16] 0000,0,0,0,0000 POP SUCCESS +[17] 0000,0,0,0,0000 TRY 14 +[18] 0000,0,0,0,0000 MATCH_SEQ "set_" +[19] 0000,0,0,0,0000 TRY 1 +[20] 0000,0,0,0,0000 MATCH_SEQ "name" +[21] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[22] 0000,0,0,0,0000 POP SUCCESS +[23] 0000,0,0,0,0000 TRY 1 +[24] 0000,0,0,0,0000 MATCH_SEQ "value" +[25] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[26] 0000,0,0,0,0000 POP SUCCESS +[27] 0000,0,0,0,0000 TRY 1 +[28] 0000,0,0,0,0000 MATCH_SEQ "type" +[29] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[30] 0000,0,0,0,0000 POP SUCCESS +[31] 0000,0,0,0,0000 POP FAIL +[32] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[33] 0000,0,0,0,0000 POP SUCCESS +[34] 0000,0,0,0,0000 POP FAIL + + + + +result of "helix|hello|help|hero|hers|her" +| CONCAT +|| he UNIT_SPAN +|| ALT +||| CONCAT +|||| l UNIT +|||| ALT +||||| CONCAT +|||||| ix UNIT_SPAN +||||| CONCAT +|||||| lo UNIT_SPAN +||||| p UNIT +||| CONCAT +|||| r UNIT +|||| ALT +||||| o UNIT +||||| s UNIT +||||| UNIT +-----IR----- +[0] 0000,0,0,0,0000 MATCH_SEQ "he" +[1] 0000,0,0,0,0000 TRY 14 +[2] 0000,0,0,0,0000 MATCH_SEQ "l" +[3] 0000,0,0,0,0000 TRY 1 +[4] 0000,0,0,0,0000 MATCH_SEQ "ix" +[5] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[6] 0000,0,0,0,0000 POP SUCCESS +[7] 0000,0,0,0,0000 TRY 1 +[8] 0000,0,0,0,0000 MATCH_SEQ "lo" +[9] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[10] 0000,0,0,0,0000 POP SUCCESS +[11] 0000,0,0,0,0000 TRY 1 +[12] 0000,0,0,0,0000 MATCH_SEQ "p" +[13] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[14] 0000,0,0,0,0000 POP SUCCESS +[15] 0000,0,0,0,0000 POP FAIL +[16] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[17] 0000,0,0,0,0000 POP SUCCESS +[18] 0000,0,0,0,0000 TRY 14 +[19] 0000,0,0,0,0000 MATCH_SEQ "r" +[20] 0000,0,0,0,0000 TRY 1 +[21] 0000,0,0,0,0000 MATCH_SEQ "o" +[22] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[23] 0000,0,0,0,0000 POP SUCCESS +[24] 0000,0,0,0,0000 TRY 1 +[25] 0000,0,0,0,0000 MATCH_SEQ "s" +[26] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[27] 0000,0,0,0,0000 POP SUCCESS +[28] 0000,0,0,0,0000 TRY 1 +[29] 0000,0,0,0,0000 MATCH_SEQ "" +[30] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[31] 0000,0,0,0,0000 POP SUCCESS +[32] 0000,0,0,0,0000 POP FAIL +[33] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[34] 0000,0,0,0,0000 POP SUCCESS +[35] 0000,0,0,0,0000 POP FAIL +-----Optimized once----- +[0] 0000,0,0,0,0000 MATCH_SEQ "he" +[1] 0000,0,0,0,0000 TRY 14 +[2] 0000,0,0,0,0000 MATCH_SEQ "l" +[3] 0000,0,0,0,0000 TRY 1 +[4] 0000,0,0,0,0000 MATCH_SEQ "ix" +[5] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[6] 0000,0,0,0,0000 POP SUCCESS +[7] 0000,0,0,0,0000 TRY 1 +[8] 0000,0,0,0,0000 MATCH_SEQ "lo" +[9] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[10] 0000,0,0,0,0000 POP SUCCESS +[11] 0000,0,0,0,0000 TRY 1 +[12] 0000,0,0,0,0000 MATCH_SEQ "p" +[13] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[14] 0000,0,0,0,0000 POP SUCCESS +[15] 0000,0,0,0,0000 POP FAIL +[16] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[17] 0000,0,0,0,0000 POP SUCCESS +[18] 0000,0,0,0,0000 TRY 14 +[19] 0000,0,0,0,0000 MATCH_SEQ "r" +[20] 0000,0,0,0,0000 TRY 1 +[21] 0000,0,0,0,0000 MATCH_SEQ "o" +[22] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[23] 0000,0,0,0,0000 POP SUCCESS +[24] 0000,0,0,0,0000 TRY 1 +[25] 0000,0,0,0,0000 MATCH_SEQ "s" +[26] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[27] 0000,0,0,0,0000 POP SUCCESS +[28] 0000,0,0,0,0000 TRY 1 +[29] 0000,0,0,0,0000 MATCH_SEQ "" +[30] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[31] 0000,0,0,0,0000 POP SUCCESS +[32] 0000,0,0,0,0000 POP FAIL +[33] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[34] 0000,0,0,0,0000 POP SUCCESS +[35] 0000,0,0,0,0000 POP FAIL + + + + +result of "a*.[abc]*(?abcd|a(bc)c)(?.*)234" +| CONCAT +|| * COUNT +||| a UNIT +|| CONCAT +||| . UNIT_SET +||| CONCAT +|||| * COUNT +||||| [abc] UNIT_SET +|||| CONCAT +||||| (? GROUP_BEGIN +|||||| CONCAT +||||||| abc UNIT_SPAN +||||||| ALT +|||||||| d UNIT +|||||||| c UNIT +||||| CONCAT +|||||| (? GROUP_BEGIN +||||||| * COUNT +|||||||| . UNIT_SET +|||||| CONCAT +||||||| 234 UNIT_SPAN +-----IR----- +[0] 0000,0,0,0,0000 COUNT_SEQ GREEDY MIN: 0 MAX: -1 SEQ: "a" +[1] 0000,0,0,0,0000 MATCH_SET "." +[2] 0000,0,0,0,0000 COUNT_SET GREEDY MIN: 0 MAX: -1 SET: "[abc]" +[3] 0000,0,0,0,0000 TRY 10 +[4] 0000,0,0,0,0000 MATCH_SEQ "abc" +[5] 0000,0,0,0,0000 TRY 1 +[6] 0000,0,0,0,0000 MATCH_SEQ "d" +[7] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[8] 0000,0,0,0,0000 POP SUCCESS +[9] 0000,0,0,0,0000 TRY 1 +[10] 0000,0,0,0,0000 MATCH_SEQ "c" +[11] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[12] 0000,0,0,0,0000 POP SUCCESS +[13] 0000,0,0,0,0000 POP FAIL +[14] 0000,0,0,0,0000 CONDITION SUCCESS: 2 FAIL: 1 +[15] 0000,0,0,0,0000 POP FAIL +[16] 0000,0,0,0,0000 PACK SUCCESS GROUP: "a" +[17] 0000,0,0,0,0000 TRY 1 +[18] 0000,0,0,0,0000 COUNT_SET GREEDY MIN: 0 MAX: -1 SET: "." +[19] 0000,0,0,0,0000 CONDITION SUCCESS: 2 FAIL: 1 +[20] 0000,0,0,0,0000 POP FAIL +[21] 0000,0,0,0,0000 PACK SUCCESS GROUP: "name" +[22] 0000,0,0,0,0000 MATCH_SEQ "234" +-----Optimized once----- +[0] 0000,0,0,0,0000 COUNT_SEQ GREEDY MIN: 0 MAX: -1 SEQ: "a" +[1] 0000,0,0,0,0000 MATCH_SET "." +[2] 0000,0,0,0,0000 COUNT_SET GREEDY MIN: 0 MAX: -1 SET: "[abc]" +[3] 0000,0,0,0,0000 TRY 10 +[4] 0000,0,0,0,0000 MATCH_SEQ "abc" +[5] 0000,0,0,0,0000 TRY 1 +[6] 0000,0,0,0,0000 MATCH_SEQ "d" +[7] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[8] 0000,0,0,0,0000 POP SUCCESS +[9] 0000,0,0,0,0000 TRY 1 +[10] 0000,0,0,0,0000 MATCH_SEQ "c" +[11] 0000,0,0,0,0000 CONDITION SUCCESS: 1 FAIL: 2 +[12] 0000,0,0,0,0000 POP SUCCESS +[13] 0000,0,0,0,0000 POP FAIL +[14] 0000,0,0,0,0000 CONDITION SUCCESS: 2 FAIL: 1 +[15] 0000,0,0,0,0000 POP FAIL +[16] 0000,0,0,0,0000 PACK SUCCESS GROUP: "a" +[17] 0000,0,0,0,0000 TRY 1 +[18] 0000,0,0,0,0000 FIND_COMPLEX FURTHEST COUNT: -1 +[19] 0000,0,0,0,0000 CONDITION SUCCESS: 2 FAIL: 1 +[20] 0000,0,0,0,0000 POP FAIL +[21] 0000,0,0,0,0000 PACK SUCCESS GROUP: "name" +[22] 0000,0,0,0,0000 MATCH_SEQ "234" + + + + +Program has exited with no error, press any key to exit. \ No newline at end of file