docs/current/07-质量与裁定/20260919_S2词法器执行记录.md
GitHub ↗
当前有效

S2 词法器执行记录(vitro/engine/lexer)

4021 字·约 11 分钟 阅读 2026-10-10 17:07

执行:2026-09-19 | 上位文档:MoonBit迁移总计划.md §10 S2 行 范围:vitro/engine/lexer(独立预处理 pass + LineMap + 宿主 IO)——验收锚 = L1/L2 token TSV + 随机词法差分 ≥2000(.mbtx 语料生成器)+ 发布。 状态:✅ 收官——vitro/engine@0.2.0 上架 mooncakes(含 lexer 包);随机 2400 例 + 真实语料 444 例(baseline 363 + K&R 81)L1/L2 双层差分逐字节一致。

1. 交付物清单

组件 位置 说明
facade 包 moonbit/lexer/ tokenize / tokenize_raw / tokenize_with / tokenize_with_vfs + TSV emitter(dump_raw_tsv / dump_lex_tsv)+ README 文档测试
公共类型包 moonbit/lexer/token/ TokenType 116 变体(命名与 Rust Debug 逐字节对齐)/ Token(byte_off)/ LexResult(诊断单值同返)/ LineMap / SourceProvider(open trait) / SourceError
词法核心 moonbit/lexer/internal/scanner/ Bytes 扫描器(列 = Unicode 标量计数复刻 / byte_off 直记 / 主循环 while 重派发);number 全进制值域分派(UInt64 手写累积);string/char 转义(P4 后口径)
预处理 pass moonbit/lexer/internal/pp/ 行拼接(\ 续行)→ 注释剥离(字符串感知、跨逻辑行)→ 指令消费(显式 include 栈 + 条件栈边界)→ 宏展开(双保险丝 + W1018/W1019 + 蓝漆 + H01 包装);cond 完整 C 常量表达式求值器(Int64 wrapping + 短路归一 + 深度保险丝 128)
宿主 IO moonbit/lexer/internal/host/ StubsProvider(默认,自定义 include 显式 UnsupportedHost)/ VfsProvider(内存文件表);14 存根(gen_stubs 生成,sha 落款,--check 幂等)
Rust 出口 native/crates/vitro_lexer + vitro_cli dump-tokens tokenize_raw(L1)+ byte_off 记账字段(防线维护,生产零语义变化)
比对驱动 scripts/lexer_diff/(Go) 三语料编排 + 逐文件逐字节 diff + fail loud + --selftest J9 埋雷
语料生成器 moonbit/scripts/gen_corpus.mbtx xorshift64* 确定性(seed 20260919);生成空间 = 两侧语义一致空间(已知差异形状不生成)
开发工具 moonbit/cmd/dump_tokens/(native) MoonBit 侧批量 TSV 出口(Vfs 注入对齐 Rust 真实 fs 语义)

