1051 lines
16 KiB
Markdown
1051 lines
16 KiB
Markdown
# NGRA 字符串正则 IR 设计补充建议
|
||
|
||
基于当前 `ngra-regex-ir.md` 的设计,我认为整体方向是成立的:它已经不是“完全低级的执行字节码”,而是保留了 `COUNT`、`COUNT_SEQ`、`COUNT_SET`、`TRY`、`PACK` 等高层结构,因此可以继续承担后续 match-opt 的输入。
|
||
|
||
不过,为了让规则更容易扩展、避免语义误判,我建议把优化明确拆成三类,并把当前 IR 分成“语义 IR / HIR”和“计划 IR / LIR”。
|
||
|
||
---
|
||
|
||
## 1. 总体判断
|
||
|
||
当前 IR 最大的优点:
|
||
|
||
1. **足够紧凑**:`COUNT_SEQ`、`COUNT_SET`、`TRY` 等复合指令可以避免过早打碎成 `LoadChar + Compare + Branch`。
|
||
2. **保留 regex 结构**:量词、字符集、确定序列、组打包等信息仍然可见。
|
||
3. **适合继续做 match planning**:后续可以从这些指令中识别 `.*literal`、`[set]*`、bounded repeat、固定长度 block 等结构。
|
||
|
||
当前 IR 最大的风险:
|
||
|
||
1. `SEARCH` 这个名字容易误导。当前 HIR 中的 `SEARCH` 实际应当是 **MATCH_AT_CURRENT**,不是允许跳过字符的 `FIND`。
|
||
2. 检查点目前只描述为锚点指针栈,但真正的 backtracking frame 至少还要保存 `pos`、capture watermark、symbol watermark、repeat state 和 continuation。
|
||
3. `COUNT_*` 的语义必须明确是否保留 retry/backtracking,否则 `a*.`、`[abc]*abc` 等场景会被错误优化。
|
||
4. 字符集的语义表示和物理表示要分开:语义上 `[0-9a-z]` 是一个 set,物理上可以用 inline set-region 的多条 range-test 表示,避免每个 set 挂 `List<RangePair>`。
|
||
|
||
---
|
||
|
||
## 2. 建议的分层
|
||
|
||
我建议仍然区分 HIR 和 LIR,不是因为机器必须分,而是因为人写规则时边界更清楚。
|
||
|
||
```text
|
||
AST
|
||
↓
|
||
AST Opt
|
||
↓
|
||
HIR Gen
|
||
↓
|
||
HIR Analysis
|
||
↓
|
||
HIR → LIR / MatchPlan Lowering
|
||
↓
|
||
LIR
|
||
↓
|
||
SourceGen / RuntimeGen / Interpreter
|
||
```
|
||
|
||
### HIR:表达 regex 语义
|
||
|
||
HIR 保留接近正则结构的指令:
|
||
|
||
```text
|
||
SEARCH_SEQ // 语义上是当前位置 MATCH_SEQ
|
||
SEARCH_SET // 语义上是当前位置 MATCH_SET
|
||
SEARCH_DOT // 或 UNIT_DOT,不应和 literal "." 混淆
|
||
COUNT
|
||
COUNT_SEQ
|
||
COUNT_SET
|
||
TRY
|
||
POP
|
||
PACK
|
||
```
|
||
|
||
注意:HIR 中可以继续叫 `SEARCH`,但文档里要明确:
|
||
|
||
```text
|
||
HIR 的 SEARCH_* 不表示向后查找;
|
||
它表示从当前活动指针开始匹配。
|
||
真正允许跳过字符的搜索操作只在 LIR / MatchPlan 阶段引入,命名为 FIND_*。
|
||
```
|
||
|
||
### LIR:表达匹配计划
|
||
|
||
LIR 不只是更低级,而是“已经做过策略规划”的 IR:
|
||
|
||
```text
|
||
MATCH_SEQ
|
||
MATCH_CHAR
|
||
MATCH_DOT
|
||
MATCH_SET
|
||
|
||
FIND_SEQ
|
||
FIND_CHAR
|
||
FIND_SET
|
||
FIND_LAST_SEQ
|
||
FIND_LAST_SET
|
||
|
||
CONSUME_WHILE_SET
|
||
CONSUME_UNTIL_CHAR
|
||
CONSUME_UNTIL_SET
|
||
|
||
COUNT_SET
|
||
COUNT_SEQ
|
||
COUNT_BLOCK
|
||
CHECK_MIN_LENGTH
|
||
CHECK_FIXED_OFFSET
|
||
|
||
PUSH_BACKTRACK
|
||
POP_BACKTRACK
|
||
COMMIT
|
||
FAIL
|
||
|
||
CAPTURE_BEGIN
|
||
CAPTURE_END
|
||
PACK
|
||
```
|
||
|
||
LIR 中应该已经包含:
|
||
|
||
```text
|
||
LoweredSetId
|
||
SearchMode
|
||
RepeatMode
|
||
RetryMode
|
||
CaptureMode
|
||
EmitHint
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 三类优化策略
|
||
|
||
建议把所有优化分成三类:
|
||
|
||
```text
|
||
1. Regex algebra rewrite
|
||
改正则结构。
|
||
|
||
2. Match plan rewrite
|
||
改匹配策略。
|
||
|
||
3. Backend / C# span trick
|
||
改 C# 代码写法。
|
||
```
|
||
|
||
这个分类很重要,因为它能直接决定优化应该放在哪个阶段。
|
||
|
||
---
|
||
|
||
# 4. Regex algebra rewrite
|
||
|
||
这类优化应该放在 AST Opt 或 HIR 生成前后。它们改变的是正则结构,而不是 C# 写法。
|
||
|
||
## 4.1 Literal 合并
|
||
|
||
```regex
|
||
a b c
|
||
```
|
||
|
||
变成:
|
||
|
||
```regex
|
||
abc
|
||
```
|
||
|
||
HIR:
|
||
|
||
```text
|
||
SEARCH_SEQ "abc"
|
||
```
|
||
|
||
不要生成三条 `SEARCH_CHAR`。
|
||
|
||
---
|
||
|
||
## 4.2 Concat / Alternation flatten
|
||
|
||
```regex
|
||
a(?:b(?:c))
|
||
```
|
||
|
||
变成:
|
||
|
||
```regex
|
||
abc
|
||
```
|
||
|
||
```regex
|
||
a|(?:b|c)
|
||
```
|
||
|
||
变成:
|
||
|
||
```regex
|
||
a|b|c
|
||
```
|
||
|
||
---
|
||
|
||
## 4.3 Alternation factoring / trie
|
||
|
||
这个你已经放在 AST Opt 做,位置是对的。
|
||
|
||
```regex
|
||
foobar|foobaz|foozip
|
||
```
|
||
|
||
变成:
|
||
|
||
```regex
|
||
foo(?:bar|baz|zip)
|
||
```
|
||
|
||
或者 trie AST:
|
||
|
||
```text
|
||
SEQ "foo"
|
||
ALT
|
||
SEQ "bar"
|
||
SEQ "baz"
|
||
SEQ "zip"
|
||
```
|
||
|
||
这类结构改写越早越好。后续 IR opt 只需要识别这个结构并决定是否 `switch` / first-set dispatch。
|
||
|
||
---
|
||
|
||
## 4.4 Bounded repeat 展开
|
||
|
||
```regex
|
||
a{2,3}
|
||
```
|
||
|
||
可以变成:
|
||
|
||
```regex
|
||
aa a?
|
||
```
|
||
|
||
或:
|
||
|
||
```text
|
||
SEARCH_SEQ "aa"
|
||
OPTIONAL SEARCH_CHAR 'a'
|
||
```
|
||
|
||
对于 lazy:
|
||
|
||
```regex
|
||
a{2,3}?b
|
||
```
|
||
|
||
要注意尝试顺序。它等价于:
|
||
|
||
```regex
|
||
aa(?:b|ab)
|
||
```
|
||
|
||
而不是:
|
||
|
||
```regex
|
||
aa(?:a|ab)
|
||
```
|
||
|
||
Greedy 版本:
|
||
|
||
```regex
|
||
a{2,3}b
|
||
```
|
||
|
||
如果展开成显式分支,尝试顺序更像:
|
||
|
||
```regex
|
||
aa(?:ab|b)
|
||
```
|
||
|
||
语言集合相同,但 backtracking 尝试顺序不同。
|
||
|
||
---
|
||
|
||
## 4.5 `a+b` 不建议改成 `a*(ab|b)`
|
||
|
||
当前文档中写:
|
||
|
||
```text
|
||
a+b 可以优化为 a*(ab|b)
|
||
```
|
||
|
||
这个需要修正。
|
||
|
||
`a+b` 的语言是:
|
||
|
||
```text
|
||
a^n b, n >= 1
|
||
```
|
||
|
||
而 `a*(ab|b)` 会额外匹配:
|
||
|
||
```text
|
||
b
|
||
```
|
||
|
||
因为第二个分支 `a*b` 可以在 `a*` 取 0 时匹配 `b`。
|
||
|
||
更安全的结构是:
|
||
|
||
```regex
|
||
aa*b
|
||
```
|
||
|
||
或者:
|
||
|
||
```text
|
||
SEARCH_CHAR 'a'
|
||
COUNT_SEQ 'a' 0..∞
|
||
SEARCH_CHAR 'b'
|
||
```
|
||
|
||
如果要利用后继 `b` 做策略优化,应在 MatchPlan 阶段处理,而不是做这个不等价的 algebra rewrite。
|
||
|
||
---
|
||
|
||
## 4.6 Charset 合并
|
||
|
||
```regex
|
||
[abc]|[def]
|
||
```
|
||
|
||
可以变成:
|
||
|
||
```regex
|
||
[abcdef]
|
||
```
|
||
|
||
```regex
|
||
[a-f]|[d-z]
|
||
```
|
||
|
||
可以变成:
|
||
|
||
```regex
|
||
[a-z]
|
||
```
|
||
|
||
这属于 AST/HIR 层的字符类代数。
|
||
|
||
---
|
||
|
||
## 4.7 Charset 展开为 alternation 要谨慎
|
||
|
||
文档中写:
|
||
|
||
```text
|
||
abc[de] 可以展开为 (abcd|abce)
|
||
```
|
||
|
||
这个是可行的,但不一定总是好。建议规则改成:
|
||
|
||
```text
|
||
当 charset 很小,并且展开后能形成更长 literal / trie / branch dispatch 时,可以展开;
|
||
否则保留为 MATCH_SET。
|
||
```
|
||
|
||
例如:
|
||
|
||
```regex
|
||
abc[de]
|
||
```
|
||
|
||
既可以是:
|
||
|
||
```text
|
||
SEARCH_SEQ "abc"
|
||
SEARCH_SET [de]
|
||
```
|
||
|
||
也可以是:
|
||
|
||
```text
|
||
ALT
|
||
SEARCH_SEQ "abcd"
|
||
SEARCH_SEQ "abce"
|
||
```
|
||
|
||
前者代码更短,后者可能更适合 required literal 或 trie。需要 code size budget。
|
||
|
||
---
|
||
|
||
# 5. Match plan rewrite
|
||
|
||
这类优化应该放在 HIR Analysis / HIR → LIR lowering。
|
||
|
||
它们不一定改成另一条正则,而是把匹配过程改成更高效的搜索/消费计划。
|
||
|
||
---
|
||
|
||
## 5.1 长度预测应该在 HIR Analysis 做,SourceGen 只负责发代码
|
||
|
||
当前文档写“长度预测在 source-gen 阶段做”。我建议改成:
|
||
|
||
```text
|
||
minLength / maxLength 的计算在 HIR Analysis 做;
|
||
SourceGen 只负责把 CHECK_MIN_LENGTH 发成 C#。
|
||
```
|
||
|
||
例如:
|
||
|
||
```regex
|
||
abc[0-9]{2}xyz
|
||
```
|
||
|
||
分析结果:
|
||
|
||
```text
|
||
MinLength = 8
|
||
```
|
||
|
||
LIR:
|
||
|
||
```text
|
||
CHECK_MIN_LENGTH 8
|
||
MATCH_SEQ "abc"
|
||
...
|
||
```
|
||
|
||
SourceGen:
|
||
|
||
```csharp
|
||
if ((uint)(end - pos) < 8) goto Fail;
|
||
```
|
||
|
||
---
|
||
|
||
## 5.2 `.*` 优化
|
||
|
||
HIR:
|
||
|
||
```text
|
||
COUNT_DOT min=0 max=∞ greedy=true
|
||
MATCH_CHAR 'a'
|
||
```
|
||
|
||
可以 lower 成:
|
||
|
||
```text
|
||
FIND_LAST_CHAR 'a'
|
||
```
|
||
|
||
lazy:
|
||
|
||
```text
|
||
COUNT_DOT min=0 max=∞ greedy=false
|
||
MATCH_CHAR 'a'
|
||
```
|
||
|
||
lower 成:
|
||
|
||
```text
|
||
FIND_CHAR 'a'
|
||
```
|
||
|
||
但要注意:
|
||
|
||
1. 默认 `.` 不匹配 `\n`,搜索范围应限制在当前行。
|
||
2. Singleline 模式下才可以跨整个 span。
|
||
3. 如果 `.*` 内部或后继结构涉及 capture/backtracking,不能简单“删掉 COUNT”,而应转换为带 retry 的 find plan。
|
||
4. 后继如果是 `MATCH_SEQ "abc"`,则变成 `FIND_SEQ` / `FIND_LAST_SEQ`。
|
||
5. 后继如果是 `MATCH_SET`,则变成 `FIND_SET` / `FIND_LAST_SET`。
|
||
|
||
---
|
||
|
||
## 5.3 `[set]*` 优化
|
||
|
||
文档中写 `[set]*` 优化在 source-gen 阶段做。建议改成:
|
||
|
||
```text
|
||
是否将 COUNT_SET 变成 CONSUME_WHILE / CONSUME_UNTIL,在 lowering 做;
|
||
具体使用 IndexOfAnyExcept / SearchValues / manual loop,在 SourceGen 做。
|
||
```
|
||
|
||
例如:
|
||
|
||
```regex
|
||
[a-f0-9]*
|
||
```
|
||
|
||
LIR:
|
||
|
||
```text
|
||
CONSUME_WHILE_SET LowerHex
|
||
```
|
||
|
||
SourceGen:
|
||
|
||
```csharp
|
||
int n = span.IndexOfAnyExcept(s_lowerHex);
|
||
```
|
||
|
||
或者手写 loop / range compare。
|
||
|
||
---
|
||
|
||
## 5.4 First-set 搜索
|
||
|
||
如果非锚定匹配:
|
||
|
||
```regex
|
||
abc|defg|xyz
|
||
```
|
||
|
||
first-set 是:
|
||
|
||
```text
|
||
'a', 'd', 'x'
|
||
```
|
||
|
||
LIR 可以是:
|
||
|
||
```text
|
||
FIND_SET { 'a', 'd', 'x' }
|
||
VERIFY_BRANCH
|
||
```
|
||
|
||
你文档里说“这个不好做,因为 IR 直接把 ALT 打碎成 if-else 串了”。这说明 AST Opt / HIR 里最好保留 ALT region 或者至少保留 branch summary。
|
||
|
||
即使 AST 已经 trie 化,IR 也应该尽量不要把它完全打碎成不可恢复的 if-else 串。至少给每个 region 计算 summary:
|
||
|
||
```text
|
||
FirstSet
|
||
MinLength
|
||
RequiredLiteral
|
||
CanBacktrack
|
||
CanCapture
|
||
```
|
||
|
||
---
|
||
|
||
## 5.5 Required literal 搜索
|
||
|
||
例如:
|
||
|
||
```regex
|
||
.*ERROR:[0-9]+
|
||
```
|
||
|
||
可以直接规划:
|
||
|
||
```text
|
||
FIND_SEQ "ERROR:"
|
||
VERIFY_AFTER_ANCHOR
|
||
```
|
||
|
||
这通常比只做 first-set 更强。
|
||
|
||
---
|
||
|
||
## 5.6 Fixed-offset check
|
||
|
||
```regex
|
||
[a-z]{20}XYZ
|
||
```
|
||
|
||
可以 lower 成:
|
||
|
||
```text
|
||
CHECK_MIN_LENGTH 23
|
||
CHECK_AT_OFFSET 20 MATCH_SEQ "XYZ"
|
||
MATCH_SET_REPEAT [a-z] count=20
|
||
MATCH_SEQ "XYZ"
|
||
```
|
||
|
||
这个属于 match plan,不属于 sourcegen。
|
||
|
||
---
|
||
|
||
## 5.7 Auto-atomic / possessive 化
|
||
|
||
```regex
|
||
[0-9]*[A-Z]
|
||
```
|
||
|
||
由于:
|
||
|
||
```text
|
||
[0-9] ∩ [A-Z] = ∅
|
||
```
|
||
|
||
所以 `[0-9]*` 不需要回退,可以 lower 成:
|
||
|
||
```text
|
||
COUNT_SET [0-9] greedy atomic
|
||
MATCH_SET [A-Z]
|
||
```
|
||
|
||
如果后继 first-set 和 repeat set 有交集,就不能这么做。
|
||
|
||
---
|
||
|
||
## 5.8 Repeat retry mode
|
||
|
||
LIR 应该明确 repeat 的 retry 策略:
|
||
|
||
```text
|
||
RetryMode.None
|
||
RetryMode.BackoffBy1
|
||
RetryMode.BackoffByStride
|
||
RetryMode.SavedPositions
|
||
RetryMode.GeneralBacktrack
|
||
```
|
||
|
||
例如:
|
||
|
||
```regex
|
||
(abc[cd])+
|
||
```
|
||
|
||
每轮固定长度 4,所以可以:
|
||
|
||
```text
|
||
RetryMode.BackoffByStride stride=4
|
||
```
|
||
|
||
不需要保存每个内部指令的回溯点。
|
||
|
||
---
|
||
|
||
# 6. Backend / C# span trick
|
||
|
||
这类优化只放 SourceGen。它不再改变 regex 或 match plan。
|
||
|
||
---
|
||
|
||
## 6.1 `StartsWith`
|
||
|
||
LIR:
|
||
|
||
```text
|
||
MATCH_SEQ "sample"
|
||
```
|
||
|
||
SourceGen 决定:
|
||
|
||
```csharp
|
||
slice.StartsWith("sample", StringComparison.Ordinal)
|
||
```
|
||
|
||
还是短串展开:
|
||
|
||
```csharp
|
||
slice.Length >= 3 &&
|
||
slice[0] == 'a' &&
|
||
slice[1] == 'b' &&
|
||
slice[2] == 'c'
|
||
```
|
||
|
||
建议规则:
|
||
|
||
```text
|
||
长度 1:
|
||
char compare
|
||
|
||
长度 2~3:
|
||
直接 char compare
|
||
|
||
中长 literal:
|
||
StartsWith
|
||
|
||
IgnoreCase:
|
||
StartsWith(..., OrdinalIgnoreCase)
|
||
```
|
||
|
||
---
|
||
|
||
## 6.2 `[0-9]` 不建议写成 `IndexOf("0123456789")` 用于当前位置测试
|
||
|
||
文档中写:
|
||
|
||
```text
|
||
[0-9] 展开为 IndexOf("0123456789")
|
||
```
|
||
|
||
这个要分两种场景:
|
||
|
||
### 当前位置匹配
|
||
|
||
如果是:
|
||
|
||
```text
|
||
MATCH_SET [0-9]
|
||
```
|
||
|
||
更好是:
|
||
|
||
```csharp
|
||
(uint)(ch - '0') <= 9
|
||
```
|
||
|
||
### span 中搜索
|
||
|
||
如果是:
|
||
|
||
```text
|
||
FIND_SET [0-9]
|
||
```
|
||
|
||
可以考虑:
|
||
|
||
```csharp
|
||
span.IndexOfAnyInRange('0', '9')
|
||
```
|
||
|
||
或:
|
||
|
||
```csharp
|
||
span.IndexOfAny(s_digits)
|
||
```
|
||
|
||
所以规则应写成:
|
||
|
||
```text
|
||
MATCH_SET [0-9] → unsigned range compare
|
||
FIND_SET [0-9] → IndexOfAnyInRange / SearchValues
|
||
```
|
||
|
||
---
|
||
|
||
## 6.3 SearchValues
|
||
|
||
对于多字符集合:
|
||
|
||
```text
|
||
FIND_SET [0-9a-f]
|
||
```
|
||
|
||
SourceGen 可生成:
|
||
|
||
```csharp
|
||
private static readonly SearchValues<char> s_lowerHex =
|
||
SearchValues.Create("0123456789abcdef");
|
||
```
|
||
|
||
然后:
|
||
|
||
```csharp
|
||
span.IndexOfAny(s_lowerHex);
|
||
```
|
||
|
||
当前位置测试不一定要用 `SearchValues`,可能 range compare 或 bitmap 更快。
|
||
|
||
---
|
||
|
||
## 6.4 Inline vs helper
|
||
|
||
复杂 Unicode set:
|
||
|
||
```text
|
||
MATCH_SET UnicodeWord
|
||
```
|
||
|
||
SourceGen 决定:
|
||
|
||
```csharp
|
||
if (!IsUnicodeWord(ch)) goto Fail;
|
||
```
|
||
|
||
还是:
|
||
|
||
```csharp
|
||
if (ch < 128)
|
||
{
|
||
if (!AsciiWordContains(ch)) goto Fail;
|
||
}
|
||
else
|
||
{
|
||
if (!IsUnicodeWord(ch)) goto Fail;
|
||
}
|
||
```
|
||
|
||
这个应由代码尺寸、热度、是否 source-gen 决定。
|
||
|
||
---
|
||
|
||
## 6.5 Code size budget
|
||
|
||
SourceGen 应负责最终代码膨胀控制:
|
||
|
||
```text
|
||
单方法最大语句数
|
||
literal 展开阈值
|
||
bounded repeat 展开阈值
|
||
是否生成 helper
|
||
是否抽 cold path
|
||
SearchValues 字段数量
|
||
```
|
||
|
||
---
|
||
|
||
# 7. 字符集的物理表示建议
|
||
|
||
为了避免每个 set 挂 `List<RangePair>`,可以用线性 IR region 表示同一个字符测试。
|
||
|
||
例如语义:
|
||
|
||
```regex
|
||
[0-9a-z]
|
||
```
|
||
|
||
物理 IR:
|
||
|
||
```text
|
||
MATCH_SET_BEGIN default=false end=13
|
||
TEST_RANGE '0' '9' accept=true
|
||
TEST_RANGE 'a' 'z' accept=true
|
||
MATCH_SET_END
|
||
```
|
||
|
||
语义:
|
||
|
||
```regex
|
||
[^0-9a-z]
|
||
```
|
||
|
||
物理 IR:
|
||
|
||
```text
|
||
MATCH_SET_BEGIN default=true end=13
|
||
TEST_RANGE '0' '9' accept=false
|
||
TEST_RANGE 'a' 'z' accept=false
|
||
MATCH_SET_END
|
||
```
|
||
|
||
要点是:
|
||
|
||
```text
|
||
这些 TEST_RANGE 测的是同一个字符;
|
||
整个 MATCH_SET 只推进 pos 一次。
|
||
```
|
||
|
||
不能把它误解成连续匹配多个字符。
|
||
|
||
复杂 Unicode set 可以走:
|
||
|
||
```text
|
||
MATCH_SET_REF setId=42
|
||
```
|
||
|
||
`setId=42` 指向全局 pooled table,而不是每个节点一个 List。
|
||
|
||
---
|
||
|
||
# 8. 检查点语义建议
|
||
|
||
当前文档把检查点描述为锚点指针栈,这对 capture 起点有用,但不够覆盖 backtracking。
|
||
|
||
建议把 checkpoint / backtrack frame 定义成:
|
||
|
||
```text
|
||
BacktrackFrame:
|
||
pos
|
||
anchor
|
||
captureTop
|
||
symbolTop
|
||
repeatState
|
||
continuation
|
||
```
|
||
|
||
其中:
|
||
|
||
```text
|
||
pos:
|
||
活动指针恢复位置
|
||
|
||
anchor:
|
||
区间开始位置
|
||
|
||
captureTop:
|
||
捕获记录栈回滚点
|
||
|
||
symbolTop:
|
||
符号区回滚点
|
||
|
||
repeatState:
|
||
当前 COUNT/loop 的计数、起止位置、retry mode
|
||
|
||
continuation:
|
||
失败后跳到哪里继续尝试
|
||
```
|
||
|
||
如果暂时是 tokenizer / no-capture 模式,可以裁掉 captureTop,但 IR 语义最好预留。
|
||
|
||
---
|
||
|
||
# 9. 建议修改原文的几个点
|
||
|
||
## 9.1 “HIT” 应改为 “HIR”
|
||
|
||
原文:
|
||
|
||
```text
|
||
将HIT进行进一步优化
|
||
```
|
||
|
||
应为:
|
||
|
||
```text
|
||
将 HIR 进行进一步优化
|
||
```
|
||
|
||
## 9.2 `SEARCH` 的定义应改写
|
||
|
||
原文说“向后搜索确定长度序列”。建议改为:
|
||
|
||
```text
|
||
HIR 中 SEARCH_* 表示从当前活动指针开始匹配;
|
||
它不是允许跳过字符的 FIND。
|
||
真正的 FIND_* 在 HIR→LIR 阶段由 match planner 引入。
|
||
```
|
||
|
||
## 9.3 `a+b → a*(ab|b)` 不等价
|
||
|
||
建议删除或改成:
|
||
|
||
```regex
|
||
a+b → aa*b
|
||
```
|
||
|
||
或者:
|
||
|
||
```text
|
||
MATCH_CHAR 'a'
|
||
COUNT_SEQ 'a' 0..∞
|
||
MATCH_CHAR 'b'
|
||
```
|
||
|
||
## 9.4 `[0-9] → IndexOf("0123456789")` 应细分
|
||
|
||
改成:
|
||
|
||
```text
|
||
当前位置 MATCH_SET:
|
||
用 unsigned range compare。
|
||
|
||
span 搜索 FIND_SET:
|
||
用 IndexOfAnyInRange / SearchValues / IndexOfAny。
|
||
```
|
||
|
||
## 9.5 `.*` 优化不要说 “COUNT 消失”
|
||
|
||
建议改成:
|
||
|
||
```text
|
||
COUNT_DOT 与后继匹配项合并为 FIND / FIND_LAST 计划。
|
||
```
|
||
|
||
这样更准确,因为有时还需要保留 retry / range 限制 / newline 限制。
|
||
|
||
---
|
||
|
||
# 10. 推荐最终管线
|
||
|
||
```text
|
||
Tokenize
|
||
- 区分 Dot 与 EscapedLiteralDot
|
||
- 区分 raw charset / literal / quantifier / group
|
||
|
||
AST Build
|
||
- 建立 regex 语义树
|
||
|
||
AST Opt
|
||
- literal merge
|
||
- concat flatten
|
||
- alternation factoring / trie
|
||
- bounded repeat normalization
|
||
- simple group elimination
|
||
- charset algebra merge
|
||
|
||
HIR Gen
|
||
- SEARCH_* as MATCH_AT_CURRENT
|
||
- COUNT_*
|
||
- TRY / POP
|
||
- PACK
|
||
- raw set / raw dot
|
||
|
||
HIR Analysis
|
||
- minLength / maxLength
|
||
- nullable
|
||
- firstSet
|
||
- lastSet
|
||
- required literal
|
||
- fixed length
|
||
- capture effect
|
||
- repeat overlap
|
||
|
||
HIR → LIR / MatchPlan
|
||
- raw set → lowered set
|
||
- dot → dot mode
|
||
- introduce FIND
|
||
- introduce CONSUME
|
||
- choose retry mode
|
||
- choose atomic/possessive
|
||
- emit check-min-length / fixed-offset checks
|
||
|
||
LIR
|
||
- explicit MATCH / FIND / CONSUME
|
||
- explicit lowered set
|
||
- explicit backtracking plan
|
||
- explicit capture plan
|
||
|
||
SourceGen
|
||
- StartsWith / SequenceEqual
|
||
- IndexOf / IndexOfAny / IndexOfAnyExcept
|
||
- SearchValues
|
||
- range compare / bitmap
|
||
- inline/helper split
|
||
- code size budget
|
||
```
|
||
|
||
---
|
||
|
||
# 11. 总结
|
||
|
||
我建议保留你当前 IR 的核心设计,但把文档里的边界收紧:
|
||
|
||
```text
|
||
AST Opt:
|
||
做 Regex algebra rewrite。
|
||
|
||
HIR:
|
||
表示从当前位置开始的 regex 语义。
|
||
|
||
HIR Analysis:
|
||
提取结构属性。
|
||
|
||
HIR → LIR:
|
||
做 Match plan rewrite。
|
||
|
||
LIR:
|
||
表示明确的匹配计划。
|
||
|
||
SourceGen:
|
||
做 C# span trick。
|
||
```
|
||
|
||
最重要的改动是:
|
||
|
||
```text
|
||
SEARCH 在 HIR 中应被定义为 MATCH_AT_CURRENT;
|
||
FIND 只能由后续优化阶段引入。
|
||
```
|
||
|
||
以及:
|
||
|
||
```text
|
||
字符集可以用线性 set-region 表示,避免 List 分配;
|
||
但 set-region 内的多条 range-test 必须测试同一个字符,不能变成多个连续 MATCH。
|
||
```
|
||
|
||
这样你的 IR 会同时满足三个目标:
|
||
|
||
1. 对人来说可读;
|
||
2. 对优化规则来说容易扩展;
|
||
3. 对 GC 和 sourcegen 来说物理表示足够紧凑。
|