ngra text regex will not use ir, source code will be generated directly from ast
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# IR 层退役说明
|
||||
|
||||
2026-08-12。本项目的正则源生成路线从「AST → IR → 优化 → 前缀化 → 源码」
|
||||
改为「AST → 树上规范化 → 直接递归生成源码」。
|
||||
所有 `*.cs.old` 文件是退役的 IR 层实现,保留作参考,不参与编译。
|
||||
|
||||
## 为什么不要 IR 了
|
||||
|
||||
一句话:**前缀 IR 的整套机制都在补偿线性化亲手制造的问题,而源生成端最后又把树形还原了回来**。压平再还原,两头各付一遍成本。
|
||||
|
||||
逐项对照,前缀 IR 的每个机制在树上的对应物:
|
||||
|
||||
| 前缀机制 | 存在原因 | 树上的对应物 |
|
||||
| --- | --- | --- |
|
||||
| `Prev_Fail` / `Prev_Success` | 压平后失败链丢失 | ALT 节点的 case 链,天生就是 if/else |
|
||||
| `Observe_Line` / `ObserveSource` | 平坦流里"读谁的结果"需显式编号 | 结构化控制流,结果就在作用域里 |
|
||||
| `Pop_xxx` + region id | `break` 要知道跳出哪个作用域 | 树的作用域嵌套本身 |
|
||||
| `RegionExtent` / 套山 | 切片找不到子表达式边界 | 子树边界白送 |
|
||||
|
||||
IR 窗口优化器(`ScanAndOptimizeIR`)为了在平坦序列上做局部重写,
|
||||
搭了 8 条滑窗、`IsFullyOptimized` 防重扫、`goto phase_zero` 重启、
|
||||
`CommandCount` 重算一整套脚手架——这些在树上是单节点替换。
|
||||
|
||||
历史讨论结论:.NET 官方 RegexGenerator 同样直接展开 `RegexNode` 树生成源码;
|
||||
线性字节码的正当用途是**解释器**(`#run stepping` 模式),不是源生成。
|
||||
IR 层代码因此归档而非删除:将来做 stepping 解释器时是字节码路线的起点。
|
||||
|
||||
## 需要在 AST 上补什么
|
||||
|
||||
### 基础件(不存在,先补)
|
||||
|
||||
1. **子树深拷贝**——`(ab){2,3}` 展开需要克隆 body;配合破坏性移动可省一份
|
||||
2. **合成节点工厂**——展开要凭空造 CONCAT/COUNT 节点,
|
||||
需要 `Token<RegexElementLabel>` 的合成构造入口(如 `Token.Synthetic(flag, value)`)
|
||||
3. **FIND 节点标签**——`RegexElementLabel` 新增 `FIND`,语义为"搜索连接"
|
||||
(替代 IR 的 `FIND_COMPLEX`,见下)
|
||||
|
||||
### 树上规范化 pass(`OptimizeTree` 扩到六阶段)
|
||||
|
||||
| # | Pass | 状态 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 0 | 清孤儿 | 已有 | 不变 |
|
||||
| 1 | `FlattenAlt` + `RotateConcat` | 已有 | 不变 |
|
||||
| 2 | **COUNT 展开族** | 待写 | `a+`→`CONCAT(a, a*)`、`a{1}`→`a`、`a{m,n}`→前缀展开+余数;产物是右脊 CONCAT,天然归一 |
|
||||
| 3 | `MergeAlt` | 已有 | 不变 |
|
||||
| 4 | `MergeConcat` | 已有 | 顺带吞掉 phase2 克隆出的相邻字面量 |
|
||||
| 5 | **`FirstAtom`/`LastAtom` 综合** | 待写 | 一趟自底向上;GROUP 透传、COUNT 不透传、ALT 不一致则 null。必须在所有结构重写之后、字面量合并之后(锚要极大) |
|
||||
| 6 | **FIND 融合** | 待写 | 后序遍历,`CONCAT(左.LastAtom==dot-star, 右.FirstAtom==字面量)` → 原地替换为 FIND 节点;覆盖有锚/穿 GROUP/无锚吞尾三种形态 |
|
||||
|
||||
FIND 节点语义契约(继承自 IR 版 FIND+PACK 协议):
|
||||
候选按优先级排序(FURTHEST 从远端往前退 / NEAREST 从近端往后进),
|
||||
右子树是候选验证器,候选耗尽才是真失败;
|
||||
GROUP 的捕获终点落在锚起点**之前**。
|
||||
|
||||
### 代码生成(按节点 label 一对一)
|
||||
|
||||
| 节点 | 形状 |
|
||||
| --- | --- |
|
||||
| `UNIT` / `UNIT_SPAN` | `StartsWith` + 推进 |
|
||||
| `UNIT_SET` | 列表模式 char 测试 `__rest is [var c, ..] && ...` |
|
||||
| `CONCAT` | 顺序发射,失败短路 |
|
||||
| `ALT` | case 链 → `if / else if / else`;复杂 case 落回滚作用域 |
|
||||
| `GROUP` | 进入存 span,成功写捕获槽(PACK 职责回归 GROUP) |
|
||||
| `COUNT`(叶) | 计数循环 / `IndexOfAnyExcept` 向量化 |
|
||||
| `COUNT`(复杂体) | 循环包递归 body |
|
||||
| `FIND` | 候选重试循环,验证器 = 右子树 |
|
||||
|
||||
生成约定(沿用已定结论):
|
||||
|
||||
- **破坏性遍历**:谁消费谁切,下游拿到无主子树根,物理上无法偷看上下文;
|
||||
展开族用移动代替克隆省一份拷贝;生成后 `TextRegexSource.Ast` 是残骸,置空或立 flag
|
||||
- **不变式契约**:进入时游标就位;离开时 `__ok` 必被赋值,
|
||||
成功则游标已推进、失败则游标复位
|
||||
- **剩余 span 写法**:游标是 `ReadOnlySpan<char> __rest`,绝对偏移按需反算
|
||||
- `do{}while(false)` + `break` 只在真需要回滚的节点出现(ALT 复杂 case、
|
||||
COUNT 复杂体、FIND 重试),不再每 region 强制
|
||||
- 变量命名与槽位由 `SourceGenContext` 独有(该类保留,删掉 `RegionExtent` 即可)
|
||||
|
||||
## 当前编译错误(预期内,待重写)
|
||||
|
||||
| 文件 | 错误原因 | 处置 |
|
||||
| --- | --- | --- |
|
||||
| `RegexAnalyzer.cs` | `TextRegexSource` 记录携带 `List<RegexIR>` 字段,`Analyze` 调 `GenerateIR`/`ScanAndOptimizeIR`/`EmitPrefix` | 砍掉 IR 字段与三个调用,管线止于 `OptimizeTree` |
|
||||
| `TextRegexSourceGen.GenClass.cs` | `GenerateSource` 读 `source.IRs`,`GenerateBlock`/`GenerateRegion`/`MeasureRegionExtents`/`FindRegionEnd` 全是 IR 机器 | 重写为读 `source.Ast` 的节点递归;保留 `FormatStringLiteral`/`FormatCharLiteral`/charset 解析 |
|
||||
|
||||
## `.old` 文件清单与参考价值
|
||||
|
||||
| 文件 | 参考价值 |
|
||||
| --- | --- |
|
||||
| `RegexIR.cs.old`、`RegexIR.Abstraction.cs.old`、`RegexIR.Prefix.cs.old`、`RegexIRGenerator.cs.old` | 字节码指令集定义,stepping 解释器的起点 |
|
||||
| `RegexAnalyzer.IRGenerate.cs.old` | GROUP→TRY/POP/PACK 协议、COUNT 源文本解析(min/max/greedy 解析逻辑树上重写时照抄) |
|
||||
| `RegexAnalyzer.IROptimize.cs.old` | 展开族与 FIND 合并的**语义定义**(树上 pass 的行为基准) |
|
||||
| `RegexAnalyzer.PrefixEmit.cs.old` | region/观察/pop 语义的原始出处 |
|
||||
| `TextRegexSourceGen.GenClass.SingleIR.cs.old` | L4 生成器 body(匹配逻辑可直接搬进节点生成器) |
|
||||
| `TextRegexSourceGen.GenClass.MultiIR.cs.old`、`.Prefix.cs.old` | L1/L5 引导文档,FIND 重试循环与 POP 冲突的解法记录在此 |
|
||||
|
||||
配套文档:`future/ngra-regex-sourcegen-goldens.md` 的目标代码形状**全部继续有效**
|
||||
(它记录的是生成产物的形状,与中间表示无关),
|
||||
`test/SingleFile/regex.cs` 的手写样本同样有效。
|
||||
`future/ngra-regex-ir.md` 的「优化策略」一节按旧认识写成,待按本文档修订。
|
||||
@@ -214,18 +214,18 @@ namespace StgSharp.RegularAnalysis.Text
|
||||
RegexAstNode b = left.Right;
|
||||
RegexAstNode c = root.Right;
|
||||
root.Left = a;
|
||||
if (RegexAstNode.IsNullOrEmpty(a)) {
|
||||
if (!RegexAstNode.IsNullOrEmpty(a)) {
|
||||
a.Parent = root;
|
||||
}
|
||||
|
||||
root.Right = left;
|
||||
left.Left = b;
|
||||
left.Right = c;
|
||||
if (RegexAstNode.IsNullOrEmpty(b)) {
|
||||
if (!RegexAstNode.IsNullOrEmpty(b)) {
|
||||
b.Parent = left;
|
||||
}
|
||||
|
||||
if (RegexAstNode.IsNullOrEmpty(c)) {
|
||||
if (!RegexAstNode.IsNullOrEmpty(c)) {
|
||||
c.Parent = left;
|
||||
}
|
||||
left = root.Left;
|
||||
|
||||
Reference in New Issue
Block a user