remove old generatoer, use new generator
This commit is contained in:
@@ -1,29 +1,56 @@
|
||||
# NGRA Regex 源生成手写样本
|
||||
# NGRA Regex 源生成 Checklist
|
||||
|
||||
这个文件用于记录源生成器实现前需要手写的正则匹配样本。
|
||||
本文件按**一次扫描消耗的前缀 IR 数量**分层,记录源生成器需要实现的形态。
|
||||
消耗量从高到低排列,从整个 region 的识别一直降到单条 `MATCH` 指令。
|
||||
|
||||
流程:
|
||||
## 为什么按 IR 消耗量分层
|
||||
|
||||
1. 选一个表达式。
|
||||
2. 手写理想匹配代码。
|
||||
3. 生成优化后 IR。
|
||||
4. 对照手写代码和 IR 段。
|
||||
5. 标注每段 IR 应该生成的代码形状。
|
||||
6. 抽取生成器规则。
|
||||
早期版本按正则语法分批(基础匹配 / 计数 / ALT / 捕获组……),这描述的是**表达式形态**。
|
||||
但 `EmitPrefix` 之后,生成器面对的不再是表达式,而是**带前缀的指令窗口**:
|
||||
|
||||
选样原则:
|
||||
- `a|b` 与 `(?<a>abcd|a(bc)c)` 在语法上分属两批,在前缀 IR 里却共用同一个
|
||||
「region 链 + `Prev_Fail` 切换」骨架;
|
||||
- `abc` 与 `a[abc]d` 在语法上同属一批,消耗的 IR 条数却不同;
|
||||
- `WorkingRegion` / `ExeCondition` / `ObserveMethod` / `PopCondition`
|
||||
是**横切在每条指令上**的,任何按语法组织的清单都无法表达它们。
|
||||
|
||||
1. 表达式必须是典型正则写法。
|
||||
2. 全部样本合起来应覆盖计划支持的大部分 regex 语法。
|
||||
3. 样本不以表达式结构优化为目标,避免依赖公共前缀提取、分支树重排等 AST 优化。
|
||||
4. `TRY`/`CONDITION` 前缀化改写属于 IR 执行形态整理,不算表达式优化。
|
||||
因此清单以「一个 `TryGenerateXxx` 一次吞掉几条 IR」为轴,与
|
||||
`RegexAnalyzer.PrefixEmit` 里 `TryEmitXxx` 的调度链保持同构。
|
||||
|
||||
## 生成器统一约定
|
||||
|
||||
这些约定是所有层级的前提,先固定下来再写具体层:
|
||||
|
||||
1. **返回值语义**:所有 `TryGenerateXxx` 返回**本次消耗的 IR 条数**,`-1` 表示窗口不匹配。
|
||||
不得返回 pattern 长度、字符数等其它含义的整数。
|
||||
2. **调度链顺序**:调度链的 `if / else if` 严格按 L1 → L2 → L3 → L4 排列,
|
||||
层号即消耗量的递减顺序,高消耗形态优先尝试,落空后自然降级到单指令。
|
||||
3. **窗口约定**:扫描窗口为 `ir_sp[index..]`,长度不足时由各 `TryGenerate` 自行短路返回 `-1`。
|
||||
窥探深度参照 IR 优化器的 8 条窗口。
|
||||
4. **前缀不参与消耗计数**:前缀装饰(L5)不单独消耗 IR,它包裹在 L1–L4 产出的代码外层。
|
||||
5. **作用域归属**:代码块的作用域由 `Prefix.WorkingRegion` 决定,不由表达式嵌套决定。
|
||||
6. **控制流形态**:region 一律生成 `do { ... } while(false)` + `break`,
|
||||
不使用 .NET generated regex 的 `goto` + 标签方案。
|
||||
|
||||
### 正确性核心与纯优化的分界
|
||||
|
||||
- **L2 + L4 + L5 是正确性核心**:region 作用域、单指令主体、前缀装饰三者齐备,
|
||||
任意表达式即可端到端生成出语义正确的代码。
|
||||
- **L1 与 L3 是纯优化**:它们只把 L2/L4 会生成的嵌套结构压平成更好的代码形状。
|
||||
未实现时必须干净地返回 `-1`,让窗口自然降级,**不得影响正确性**。
|
||||
|
||||
这条分界决定了实现顺序:先把 L2/L4/L5 打通拿到可运行基线,再回头做 L1/L3。
|
||||
|
||||
## 记录模板
|
||||
|
||||
```text
|
||||
Regex:
|
||||
层级:
|
||||
|
||||
覆盖形态:
|
||||
触发窗口:
|
||||
|
||||
消耗 IR 数:
|
||||
|
||||
覆盖样本:
|
||||
|
||||
期望代码形状:
|
||||
|
||||
@@ -31,90 +58,158 @@ Optimized IR:
|
||||
|
||||
IR -> Code 规则:
|
||||
|
||||
前缀交互:
|
||||
|
||||
备注:
|
||||
```
|
||||
|
||||
## 第一批:基础线性匹配与转义
|
||||
## L0 方法级
|
||||
|
||||
| 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 已消解转义语义 |
|
||||
一次消耗**整个 IR 列表 + `RegexInfo`**,只产出骨架,不产出任何匹配逻辑。
|
||||
|
||||
## 第二批:计数
|
||||
| # | 触发 | 消耗 | 产出 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| L0.1 | `TextRegexSource` 入口 | 全部 | `Match(ReadOnlySpan<char>)` 签名与 `MatchResult` 返回 | [ ] |
|
||||
| L0.2 | `RegionCount` | 元数据 | 每个 region 的 `start` / `end` / `success` 槽 | [ ] |
|
||||
| L0.3 | `SingleLineResultCount` | 元数据 | `Observe_Line` 所需的行结果槽 | [ ] |
|
||||
| L0.4 | `MinPredictLength` | 元数据 | 入口处最小长度断言(可延后) | [ ] |
|
||||
| L0.5 | — | — | 活动指针、锚点指针、`SourceGenContext` 按需变量声明 | [ ] |
|
||||
|
||||
| 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 语义在后端展开 |
|
||||
样本:任意,用 `abc` 验证骨架能编译即可。
|
||||
|
||||
## 第三批:ALT 与前缀化
|
||||
关注点:region 槽是否必须全量分配、行结果槽能否按 `ObserveSource` 稀疏分配。
|
||||
|
||||
| Regex | 覆盖形态 | 手写重点 |
|
||||
| --- | --- | --- |
|
||||
| `a|b` | 最小 ALT | region 缓冲、失败切换、成功短路 |
|
||||
| `ab|cd|ef` | 多分支 ALT | 多个 case 的失败链 |
|
||||
| `a|bc|def` | 不同长度 ALT | case 间活动指针恢复 |
|
||||
| `(ab|[cd]|.)x` | 混合 case ALT | seq、set、任意字符分支后接公共后缀 |
|
||||
## L1 Region 组合级(纯优化)
|
||||
|
||||
## 第四批:复杂计数块
|
||||
一次消耗**多个 region + 尾部控制指令**,是窗口消耗量最高的形态。
|
||||
这些组合在 `EmitPrefix` 里已经被识别过一次
|
||||
(`TryEmitAltChain` / `TryEmitTryGroupTail` / `TryEmitComplexIR`),生成器应与之一一对应。
|
||||
|
||||
| Regex | 覆盖形态 | 手写重点 |
|
||||
| --- | --- | --- |
|
||||
| `(ab){2,3}` | `COUNT` complex body | complex body 调用域、计数退出 |
|
||||
| `(123(4|5)67){3,4}114514` | complex count + ALT + 后缀 | `CommandCount` 重算、region 复用、后续线性匹配 |
|
||||
除 L1.5–L1.7 外,本层全部是**代码形状优化**:不实现时窗口会降级到 L2 + L5,
|
||||
生成出语义等价但嵌套更深的代码。
|
||||
|
||||
## 第五批:FIND 与 `.*`
|
||||
| # | 触发窗口 | 消耗 | 覆盖样本 | 性质 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| L1.1 | region 链,后继 region 带 `Prev_Fail` | Σ region | `a\|b`、`ab\|cd\|ef`、`a\|bc\|def` | 压平 | [ ] |
|
||||
| L1.2 | 同上 + 公共后缀 | Σ region + 后缀 | `(ab\|[cd]\|.)x` | 压平 | [ ] |
|
||||
| L1.3 | region + `POP FAIL(Prev_Fail)` + `PACK SUCCESS(Prev_Success)` | region + 2 | `(?<name>abc)` | 压平 | [ ] |
|
||||
| L1.4 | L1.3 且 region 内部是 ALT 链 | 嵌套 | `(?<a>abcd\|a(bc)c)` | 压平 | [ ] |
|
||||
| L1.5 | `COUNT_COMPLEX` + body | `CommandCount + 1` | `(ab){2,3}` | **必需** | [ ] |
|
||||
| L1.6 | `COUNT_COMPLEX` + body(含 ALT) + 线性后缀 | 嵌套 + 后缀 | `(123(4\|5)67){3,4}114514` | **必需** | [ ] |
|
||||
| L1.7 | `FIND_COMPLEX` + body | `CommandCount + 1` | `.*(ab\|cd)` | **必需** | [ ] |
|
||||
|
||||
| Regex | 覆盖形态 | 手写重点 |
|
||||
| --- | --- | --- |
|
||||
| `.*abc` | 最远/最近搜索前缀 | `FIND_SEQ` 或 `FIND_COMPLEX` 到后续锚点 |
|
||||
| `abc.*` | 后缀任意吞吐 | 尾部 `.*` 的快速成功策略 |
|
||||
| `[.]*` | 字面点号计数 | 不应被当成原装 `.*` 优化 |
|
||||
期望代码形状:L1.1/L1.2 压平为 `if / else if / else` 链;L1.3/L1.4 在 region 成功分支写捕获区间;
|
||||
L1.5–L1.7 为循环 / 搜索包裹 body,`CommandCount` 必须按前缀化后的实际条数重算。
|
||||
|
||||
## 第六批:字符集语义
|
||||
L1.5–L1.7 标记为必需,是因为 `COUNT_COMPLEX` / `FIND_COMPLEX` 的 body 不是独立 region,
|
||||
L2 接不住它们,没有替代降级路径。
|
||||
|
||||
| Regex | 覆盖形态 | 手写重点 |
|
||||
| --- | --- | --- |
|
||||
| `[123abc]` | 离散字符集 | set 后端展开为离散匹配 |
|
||||
| `[1-9]` | 连续字符集 | set 后端展开为范围匹配 |
|
||||
| `\w` | 转义字符集 | escape-set 后端语义 |
|
||||
| `\w+` | 转义字符集计数 | set 计数循环 |
|
||||
关注点:ALT 各 case 之间活动指针如何恢复、`PACK` 取哪一个成功结果作为捕获区间。
|
||||
|
||||
## 第七批:捕获组
|
||||
## L2 Region 级(正确性核心)
|
||||
|
||||
| Regex | 覆盖形态 | 手写重点 |
|
||||
| --- | --- | --- |
|
||||
| `(abc)` | 普通捕获组 | 默认序号 group |
|
||||
| `(?<name>abc)` | 简单命名组 | group 起止范围、`PACK` |
|
||||
| `(?<name>.*)234` | group 内 `.*` + 后续锚点 | `FIND_COMPLEX FURTHEST -1` 与后续 `MATCH_SEQ` |
|
||||
| `(?<name>.*)a` | group 内 `.*` + 单字符锚点 | 最远搜索回退到后缀匹配 |
|
||||
| `(?<a>abcd|a(bc)c)` | group 内 ALT | group success 依赖 region |
|
||||
| `a*.[abc]*(?<a>abcd|a(bc)c)(?<name>.*)234` | 综合样本 | count、set、ALT、group、find、后缀匹配 |
|
||||
一次消耗 **`CommandCount + 1`**,即一个 `TRY` 归约出的完整 region。
|
||||
决定作用域与失败复位策略,是所有嵌套结构的承载体。
|
||||
|
||||
## 当前优先级
|
||||
| # | 触发窗口 | 消耗 | 覆盖样本 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| L2.1 | `RegexTryIR` + 其 body | `CommandCount + 1` | `(abc)` | [ ] |
|
||||
| L2.2 | 同上,body 内含嵌套 region | 递归 | `(a(bc))` | [ ] |
|
||||
| L2.3 | 单指令 region(被折叠的 TRY) | 1 | `(a)` | [ ] |
|
||||
|
||||
第一阶段只要求源生成器能稳定处理:
|
||||
期望代码形状:
|
||||
|
||||
1. `MATCH_SEQ`
|
||||
2. `MATCH_SET`
|
||||
3. `COUNT_SEQ`
|
||||
4. `COUNT_SET`
|
||||
5. 简单 `ALT`
|
||||
6. `PACK`
|
||||
7. `FIND_SEQ`
|
||||
8. `FIND_COMPLEX COUNT:-1`
|
||||
```csharp
|
||||
__rN_begin = __cur;
|
||||
__rN_ok = false;
|
||||
do
|
||||
{
|
||||
// body,POP 在此 break
|
||||
__rN_ok = __ok;
|
||||
} while (false);
|
||||
if (!__rN_ok) { __cur = __rN_begin; }
|
||||
__ok = __rN_ok;
|
||||
```
|
||||
|
||||
低优先级:
|
||||
失败路径必须把活动指针复位到 region 入口。`POP` 直接编译为 `break`,不引入标签。
|
||||
|
||||
关注点:region 全部内联展开、嵌套 region 的指针复位链、`break` 只能跳出最近一层 `do`。
|
||||
|
||||
## L3 多指令定式(纯优化)
|
||||
|
||||
一次消耗 **2–8 条**,滑动窗口内的固定组合。全部属于代码质量优化,非正确性必需。
|
||||
每条都必须能在窗口不匹配时干净地降级到 L4。
|
||||
|
||||
| # | 触发窗口 | 消耗 | 覆盖样本 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| L3.1 | `FIND_COMPLEX FURTHEST COUNT:-1` + `PACK` + `MATCH_SEQ` | 3 | `(?<name>.*)234` | [ ] |
|
||||
| L3.2 | 同上,锚点为单字符 | 3 | `(?<name>.*)a` | [ ] |
|
||||
| L3.3 | `FIND_SEQ` + `MATCH_SEQ`,合并为一次 `IndexOf` | 2 | `.*abc` | [ ] |
|
||||
| L3.4 | `MATCH_SEQ` + `COUNT_SEQ`(`a+`、`a{m,n}` 展开形态) | 2 | `a{3,5}`、`a+` | [ ] |
|
||||
| L3.5 | 连续 `MATCH_SEQ` / `MATCH_SET`,合流为单个 `if` 与表达式 | 2–8 | `a[abc]d` | [ ] |
|
||||
| L3.6 | 尾部 `COUNT_SET .*`,快速成功 | 1–2 | `abc.*` | [ ] |
|
||||
|
||||
关注点:合并后失败诊断是否还能定位到原指令、合并是否破坏 `Observe_Line` 的行号引用。
|
||||
|
||||
## L4 单指令级
|
||||
|
||||
一次消耗 **1 条**。这是调度链的兜底层,必须对每种 `RegexIRCommand` 都有实现。
|
||||
|
||||
| # | 指令 | 覆盖样本 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| L4.1 | `MATCH_SEQ` | `abc`、`\.`、`a\*b` | [ ] |
|
||||
| L4.2 | `MATCH_SET` 离散 | `[abc]`、`[123abc]`、`[.]` | [ ] |
|
||||
| L4.3 | `MATCH_SET` 范围 | `[1-9]` | [ ] |
|
||||
| L4.4 | `MATCH_SET` 转义类 | `\w` | [ ] |
|
||||
| L4.5 | `MATCH_SET` 任意字符 | `.` | [ ] |
|
||||
| L4.6 | `COUNT_SEQ` | `a*`、`a?` | [ ] |
|
||||
| L4.7 | `COUNT_SET` | `[abc]*`、`\w+`、`.{11,13}`、`[.]*` | [ ] |
|
||||
| L4.8 | `FIND_SEQ` | `.*abc` 降级路径 | [ ] |
|
||||
| L4.9 | `FIND_SET` | — | [ ] |
|
||||
| L4.10 | `POP` | ALT 尾 | [ ] |
|
||||
| L4.11 | `PACK` | `(abc)` 默认序号组 | [ ] |
|
||||
|
||||
关注点:`[.]` 与 `.` 必须走不同后端语义;`[.]*` 不得被当作 `.*` 优化。
|
||||
|
||||
## L5 前缀装饰(横切,消耗 0)
|
||||
|
||||
前缀不消耗 IR,而是包裹在 L1–L4 产出的代码外层。
|
||||
**这一层不实现,L1–L4 的产出就无法拼装成正确的控制流。**
|
||||
|
||||
| # | 前缀字段 | 取值 | 生成形态 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| L5.1 | `ExeCondition` | `DEF` | 无条件顺序执行 | [ ] |
|
||||
| L5.2 | `ExeCondition` | `Prev_Success` | 包裹 `if (前驱成功)` | [ ] |
|
||||
| L5.3 | `ExeCondition` | `Prev_Fail` | 包裹 `else` / `if (!前驱成功)` | [ ] |
|
||||
| L5.4 | `ExeCondition` | `Always` | 脱离条件链,始终执行 | [ ] |
|
||||
| L5.5 | `ObserveMethod` | `NONE` | 读紧邻前驱的隐式结果 | [ ] |
|
||||
| L5.6 | `ObserveMethod` | `Observe_Line` + `ObserveSource` | 读指定行结果槽 | [ ] |
|
||||
| L5.7 | `ObserveMethod` | `Observe_Region` + `ObserveSource` | 读指定 region 结果槽 | [ ] |
|
||||
| L5.8 | `PopCondition` | `Pop_Success` | 写回 region 成功并提前退出 | [ ] |
|
||||
| L5.9 | `PopCondition` | `Pop_Fail` | 写回 region 失败并提前退出 | [ ] |
|
||||
| L5.10 | `WorkingRegion` | 任意 | 决定代码落入哪个 region 作用域 | [ ] |
|
||||
|
||||
关注点:`Observe_Line` 只读目标行结果,`Observe_Region` 只读目标 region 结果,
|
||||
二者都不得退化成「读上一条」。行结果槽仅在被 `Observe_Line` 引用时才分配。
|
||||
|
||||
## 实现顺序
|
||||
|
||||
调度链按 L1 → L4 排列,但**实现**先拿正确性核心(L2/L4/L5),再回头填纯优化层(L1/L3):
|
||||
|
||||
1. **L0** — 生成的空骨架能通过编译,`TextRegexSourceGen.GenFile` 的两处 TODO 闭合。
|
||||
2. **L2.1** — `(abc)` 产出正确的 `do{}while(false)` 作用域与复位逻辑。
|
||||
3. **L5.1–L5.3、L5.5** — 顺序与成功/失败链能拼装,此时 L2 的 region 才真正可用。
|
||||
4. **L4 全部** — 兜底层补齐,任意线性表达式可端到端生成。
|
||||
5. **L5.6–L5.9** — 跨行 / 跨 region 观察与提前退出,`a|b` 与 `(?<name>abc)` 自然跑通(嵌套形态)。
|
||||
**此时获得可运行基线,任何表达式都能生成正确代码。**
|
||||
6. **L1.5、L1.7** — 复杂计数与复杂搜索,无降级路径,必须实现。
|
||||
7. **L1.1–L1.4** — 把 ALT 链与捕获组的嵌套形态压平。
|
||||
8. **L3 全部** — 代码质量优化,可持续追加。
|
||||
|
||||
第一阶段验收口径(沿用原优先级):`MATCH_SEQ`、`MATCH_SET`、`COUNT_SEQ`、`COUNT_SET`、
|
||||
简单 `ALT`、`PACK`、`FIND_SEQ`、`FIND_COMPLEX COUNT:-1` 全部稳定。
|
||||
|
||||
低优先级,暂不纳入 checklist:
|
||||
|
||||
1. 逐 region 最短长度断言。
|
||||
2. 复杂字符集完全内联。
|
||||
@@ -132,3 +227,30 @@ IR -> Code 规则:
|
||||
5. `Observe_Region` 是否只读取目标 region 结果。
|
||||
6. `PACK` 使用哪一个成功结果作为捕获区间。
|
||||
7. 最小长度检查是否值得在当前样本中生成。
|
||||
8. 本形态消耗几条 IR,落空时降级到哪一层。
|
||||
9. 前缀的执行条件与观察源是否被正确外提,而不是硬编码进指令主体。
|
||||
|
||||
## 已手写样本索引
|
||||
|
||||
`test/SingleFile/regex.cs` 中已有的样本与层级对应:
|
||||
|
||||
| 函数 | 表达式 | 对应层级 |
|
||||
| --- | --- | --- |
|
||||
| `Match_ABC` | `abc` | L4.1 |
|
||||
| `Match_A_charset_ABC_C` | `a[abc]d` | L3.5 / L4.1 / L4.2 |
|
||||
| `Match_charset_ABC` | `[abc]` | L4.2 |
|
||||
| `Match_Dot` | `.` | L4.5 |
|
||||
| `Match_A_3_5` | `a{3,5}` | L3.4 |
|
||||
| `Match_AB_2_3` | `(ab){2,3}` | L1.5 |
|
||||
| `MatchDiffCase` | `(ab\|[cd]\|.)x` | L1.2 |
|
||||
| `MatchCountAnyGroup` | `(?<name>.*)x` | L3.1 |
|
||||
|
||||
待补:L2.1 `(abc)`、L1.1 `a\|b`、L1.3 `(?<name>abc)`、L5 各前缀组合的最小样本。
|
||||
其中 L5 的样本最关键:需要分别写出 `Prev_Success` / `Prev_Fail` 包裹、
|
||||
`Observe_Line` / `Observe_Region` 读取、`Pop_Success` / `Pop_Fail` 写回三组最小代码形状。
|
||||
|
||||
## 综合验收样本
|
||||
|
||||
`a*.[abc]*(?<a>abcd|a(bc)c)(?<name>.*)234`
|
||||
|
||||
覆盖 L1.1、L1.3、L1.4、L3.1、L4.6、L4.7,作为全链路收口用例,最后再手写。
|
||||
|
||||
Reference in New Issue
Block a user