2. 验收数字(对真值)

  • 随机差分:2400 例(gen_corpus.mbtx,seed 20260919,双次运行逐字节确定性验证)× L1+L2 = 4800 TSV 逐字节一致——超额完成总计划"≥2000"锚(20%)。
  • 真实语料:baseline 363 例(726 TSV)+ K&R 81 例(162 TSV)逐字节一致——含自定义头文件、嵌套子目录 include(e2_has_include_nest)、__has_include 链、参数化宏全族。
  • 单测:MoonBit workspace 105/105(lexer 包 43:行为 + U1#6/U1#11 红锚族 + README 文档测试)。
  • J9:lexer_diff --selftest 注入单字节差异→驱动红(exit 1);facts 新键埋雷(FAIL 输出禁采)随 go test ./scripts/facts 留痕。
  • facts 键:moonbit_lexer_diff_tsv(三语料 TSV 总数,--run 采集,audit 区间 5000~8000)。

3. 差分过程中修掉的移植缺陷(红→绿留痕)

  1. 实参捕获数组别名:args.push(current); current.clear() 引用语义清空已存实参——MAX(1,2) 展开双参同值(diff g000002 抓出)。
  2. 存根 include 行号公式:L − N − 2 + k 误套哨兵公式——存根路径 Rust 无哨兵包装,正确为 L − N + k(负行号是 oracle 行号补偿缺陷的忠实复刻)。
  3. u8 前缀列号基准:Rust 的 string_literal() 起点在引号处(不含 u8)而 byte_off 记账在 u8 起点——拆分 span_start 双基准。
  4. string/char 列号基准:Rust make_token(ty, &源切片) 用源切片长度反推列后才覆盖 text 为解码值——复刻源切片基准(value 与源拼写不等长时两口径分叉)。
  5. gen_stubs %q 双重转义:已转义文本经 %q 再转义,存根全部挤成一行 + 30 条 E1001——%s 修复。

4. 已知差异清单(快照——单一来源为 scripts/lexer_diff/known_diffs.json)

2026-09-19 第二轮审阅后:本表降级为快照,差异项增删以 JSON 为准(gen_corpus.mbtx 的禁串自检消费它;下表已按 JSON 同步扩围 D1 与补 D14-D17)。

对拍空间排除以下形状(MoonBit 侧已按目标架构修复,Rust oracle 保持缺陷行为;差异进入 S8 差异台账前的登记):

# 形状 Rust 行为 MoonBit 行为 覆盖锚
D1 多行宏 \ 续行 3×E1001("无法识别的字符 ''") 行拼接 pass 支持(⑥-4 修复) tokenize multiline macro continuation
D2 #if 位运算/三目/~ E1014 求值失败 完整常量表达式求值器(⑥-6 修复) tokenize if bitwise and ternary
D3 #if (2 || 0) == 1 短路不归一 → 选错分支 归一 0/1(⑥-3 修复) tokenize or normalization
D4 #elifdef / #elifndef 静默跳过 → 选错分支零诊断 正确实现(⑥-11 修复) tokenize elifdef real
D5 未知指令 / #error / #warning 静默跳过 显式 E1006 诊断(⑥-11/12 修复;#warning 的 W 级 severity 待码表扩容) tokenize unknown directive diagnosed
D6 零参函数式宏 FOO() 调用 误当对象宏 → 42 ( ) 函数式展开 42(⑥-9 修复) tokenize zero-param function macro
D7 宏 arity 不匹配 静默保留 → E3023 错位 预处理显式诊断 + fail-soft 保留(⑥-10 修复;L2 token 流与 Rust 一致,仅多诊断) tokenize macro arity mismatch diagnosed
D8 BOM(L2 管线) E1001 + Unknown 剥离(⑥-8 修复;L1 不剥保持可比) —(语料不产 BOM)
D9 行中 stray # 当指令消费整行(token 丢失) E1001 + Unknown token —(语料不产行中 #)
D10 E1013 未闭合组定位 EOF 行 开启行(⑥-23 修复) tokenize unmatched endif and unclosed if
D11 环检测菱形漏报(M2) visited 不回溯漏报环 路径局部 visited u11 include cycle 20 files
D12 ## 拼接产物含 # # 被当指令消费 E1001 → E1016 fail-soft —(病态形状)
D13 i64::MIN / -1 等求值边界 Rust panic 回绕值不崩 cond.mbt 注释

故意复刻的 oracle 缺陷(换 L1/L2 可比性,非差异):列号反推 column − text 的 UTF-8 字节数(⑥-1/⑥-2)、宏展开位置钉调用点、include 行号补偿(负行号)。无缺陷坐标由 Token.byte_off / loc.file_id / LineMap 并行提供。

5. 决策登记(与勘察报告的偏差)

  1. TokenType 维持 flat 116 变体(勘察 ⑤-4 曾建议分层):L1/L2 差分锚要求 to_name 与 Rust Debug 逐字节一致,flat 消除映射噪声;diag 137 臂先例已证工程可行。
  2. 公共类型独立包 lexer/token:MoonBit internal 包不可被 facade 外导入且 facade↔internal 不得成环(实证:cmd/dump_tokens 导 host 被 internal 可见性规则拒绝)——勘察 ⑨ 的 internal/{source,pp,host} 三包调整为 token(公共)+ internal/{scanner,pp,host}。
  3. StubsFirstProvider 删除:trait 不能作字段类型(语言约束)——存根优先逻辑单源在 pp/resolver。
  4. SourceProvider 为 pub(open) trait:默认 readonly trait 跨包不可 impl。
  5. substring deprecated warning 留置 23 处:本安装 core 的切片形态(str[:])返回 StringView 且 raise,与 String 类型系统摩擦;substring 是可用形态,留置登记(非阻塞)。
  6. 语料生成器为 .mbtx(总计划锚点名要求)——但受"front-matter import 无法解析本地 module 包"限制,生成器不 import lexer 包(纯生成逻辑 + x/fs 落盘),词法侧批量出口用 workspace 内 main 包(cmd/dump_tokens)。
  7. rust 侧改动面(防线维护):Lexer 加 2 个 pub(crate) 记账字段(byte_pos / last_start_byte)+ advance 一行 + next_token 一行 + tokenize_raw 方法 + CLI 子命令——生产路径零语义变化(tokenize/tokenize 行为不变,全部既有测试通过)。

6. 遗留

  • L3 诊断层对拍(错误消息逐字比对)未纳入本片锚点(验收锚 = L1/L2)——L2 已比 errors/warnings 计数;消息文案已照搬 Rust(W1018/W1019/E1017/E1016/E1021 等),逐字对拍随 S3 parser 片的 E1-E4 管道统一建。
  • #warning 的 W 级 severity 精确化:需 diag 码表加 W 码(enum 加变体对下游穷尽 match 是破坏性变更,随 diag minor 版本单独裁定)。
  • 差分驱动未进 CI(本轮全绿基线已留痕;CI 接线随 S3 片的 E1 全量管道统一建)。

7. 第二轮审阅处置(2026-09-19,用户深度审阅与探针测试)

审阅挑出 P1×5 / P2×4 / P3×4 + 系统性发现,全部亲证后处置(每条红→绿留痕):

项 发现 处置
P1-1 非 ASCII 续行 drop_last_char 按 UTF-16 长度切 UTF-8 字节 → decode 抛错兜底空串 → 整行前缀静默丢失(中文注释续行把注释吞成代码) 改按字节长度切 + 兜底返回原串;红锚 continuation splice keeps non-ascii prefix(ascii/cjk 同形状同结果)
P1-2 0.2.0 包混入 scripts/_build/ 构建产物 176KB(moon publish 打包整个 module 且不读 .gitignore;.mbtx 在 module 内运行即产生) gen_corpus.mbtx 移出 module(scripts/lexer_diff/)+ 删 _build/.mooncakes + scripts/README 登记因果链;0.3.0 重发布;方法论教训登记:包内容须白名单口径抽查(工作区 vs 包一致的垃圾 sha 比对抓不到)
P1-4 三目真分支无条件求值(与自身"惰性"注释矛盾)——0 ? 1/0 : 1 误报除零 双分支各自结构跳过;红锚 ternary lazy both branches
P1-5 超 i64 值域常量 UInt64 位型重解释 → #if ULLONG_MAX > 0 得 -1 > 0 = 假(零诊断静默错值) 显式拒(E1014,Rust 口径);vs Clang 的 u64 容纳登记 D14(clang-gap);红锚 cond int64 overflow rejected loudly
P1-6 LineMap 恒缺主文件段且 lookup 全错(探针实测 lookup(1/2/3) 全部 (1,2));真实归属能力 README 宣传但零测试 Token.real_line(文件内真实行号,权威通道——0.3.0 破坏性加字段)+ 主文件铺底 span + lookup 尾向·命中优先;红锚 linemap and real line attribution
P2 W1018 跨型判据(FOO 1 后 FOO() 1 误报遮蔽——Rust 无分型视角 params 同空应静默) 判据复刻 Rust 无分型口径;红锚 w1018 cross-shape redefinition silent
P2 未闭合字符串诊断 1 vs 2 条(行级架构丢 E1003) scanner 行模式携带"行后有换行"→ 补 E1003+E1002 双诊断;与 Rust 逐字节一致(errors=2 同);红锚 unterminated string two diagnostics
P2 CRLF 源字符串值丢 `
`(行剥离吞掉,Rust 不剥) split 保留 `
;红锚 crlf string value keeps carriage return`;差分逐字节一致
P2 D1 登记范围过窄(任意 \ 续行都不同,非仅多行宏) known_diffs.json D1 扩围登记
P2 诊断 column 恒 0 vs Rust 真实列 登记 D16(l3-surface)——L3 面差分不比,随 S3 诊断对拍统一处置
P3 moon check 实报 24 warnings 只登记 23(漏 Set::new() deprecated);walk_cycle 512 封顶静默;tokenize_raw 留副作用;10 处 catch { _ => "" } 兜底 Set 改 Set([]);walk_cycle 注释登记(对拍口径,随 S8 台账裁定);tokenize_raw 恢复保存的模式位(Rust 侧);热路径兜底注释登记不可达理由(P1-1 的教训:兜底值必须是"保守但可见"的——空串曾掩盖真实缺陷)
系统性 差异清单不完备:多处注释自称"登记"而清单无对应项(ULLONG 口径、truncate 边界),清单无法作为语料排除输入 scripts/lexer_diff/known_diffs.json 单一来源(14 diffs + 3 legacy + 8 resolved,四分类);gen_corpus 产物按 forbidden_markers 自检 fail loud;本文档 §4 表格降级为快照、以 JSON 为准

审阅者上轮误判的修正(诚实记录):Read 工具把 string.rs 裸 NUL 渲染成空格导致"Rust push 空格 vs MoonBit NUL"误判——hexdump 原始字节证明两侧一致('\x00')。教训:字符字面量不能信渲染层。

7.5 差分工具性能修复(2026-09-19,用户质疑指令耗时触发自查)

cmd/dump_tokens 的 Vfs 注入曾在 dump_one 内每文件重新 collect_files 整个语料目录 (read_dir N 项 + 读 N 文件)——2400 例语料 O(N²) ≈ 576 万次文件操作,MoonBit 侧 dump 实测 4m43s vs Rust 侧 2.5s(112 倍)。修复为目录级收集一次共享:

环节 修复前 修复后
MoonBit 侧 dump 2400 例 4m43s 3.9s(含 moon run 构建链 ~2s)
三语料差分全流程(lexer_diff ×3) 6-8 min 10.6s
facts --run(moon test + 语料 + 差分) 8-10 min ~1 min

产物语义不变(三语料 5688 TSV 逐字节一致复验通过)。教训登记:B7 加自定义头 Vfs 注入时未考虑批处理形态的复杂度——差分基建自身的性能回归会让护栏被"跑不动"绕过。

7.8 覆盖缺口补课(2026-09-19,用户指出 directives.rs/expander.rs 未逐行对照)

方法:逐函数并排细读(指令分发/条件组五函数/include 十步/define 参数表与 body 词法/expander 主循环与实参捕获/H01 包装/计费时序/trace 截断/W1019 集合)

  • 25 个边界形状差分探针(参数表空白与未闭合、quote-miss、空宏体、括号内逗号、 交替自引用、elif-after-else、孤立 endif、defined 组合、同体重定义、嵌套同宏、 多 token 字符串化、拼接数字、# 后空白、双存根、quote 式 has_include、连续调用、 嵌套 ifdef、skipping 态 define、未闭合调用、CRLF 宏体、undef 缺席宏、空指令、 include 行尾杂项)。

结果:23/25 逐字节一致;2 个 DIFF 为同族新差异(登记 D18): Rust 指令解析的 skip_whitespace 集合含 ,跨逻辑行吞后续代码—— #define X( 未闭合参数表把下一行吞进参数扫描(E1005 错位到 2:5、token 全丢); # 空指令把下一行首词当指令名静默吞掉(零诊断、count=0)。MoonBit 行级 架构天然隔离(C 语义:指令止于行尾),红锚 directive line isolation 固化。

顺带闭环:gen_corpus.mbtx 改为运行时读取 known_diffs.json 的 forbidden_markers 做产物自检(此前为硬编码镜像——审阅者建议的"清单作为排除 输入"彻底落地;JSON 误写 两字符曾致自检真实证红一次,即 J9 留痕)。

逐行对照中确认一致的关键时序(脑对结论,探针覆盖):实参先行展开时宏名 未涂蓝(嵌套同宏正确)、H01 包装判 j+1 分号、emitted 计费在提前 return 路径 不计、W1019 副作用 token 集 7 项、arity 不匹配不消费实参、include 深度保险丝 >= 阈值、#include 非 </" 开头静默跳行(两侧同)、skipping 态的 define/include 门。

8. 0.3.0 版本事实

  • Token 加 real_line : Int(破坏性字段变更——发布数小时、无外部消费者);
  • 打包卫生修复(无 _build 产物);
  • 全回归:MoonBit 113/113、Rust lexer 57/57;差分语料经 §7.8 补课扩至五目录(随机/baseline/K&R/leetcode/gap)共 5994 TSV 逐字节一致(2026-09-19 S2 收官时点;当前真值随语料增长由 facts 承载——2026-09-20 哨兵样本入 gap 后 5996)。