From c82a758d972508d77d73b725575a47aca45dd19d Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Tue, 21 Jul 2026 18:02:46 +0800 Subject: [PATCH] mainly theoratical work --- future/ngra-regex-sourcegen-goldens.md | 134 ++++++++++++++++++ .../TextRegexSourceGen.GenClass.MultiIR.cs | 17 ++- .../Text/TextRegexSourceGen.GenClass.cs | 10 +- .../Text/TextRegexSourceGen.GenContext.cs | 23 ++- 4 files changed, 162 insertions(+), 22 deletions(-) create mode 100644 future/ngra-regex-sourcegen-goldens.md diff --git a/future/ngra-regex-sourcegen-goldens.md b/future/ngra-regex-sourcegen-goldens.md new file mode 100644 index 00000000..fcb822b0 --- /dev/null +++ b/future/ngra-regex-sourcegen-goldens.md @@ -0,0 +1,134 @@ +# NGRA Regex 源生成手写样本 + +这个文件用于记录源生成器实现前需要手写的正则匹配样本。 + +流程: + +1. 选一个表达式。 +2. 手写理想匹配代码。 +3. 生成优化后 IR。 +4. 对照手写代码和 IR 段。 +5. 标注每段 IR 应该生成的代码形状。 +6. 抽取生成器规则。 + +选样原则: + +1. 表达式必须是典型正则写法。 +2. 全部样本合起来应覆盖计划支持的大部分 regex 语法。 +3. 样本不以表达式结构优化为目标,避免依赖公共前缀提取、分支树重排等 AST 优化。 +4. `TRY`/`CONDITION` 前缀化改写属于 IR 执行形态整理,不算表达式优化。 + +## 记录模板 + +```text +Regex: + +覆盖形态: + +期望代码形状: + +Optimized IR: + +IR -> Code 规则: + +备注: +``` + +## 第一批:基础线性匹配与转义 + +| Regex | 覆盖形态 | 手写重点 | +| --- | --- | --- | +| `abc` | 单个 `MATCH_SEQ` | 最小长度检查、序列匹配、活动指针推进 | +| `a[abc]d` | `MATCH_SEQ` + `MATCH_SET` + `MATCH_SEQ` | set 后端展开前的调用边界 | +| `[abc]` | 单个 `MATCH_SET` | 字符集单字符匹配 | +| `[.]` | set 中的字面点号 | 确认 `[.]` 和 `.` 语义分离 | +| `.` | 任意字符匹配 | `MATCH_SET "."` 的特殊后端语义 | +| `\.` | 转义字面点号 | 确认 `\.` 进入 seq 系 IR | +| `a\*b` | 转义操作符字面量 | tokenizer 已消解转义语义 | + +## 第二批:计数 + +| Regex | 覆盖形态 | 手写重点 | +| --- | --- | --- | +| `a*` | `COUNT_SEQ`,`MIN:0 MAX:-1` | 贪婪计数、允许空匹配 | +| `a+` | `COUNT_SEQ`,`MIN:1 MAX:-1` | 至少一次的计数 | +| `a?` | `COUNT_SEQ`,`MIN:0 MAX:1` | 可选单元 | +| `a{3,5}` | 固定下限 + 可选上限 | 下限展开与剩余计数 | +| `[abc]*` | `COUNT_SET` | 字符集循环 | +| `.{11,13}` | 任意字符计数 | set 语义在后端展开 | + +## 第三批:ALT 与前缀化 + +| Regex | 覆盖形态 | 手写重点 | +| --- | --- | --- | +| `a|b` | 最小 ALT | region 缓冲、失败切换、成功短路 | +| `ab|cd|ef` | 多分支 ALT | 多个 case 的失败链 | +| `a|bc|def` | 不同长度 ALT | case 间活动指针恢复 | +| `(ab|[cd]|.)x` | 混合 case ALT | seq、set、任意字符分支后接公共后缀 | + +## 第四批:复杂计数块 + +| Regex | 覆盖形态 | 手写重点 | +| --- | --- | --- | +| `(ab){2,3}` | `COUNT` complex body | complex body 调用域、计数退出 | +| `(123(4|5)67){3,4}114514` | complex count + ALT + 后缀 | `CommandCount` 重算、region 复用、后续线性匹配 | + +## 第五批:FIND 与 `.*` + +| Regex | 覆盖形态 | 手写重点 | +| --- | --- | --- | +| `.*abc` | 最远/最近搜索前缀 | `FIND_SEQ` 或 `FIND_COMPLEX` 到后续锚点 | +| `abc.*` | 后缀任意吞吐 | 尾部 `.*` 的快速成功策略 | +| `[.]*` | 字面点号计数 | 不应被当成原装 `.*` 优化 | + +## 第六批:字符集语义 + +| Regex | 覆盖形态 | 手写重点 | +| --- | --- | --- | +| `[123abc]` | 离散字符集 | set 后端展开为离散匹配 | +| `[1-9]` | 连续字符集 | set 后端展开为范围匹配 | +| `\w` | 转义字符集 | escape-set 后端语义 | +| `\w+` | 转义字符集计数 | set 计数循环 | + +## 第七批:捕获组 + +| Regex | 覆盖形态 | 手写重点 | +| --- | --- | --- | +| `(abc)` | 普通捕获组 | 默认序号 group | +| `(?abc)` | 简单命名组 | group 起止范围、`PACK` | +| `(?.*)234` | group 内 `.*` + 后续锚点 | `FIND_COMPLEX FURTHEST -1` 与后续 `MATCH_SEQ` | +| `(?.*)a` | group 内 `.*` + 单字符锚点 | 最远搜索回退到后缀匹配 | +| `(?abcd|a(bc)c)` | group 内 ALT | group success 依赖 region | +| `a*.[abc]*(?abcd|a(bc)c)(?.*)234` | 综合样本 | count、set、ALT、group、find、后缀匹配 | + +## 当前优先级 + +第一阶段只要求源生成器能稳定处理: + +1. `MATCH_SEQ` +2. `MATCH_SET` +3. `COUNT_SEQ` +4. `COUNT_SET` +5. 简单 `ALT` +6. `PACK` +7. `FIND_SEQ` +8. `FIND_COMPLEX COUNT:-1` + +低优先级: + +1. 逐 region 最短长度断言。 +2. 复杂字符集完全内联。 +3. 公共前缀提取和代码树生成。 +4. 对所有正则语义做完整兼容。 + +## 手写代码关注点 + +每个样本的手写代码都应尽量确认这些点: + +1. 活动指针如何推进。 +2. 失败时是否需要恢复活动指针。 +3. region result 和 single-line result 是否必须分配。 +4. `Observe_Line` 是否只读取目标行结果。 +5. `Observe_Region` 是否只读取目标 region 结果。 +6. `PACK` 使用哪一个成功结果作为捕获区间。 +7. 最小长度检查是否值得在当前样本中生成。 diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.MultiIR.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.MultiIR.cs index c3e3a342..e9322009 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.MultiIR.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.MultiIR.cs @@ -64,10 +64,25 @@ namespace StgSharp.RegularAnalysis.Text if (scan_win[0] is not RegexFindSeqIR find) { return -1; } + const string cur_ptr = "cur_ptr", + begin_ptr = "begin_ptr"; _ = main_emitter.AppendLine() .AppendLine(@$"//Find sequence:{FormatStringLiteral(find.Pattern)} at any pos") .AppendLine(@"do") - .AppendLine(@"{"); + .AppendLine(@"{") + .AppendLine(@$"int {cur_ptr}, {begin_ptr} = {current_ptr};") + .AppendLine(); + + string finder = find.IsFirst ? "FirstIndexOf" : "LastIndexOf"; + + if (scan_win[1] is RegexMatchSeqIR seq) + { + string seq_pat = FormatStringLiteral(seq.Pattern); + _ = main_emitter.AppendLine(@$"//Match sequence:{seq_pat} at current pos") + .AppendLine(string.Empty()); + } + + _ = main_emitter.AppendLine("}").AppendLine(string.Empty); return 1; } diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs index 70550560..26b82c4a 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs @@ -24,7 +24,7 @@ namespace StgSharp.RegularAnalysis.Text internal partial class TextRegexSourceGen { - private const string current_ptr = "current_ptr"; + private const string current_ptr = "cur_ptr_global"; private const string offset = "offset"; private const string orig_text = "text"; @@ -39,12 +39,10 @@ namespace StgSharp.RegularAnalysis.Text RegexInfo info = source.Info; List ir = source.IRs; ReadOnlySpan ir_sp = CollectionsMarshal.AsSpan(ir); - RegexIR[] window = new RegexIR[8]; - Span scan_win = window; SequenceEmitter match_e = new(); int index = 0; - int end = FindRrgionEnd(ir_sp, index); + int end = FindRegionEnd(ir_sp, index); ReadOnlySpan region = ir_sp[0..end]; while (true) @@ -55,7 +53,7 @@ namespace StgSharp.RegularAnalysis.Text break; } index = end; - end = FindRrgionEnd(ir_sp, end); + end = FindRegionEnd(ir_sp, end); region = ir_sp[index..end]; } @@ -90,7 +88,7 @@ namespace StgSharp.RegularAnalysis.Text } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FindRrgionEnd( + private static int FindRegionEnd( ReadOnlySpan ir_sp, int current ) diff --git a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs index 3b78eaa1..daf78781 100644 --- a/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs +++ b/src/StgSharp.RegularAnalysis/Text/TextRegexSourceGen.GenContext.cs @@ -32,8 +32,14 @@ namespace StgSharp.RegularAnalysis.Text 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; + private int _min_region_begin_index; + + public string MakeNewRegionBegin() + { + string name = $"__region_begin_{_min_region_begin_index}"; + _min_region_begin_index++; + return name; + } public string CurChar { @@ -44,19 +50,6 @@ namespace StgSharp.RegularAnalysis.Text } } - 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