diff --git a/src/StgSharp.RegularAnalysis/Abstraction/SequenceEmitter.cs b/src/StgSharp.RegularAnalysis/Abstraction/SequenceEmitter.cs index c92490ff..2bcca18b 100644 --- a/src/StgSharp.RegularAnalysis/Abstraction/SequenceEmitter.cs +++ b/src/StgSharp.RegularAnalysis/Abstraction/SequenceEmitter.cs @@ -38,6 +38,15 @@ namespace StgSharp.RegularAnalysis.Abstraction emitter.Append(string.IsNullOrEmpty(line) ? string.Empty : line); } + public static SequenceEmitter AppendLine( + this SequenceEmitter emitter, + string line, + bool condition + ) + { + return emitter is null ? null! : condition ? emitter.Append(line) : emitter; + } + } public sealed class SequenceEmitter diff --git a/src/StgSharp.RegularAnalysis/Text/RegexIR.cs b/src/StgSharp.RegularAnalysis/Text/RegexIR.cs index 96a74a20..2bb3dbc6 100644 --- a/src/StgSharp.RegularAnalysis/Text/RegexIR.cs +++ b/src/StgSharp.RegularAnalysis/Text/RegexIR.cs @@ -37,7 +37,10 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexFindComplexIR(bool nearest, int count) : RegexIR(RegexIRCommand.Find_Complex), IRegexComplex + internal sealed class RegexFindComplexIR( + bool nearest, + int count + ) : RegexIR(RegexIRCommand.Find_Complex), IRegexComplex { public bool IsFullyOptimized { get; set; } @@ -53,7 +56,10 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexFindSeqIR(string pattern, bool isFirst) : RegexIR(RegexIRCommand.Find_Seq), IRegexSequence + internal sealed class RegexFindSeqIR( + string pattern, + bool isFirst + ) : RegexIR(RegexIRCommand.Find_Seq), IRegexSequence { public bool IsFirst { get; } = isFirst; @@ -70,7 +76,9 @@ namespace StgSharp.RegularAnalysis.Text /// /// Searches for a certain string pattern or charset from current position. /// - internal sealed class RegexMatchSeqIR(string pattern) : RegexIR(RegexIRCommand.Match_Seq), IRegexSequence + internal sealed class RegexMatchSeqIR( + string pattern + ) : RegexIR(RegexIRCommand.Match_Seq), IRegexSequence { public string Pattern { get; } = pattern; @@ -82,7 +90,9 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexMatchSetIR(string charset) : RegexIR(RegexIRCommand.Match_Set) + internal sealed class RegexMatchSetIR( + string charset + ) : RegexIR(RegexIRCommand.Match_Set) { public string Charset { get; } = charset; @@ -94,7 +104,10 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexConditionIR(int success, int fail) : RegexIR(RegexIRCommand.Condition) + internal sealed class RegexConditionIR( + int success, + int fail + ) : RegexIR(RegexIRCommand.Condition) { public int SuccessCase { get; } = success; @@ -108,7 +121,10 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexPackIR(bool condition, string name = "") : RegexIR(RegexIRCommand.Pack) + internal sealed class RegexPackIR( + bool condition, + string name = "" + ) : RegexIR(RegexIRCommand.Pack) { public bool Condition { get; } = condition; @@ -122,7 +138,12 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexCountComplexIR(int min, int max, bool isGreedy, int operation) : RegexIR(RegexIRCommand.Count_Complex), IRegexCount, IRegexComplex + internal sealed class RegexCountComplexIR( + int min, + int max, + bool isGreedy, + int operation + ) : RegexIR(RegexIRCommand.Count_Complex), IRegexCount, IRegexComplex { public bool IsGreedy { get; } = isGreedy; @@ -146,7 +167,12 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexCountSeqIR(int min, int max, bool isGreedy, string seq) : RegexIR(RegexIRCommand.Count_Seq), IRegexCount, IRegexSequence + internal sealed class RegexCountSeqIR( + int min, + int max, + bool isGreedy, + string seq + ) : RegexIR(RegexIRCommand.Count_Seq), IRegexCount, IRegexSequence { public bool IsGreedy { get; } = isGreedy; @@ -168,7 +194,12 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexCountSetIR(int min, int max, bool isGreedy, string set) : RegexIR(RegexIRCommand.Count_Set), IRegexCount + internal sealed class RegexCountSetIR( + int min, + int max, + bool isGreedy, + string set + ) : RegexIR(RegexIRCommand.Count_Set), IRegexCount { public bool IsGreedy { get; } = isGreedy; @@ -192,7 +223,9 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexTryIR(int count) : RegexIR(RegexIRCommand.Try), IRegexComplex + internal sealed class RegexTryIR( + int count + ) : RegexIR(RegexIRCommand.Try), IRegexComplex { public bool IsFullyOptimized { get; set; } @@ -206,7 +239,26 @@ namespace StgSharp.RegularAnalysis.Text } - internal sealed class RegexPopIR(bool isSuccess) : RegexIR(RegexIRCommand.Pop) + internal sealed class RegexFindSetIR( + string charset, + bool isFirst + ) : RegexIR(RegexIRCommand.Find_Set) + { + + public bool IsFirst { get; } = isFirst; + + public string Charset { get; } = charset; + + protected override string AsCommand() + { + return $"FIND_SET \"{Charset}\""; + } + + } + + internal sealed class RegexPopIR( + bool isSuccess + ) : RegexIR(RegexIRCommand.Pop) { public bool IsSuccess { get; } = isSuccess; diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs index c35f1c8c..a915af4d 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs @@ -5,6 +5,7 @@ // SPDX-License-Identifier: MIT // ----------------------------------------------------------------------------- +using Microsoft.CodeAnalysis.CSharp.Syntax; using StgSharp.RegularAnalysis.Abstraction; using System; using System.Collections.Generic; @@ -20,6 +21,7 @@ namespace StgSharp.RegularAnalysis.Text { private const string current_ptr = "current_ptr"; + private const string offset = "offset"; private const string orig_text = "text"; internal static SequenceEmitter GenerateSource( @@ -28,7 +30,7 @@ namespace StgSharp.RegularAnalysis.Text { SequenceEmitter se = new(); List> sub_match_list = []; - VarNameGenerator ng = new(); + SourceGenContext ng = new(); RegexInfo info = source.Info; List ir = source.IRs; @@ -36,6 +38,28 @@ namespace StgSharp.RegularAnalysis.Text RegexIR[] window = new RegexIR[8]; Span scan_win = window; + SequenceEmitter match_e = new(); + int index = 0; + do + { + int result; + + int count = int.Min(8, ir_sp.Length - index); + ir_sp.Slice(index, count).CopyTo(scan_win); + if (TryGenerateMatchSeq(scan_win, match_e, ng) > 0) + { + count += 1; + } else if (TryGenerateMatchSet(scan_win, match_e, ng) > 0) + { + count++; + } else if (TryGenerateFindSeq(scan_win, match_e, ng) > 0) + { + count++; + } else { } + index += count; + } while (index < ir.Count); + + _ = se.AppendLine($@"public override MatchResult Match({ROS_char} text)")// .AppendLine(@"{"); @@ -47,24 +71,13 @@ namespace StgSharp.RegularAnalysis.Text .AppendLine($@"bool region_{i}_success;"); } - _ = se.AppendLine($@"int {current_ptr} = 0;"); + _ = se.AppendLine($@"int {current_ptr} = 0;") + .AppendLine($@"int {offset} = 0;") + .AppendLine(); - int index = 0; - do - { - int result; + _ = ng.GenVarDefine(se); - int count = int.Min(8, ir_sp.Length - index); - ir_sp.Slice(index, count).CopyTo(scan_win); - if (TryGenerateMatchSeq(scan_win, se, ng) > 0) - { - count++; - } else if (TryGenerateMatchSet(scan_win, se, ng) > 0) - { - count++; - } else { } - index += count; - } while (index < ir.Count); + _ = se.Append(match_e); foreach (SequenceEmitter _sub in sub_match_list) { @@ -77,66 +90,171 @@ namespace StgSharp.RegularAnalysis.Text return se; } - #region var naming - - private class VarNameGenerator - { - - private int char_counter; - - public string GenerateCharName() - { - string name = $"__char_{char_counter}"; - char_counter++; - return name; - } - - } - - #endregion - - #region sub generator - private static int TryGenerateMatchSeq( + private static int TryGenerateFindSeq( Span scan_win, SequenceEmitter se, - VarNameGenerator ng + SourceGenContext ng ) { - if (scan_win[0] is not RegexMatchSeqIR seq) { + if (scan_win[0] is not RegexFindSeqIR seq) { return -1; } string pattern = seq.Pattern; int length = pattern.Length; + string index_caller = seq.IsFirst ? "IndexOf" : "LastIndexOf"; + _ = se.AppendLine() - .AppendLine($@"if(text[{current_ptr}..({current_ptr} + {length})] == {pattern})") + .AppendLine($@"if(({offset} = [{current_ptr}..].{index_caller}({pattern})) != -1)") .AppendLine($@"{{") - .AppendLine($@"{current_ptr} += {length};") + .AppendLine($@"{current_ptr} += {offset} + {length};") .AppendLine($@"}}"); + return length; + } + + private static int TryGenerateMatchSeq( + Span scan_win, + SequenceEmitter se, + SourceGenContext ng + ) + { + if (scan_win[0] is not RegexFindSeqIR seq) { + return -1; + } + string pattern = seq.Pattern; + int length = pattern.Length; + + _ = se.AppendLine() + .AppendLine($@"if([{current_ptr}..({current_ptr} + {length})] == {pattern})") + .AppendLine($@"{{") + .AppendLine($@"{current_ptr} += {offset} + {length};") + .AppendLine($@"}}"); + return length; + } + + private static int TryGenerateFindSet( + Span scan_win, + SequenceEmitter se, + SourceGenContext ng + ) + { + if (scan_win[0] is not RegexFindSetIR seq) { + return -1; + } + string pattern = seq.Charset; + + RegexCharSet[] set_pattern = FigureCharSetType(pattern, out bool accept); + + bool first = seq.IsFirst; + + string remain = ng.CurRemain; + string offset = ng.FindResult; + _ = se.AppendLine($@"// match charset of {pattern}") + .AppendLine($@"{remain} = {orig_text}[{current_ptr}..];") + .AppendLine(); + + _ = se.AppendLine("if("); + RegexCharSet p = set_pattern[0]; + string _case = accept ? GenerateAcceptCase(p) : GenerateDenyCase(p); + _ = se.AppendLine($@"if ({_case}||"); + + for (int i = 1; i < set_pattern.Length - 1; i++) + { + p = set_pattern[i]; + _case = accept ? GenerateAcceptCase(p) : GenerateDenyCase(p); + if (string.IsNullOrEmpty(_case)) + { + continue; + } + _ = se.AppendLine($@" {_case} ||"); + } + + p = set_pattern[^1]; + _case = accept ? GenerateAcceptCase(p) : GenerateDenyCase(p); + _ = se.AppendLine($@"{_case})") + .AppendLine("{") + .AppendLine($@"{current_ptr} += 1;") + .AppendLine("}"); return 1; + + + string GenerateAcceptCase( + RegexCharSet c + ) + { + string caller; + switch (c.Type) + { + // Handle single character + case CharSetType.Single: + caller = first ? "IndexOfAny" : "LastIndexOfAny"; + return $@"({offset} = {remain}.{caller}(""{c.Value}"") != -1)"; + + // Handle range of characters + case CharSetType.Range: + caller = first ? "IndexOfAnyInRange" : "LastIndexOfAnyInRange"; + return $@"({offset} = {remain}.{caller}('{c.Value[0]}', '{c.Value[1]}') != -1)"; + + // Handle set of characters + case CharSetType.Set: + switch (c.Value) + { + case "s": + caller = first ? "IndexOfAnyWhiteSpace" : "LastIndexOfAnyWhiteSpace"; + return $@"({offset} = {remain}.{caller}() != -1)"; + case "w": + + default: + break; + } + case CharSetType.None: + return string.Empty; + default: + return string.Empty; + } + } + + string GenerateDenyCase( + RegexCharSet c + ) + { + switch (c.Type) + { + // Handle single character + case CharSetType.Single: + + // Handle range of characters + case CharSetType.Range: + + // Handle set of characters + case CharSetType.Set: + case CharSetType.None: + return string.Empty; + default: + return string.Empty; + } + } } private static int TryGenerateMatchSet( Span scan_win, SequenceEmitter se, - VarNameGenerator ng + SourceGenContext ng ) { if (scan_win[0] is not RegexMatchSeqIR seq) { return -1; } string pattern = seq.Pattern; - RegexCharSet[] set_pattern = FigureCharSetType(pattern); + RegexCharSet[] set_pattern = FigureCharSetType(pattern, out bool accept); - string var_char = ng.GenerateCharName(); + string var_char = ng.CurChar; _ = se.AppendLine($@"// match charset of {pattern}") .AppendLine($@"char {var_char} = {orig_text}[{current_ptr}];") .AppendLine(); - bool accept = set_pattern[0].Accept; - _ = se.AppendLine("if("); RegexCharSet p = set_pattern[0]; string _case = GenerateCase(p); @@ -164,29 +282,63 @@ namespace StgSharp.RegularAnalysis.Text return 1; + string GenerateCase( RegexCharSet c ) { - return c.Type switch + switch (c.Type) { // Handle single character - CharSetType.Single => $@"{var_char} == '{c.Value}'", + case CharSetType.Single: + string set = c.Value; + if (set.Length == 1) + { + return $@"{var_char} == '{c.Value}'"; + } else if (set.Length < 5) + { + StringBuilder sb = new(); + _ = sb.Append('('); + foreach (char item in set) { + _ = sb.Append(CultureInfo.InvariantCulture, $@"item == '{item}' or "); + } + _ = sb.Append(')'); + return sb.ToString(); + } else + { + return $@"""{c.Value}"".AsSpan().Contains({var_char})"; + } // Handle range of characters - CharSetType.Range => $@"({var_char} >= '{c.Value[0]}' && {var_char} <= '{c.Value[1]}')", + case CharSetType.Range: + return $@"({var_char} >= '{c.Value[0]}' && {var_char} <= '{c.Value[1]}')"; // Handle set of characters - CharSetType.Set => c.Value switch - { - "s" => $@"char.IsWhiteSpace({var_char})", - "w" => $@"char.IsLetterOrDigit({var_char}) || {var_char} == '_'", - "d" => $@"char.IsDigit({var_char})", - _ => throw new InvalidCastException(), - }, - CharSetType.None => string.Empty, - _ => string.Empty, - }; + case CharSetType.Set: + if (c.Accept) + { + return c.Value switch + { + "s" => $@"char.IsWhiteSpace({var_char})", + "w" => $@"char.IsLetterOrDigit({var_char}) || {var_char} == '_'", + "d" => $@"char.IsDigit({var_char})", + _ => throw new InvalidCastException(), + }; + } else + { + return c.Value switch + { + "s" => $@"!char.IsWhiteSpace({var_char})", + "w" => $@"!char.IsLetterOrDigit({var_char}) && {var_char} != '_'", + "d" => $@"!char.IsDigit({var_char})", + _ => throw new InvalidCastException(), + }; + } + case CharSetType.None: + return string.Empty; + default: + return string.Empty; + } } } @@ -218,12 +370,12 @@ namespace StgSharp.RegularAnalysis.Text } - public static RegexCharSet[] FigureCharSetType( - ReadOnlySpan source + private static RegexCharSet[] FigureCharSetType( + ReadOnlySpan source, + out bool accept ) { // range or single - bool accept; if (source[0] == '[') { int idx; @@ -232,7 +384,7 @@ namespace StgSharp.RegularAnalysis.Text ReadOnlySpan seq = source.Slice(idx, source.Length - idx - 1); if (seq.IndexOf('-') == -1) { - return [new RegexCharSet(seq.ToString(), CharSetType.Single, accept)]; + return [ new RegexCharSet(seq.ToString(), CharSetType.Single, true) ]; } else { List list = []; @@ -241,17 +393,18 @@ namespace StgSharp.RegularAnalysis.Text int pos = seq.IndexOf('-'); if (pos == -1) { - RegexCharSet single = new(seq.ToString(), CharSetType.Single, accept); - list.Add(single); + // has no range + SplitSet(seq, list); break; - } else - if (pos > 1) { - list.Add(new(seq[0..(pos - 1)].ToString(), CharSetType.Single, accept)); + } else if (pos > 1) + { + // has other sequence before range + SplitSet(seq[0..(pos - 1)], list); } - list.Add(new($"{seq[pos - 1]}{seq[pos + 1]}", CharSetType.Range, accept)); + list.Add(new($"{seq[pos - 1]}{seq[pos + 1]}", CharSetType.Range, true)); seq = seq[(pos + 2)..]; } - return [.. list]; + return [ .. list ]; } } else if (source[0] == '\\') { @@ -260,9 +413,9 @@ namespace StgSharp.RegularAnalysis.Text return char.ToLower(source[0], CultureInfo.InvariantCulture) switch { - 's' => [new("s", CharSetType.Set, accept)], - 'w' => [new("w", CharSetType.Set, accept)], - 'd' => [new("d", CharSetType.Set, accept)], + 's' => [ new("s", CharSetType.Set, accept) ], + 'w' => [ new("w", CharSetType.Set, accept) ], + 'd' => [ new("d", CharSetType.Set, accept) ], _ => throw new NotSupportedException() }; } else @@ -270,6 +423,40 @@ namespace StgSharp.RegularAnalysis.Text // unknown, throw throw new InvalidCastException($"Unknown char set: {source}"); } + + static void SplitSet( + ReadOnlySpan seq, + List sets + ) + { + if (seq.Length == 0 || sets == null) { + return; + } + int pos; + ReadOnlySpan remain = seq; + while ((pos = remain.IndexOf('\\')) != -1 || remain.Length > 0) + { + if (pos != 0) + { + ReadOnlySpan singles = remain[0..pos]; + sets.Add(new RegexCharSet(singles.ToString(), CharSetType.Single, true)); + } + bool accept = char.IsAsciiLetterUpper(remain[pos + 1]); + RegexCharSet set_set = char.ToLower(remain[pos + 1], CultureInfo.InvariantCulture) + switch + { + 's' => new("s", CharSetType.Set, accept), + 'w' => new("w", CharSetType.Set, accept), + 'd' => new("d", CharSetType.Set, accept), + _ => throw new NotSupportedException() + }; + sets.Add(set_set); + remain = remain[(pos + 2)..]; + } + if (remain.Length > 0) { + sets.Add(new RegexCharSet(remain.ToString(), CharSetType.Single, true)); + } + } } #endregion diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs new file mode 100644 index 00000000..3b78eaa1 --- /dev/null +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs @@ -0,0 +1,95 @@ +// ----------------------------------------------------------------------------- +// file="TextRegexSourceGen.GenContext" +// Project: StgSharp +// Copyright (c) Nitload. +// SPDX-License-Identifier: MIT +// ----------------------------------------------------------------------------- + +using StgSharp.RegularAnalysis.Abstraction; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StgSharp.RegularAnalysis.Text +{ + internal partial class TextRegexSourceGen + { + + #region var naming + + private class SourceGenContext + { + + public bool Need_char_cur { get; set; } + + public bool Need_remain_cur { get; set; } + + public bool Need_find_result { get; set; } + + private const string __char_cur = "__char_cur"; + private const string __remain_cur = "__remain_cur"; + private const string __find_result = "__find_result"; + + private readonly Dictionary _search_value_name = []; + private int _min_search_value_index; + + public string CurChar + { + get + { + Need_char_cur = true; + return __char_cur; + } + } + + public int MakeNewSearchValue( + string seq + ) + { + if (_search_value_name.TryGetValue(seq, out int idx)) { + return idx; + } + idx = _min_search_value_index; + _min_search_value_index++; + _search_value_name.Add(seq, idx); + return idx; + } + + public string FindResult + { + get + { + Need_find_result = true; + return __find_result; + } + } + + public string CurRemain + { + get + { + Need_remain_cur = true; + return __remain_cur; + } + } + + public SequenceEmitter GenVarDefine( + SequenceEmitter se + ) + { + if (se is null) { + return null!; + } + _ = se.AppendLine($@"int {__char_cur}", Need_char_cur) + .AppendLine($@"int {__remain_cur}", Need_remain_cur); + + return se; + } + + } + + #endregion + } +} diff --git a/test/StgSharpDebug/TestRegexAnalyse.cs b/test/StgSharpDebug/TestRegexAnalyse.cs index fcfe3598..1db4b947 100644 --- a/test/StgSharpDebug/TestRegexAnalyse.cs +++ b/test/StgSharpDebug/TestRegexAnalyse.cs @@ -74,7 +74,7 @@ namespace StgSharp } } - [GeneratedRegex(@".*a*b", RegexOptions.ExplicitCapture)] + [GeneratedRegex(@"", RegexOptions.ExplicitCapture)] internal static partial Regex GetRegex(); }