do some source gen work, findseq, matchseq, matchseq framework done, on find set

This commit is contained in:
Youmu Liu
2026-07-16 18:30:50 +08:00
parent ee7cbd869c
commit 0d41058632
5 changed files with 428 additions and 85 deletions
@@ -38,6 +38,15 @@ namespace StgSharp.RegularAnalysis.Abstraction
emitter.Append(string.IsNullOrEmpty(line) ? string.Empty : line); emitter.Append(string.IsNullOrEmpty(line) ? string.Empty : line);
} }
public static SequenceEmitter<string> AppendLine(
this SequenceEmitter<string> emitter,
string line,
bool condition
)
{
return emitter is null ? null! : condition ? emitter.Append(line) : emitter;
}
} }
public sealed class SequenceEmitter<T> public sealed class SequenceEmitter<T>
+63 -11
View File
@@ -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; } 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; public bool IsFirst { get; } = isFirst;
@@ -70,7 +76,9 @@ namespace StgSharp.RegularAnalysis.Text
/// <summary> /// <summary>
/// Searches for a certain string pattern or charset from current position. /// Searches for a certain string pattern or charset from current position.
/// </summary> /// </summary>
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; 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; 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; 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; 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; 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; 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; 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; } 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; public bool IsSuccess { get; } = isSuccess;
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
using Microsoft.CodeAnalysis.CSharp.Syntax;
using StgSharp.RegularAnalysis.Abstraction; using StgSharp.RegularAnalysis.Abstraction;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -20,6 +21,7 @@ namespace StgSharp.RegularAnalysis.Text
{ {
private const string current_ptr = "current_ptr"; private const string current_ptr = "current_ptr";
private const string offset = "offset";
private const string orig_text = "text"; private const string orig_text = "text";
internal static SequenceEmitter<string> GenerateSource( internal static SequenceEmitter<string> GenerateSource(
@@ -28,7 +30,7 @@ namespace StgSharp.RegularAnalysis.Text
{ {
SequenceEmitter<string> se = new(); SequenceEmitter<string> se = new();
List<SequenceEmitter<string>> sub_match_list = []; List<SequenceEmitter<string>> sub_match_list = [];
VarNameGenerator ng = new(); SourceGenContext ng = new();
RegexInfo info = source.Info; RegexInfo info = source.Info;
List<RegexIR> ir = source.IRs; List<RegexIR> ir = source.IRs;
@@ -36,6 +38,28 @@ namespace StgSharp.RegularAnalysis.Text
RegexIR[] window = new RegexIR[8]; RegexIR[] window = new RegexIR[8];
Span<RegexIR> scan_win = window; Span<RegexIR> scan_win = window;
SequenceEmitter<string> 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)")// _ = se.AppendLine($@"public override MatchResult Match({ROS_char} text)")//
.AppendLine(@"{"); .AppendLine(@"{");
@@ -47,24 +71,13 @@ namespace StgSharp.RegularAnalysis.Text
.AppendLine($@"bool region_{i}_success;"); .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; _ = ng.GenVarDefine(se);
do
{
int result;
int count = int.Min(8, ir_sp.Length - index); _ = se.Append(match_e);
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);
foreach (SequenceEmitter<string> _sub in sub_match_list) foreach (SequenceEmitter<string> _sub in sub_match_list)
{ {
@@ -77,66 +90,171 @@ namespace StgSharp.RegularAnalysis.Text
return se; 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 #region sub generator
private static int TryGenerateMatchSeq( private static int TryGenerateFindSeq(
Span<RegexIR> scan_win, Span<RegexIR> scan_win,
SequenceEmitter<string> se, SequenceEmitter<string> se,
VarNameGenerator ng SourceGenContext ng
) )
{ {
if (scan_win[0] is not RegexMatchSeqIR seq) { if (scan_win[0] is not RegexFindSeqIR seq) {
return -1; return -1;
} }
string pattern = seq.Pattern; string pattern = seq.Pattern;
int length = pattern.Length; int length = pattern.Length;
string index_caller = seq.IsFirst ? "IndexOf" : "LastIndexOf";
_ = se.AppendLine() _ = se.AppendLine()
.AppendLine($@"if(text[{current_ptr}..({current_ptr} + {length})] == {pattern})") .AppendLine($@"if(({offset} = [{current_ptr}..].{index_caller}({pattern})) != -1)")
.AppendLine($@"{{") .AppendLine($@"{{")
.AppendLine($@"{current_ptr} += {length};") .AppendLine($@"{current_ptr} += {offset} + {length};")
.AppendLine($@"}}"); .AppendLine($@"}}");
return length;
}
private static int TryGenerateMatchSeq(
Span<RegexIR> scan_win,
SequenceEmitter<string> 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<RegexIR> scan_win,
SequenceEmitter<string> 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; 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( private static int TryGenerateMatchSet(
Span<RegexIR> scan_win, Span<RegexIR> scan_win,
SequenceEmitter<string> se, SequenceEmitter<string> se,
VarNameGenerator ng SourceGenContext ng
) )
{ {
if (scan_win[0] is not RegexMatchSeqIR seq) { if (scan_win[0] is not RegexMatchSeqIR seq) {
return -1; return -1;
} }
string pattern = seq.Pattern; 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}") _ = se.AppendLine($@"// match charset of {pattern}")
.AppendLine($@"char {var_char} = {orig_text}[{current_ptr}];") .AppendLine($@"char {var_char} = {orig_text}[{current_ptr}];")
.AppendLine(); .AppendLine();
bool accept = set_pattern[0].Accept;
_ = se.AppendLine("if("); _ = se.AppendLine("if(");
RegexCharSet p = set_pattern[0]; RegexCharSet p = set_pattern[0];
string _case = GenerateCase(p); string _case = GenerateCase(p);
@@ -164,29 +282,63 @@ namespace StgSharp.RegularAnalysis.Text
return 1; return 1;
string GenerateCase( string GenerateCase(
RegexCharSet c RegexCharSet c
) )
{ {
return c.Type switch switch (c.Type)
{ {
// Handle single character // 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 // 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 // Handle set of characters
CharSetType.Set => c.Value switch case CharSetType.Set:
{ if (c.Accept)
"s" => $@"char.IsWhiteSpace({var_char})", {
"w" => $@"char.IsLetterOrDigit({var_char}) || {var_char} == '_'", return c.Value switch
"d" => $@"char.IsDigit({var_char})", {
_ => throw new InvalidCastException(), "s" => $@"char.IsWhiteSpace({var_char})",
}, "w" => $@"char.IsLetterOrDigit({var_char}) || {var_char} == '_'",
CharSetType.None => string.Empty, "d" => $@"char.IsDigit({var_char})",
_ => string.Empty, _ => 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( private static RegexCharSet[] FigureCharSetType(
ReadOnlySpan<char> source ReadOnlySpan<char> source,
out bool accept
) )
{ {
// range or single // range or single
bool accept;
if (source[0] == '[') if (source[0] == '[')
{ {
int idx; int idx;
@@ -232,7 +384,7 @@ namespace StgSharp.RegularAnalysis.Text
ReadOnlySpan<char> seq = source.Slice(idx, source.Length - idx - 1); ReadOnlySpan<char> seq = source.Slice(idx, source.Length - idx - 1);
if (seq.IndexOf('-') == -1) if (seq.IndexOf('-') == -1)
{ {
return [new RegexCharSet(seq.ToString(), CharSetType.Single, accept)]; return [ new RegexCharSet(seq.ToString(), CharSetType.Single, true) ];
} else } else
{ {
List<RegexCharSet> list = []; List<RegexCharSet> list = [];
@@ -241,17 +393,18 @@ namespace StgSharp.RegularAnalysis.Text
int pos = seq.IndexOf('-'); int pos = seq.IndexOf('-');
if (pos == -1) if (pos == -1)
{ {
RegexCharSet single = new(seq.ToString(), CharSetType.Single, accept); // has no range
list.Add(single); SplitSet(seq, list);
break; break;
} else } else if (pos > 1)
if (pos > 1) { {
list.Add(new(seq[0..(pos - 1)].ToString(), CharSetType.Single, accept)); // 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)..]; seq = seq[(pos + 2)..];
} }
return [.. list]; return [ .. list ];
} }
} else if (source[0] == '\\') } else if (source[0] == '\\')
{ {
@@ -260,9 +413,9 @@ namespace StgSharp.RegularAnalysis.Text
return char.ToLower(source[0], CultureInfo.InvariantCulture) return char.ToLower(source[0], CultureInfo.InvariantCulture)
switch switch
{ {
's' => [new("s", CharSetType.Set, accept)], 's' => [ new("s", CharSetType.Set, accept) ],
'w' => [new("w", CharSetType.Set, accept)], 'w' => [ new("w", CharSetType.Set, accept) ],
'd' => [new("d", CharSetType.Set, accept)], 'd' => [ new("d", CharSetType.Set, accept) ],
_ => throw new NotSupportedException() _ => throw new NotSupportedException()
}; };
} else } else
@@ -270,6 +423,40 @@ namespace StgSharp.RegularAnalysis.Text
// unknown, throw // unknown, throw
throw new InvalidCastException($"Unknown char set: {source}"); throw new InvalidCastException($"Unknown char set: {source}");
} }
static void SplitSet(
ReadOnlySpan<char> seq,
List<RegexCharSet> sets
)
{
if (seq.Length == 0 || sets == null) {
return;
}
int pos;
ReadOnlySpan<char> remain = seq;
while ((pos = remain.IndexOf('\\')) != -1 || remain.Length > 0)
{
if (pos != 0)
{
ReadOnlySpan<char> 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 #endregion
@@ -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<string, int> _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<string> GenVarDefine(
SequenceEmitter<string> 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
}
}
+1 -1
View File
@@ -74,7 +74,7 @@ namespace StgSharp
} }
} }
[GeneratedRegex(@".*a*b", RegexOptions.ExplicitCapture)] [GeneratedRegex(@"", RegexOptions.ExplicitCapture)]
internal static partial Regex GetRegex(); internal static partial Regex GetRegex();
} }