执行: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. 差分过程中修掉的移植缺陷(红→绿留痕)
- 实参捕获数组别名:
args.push(current); current.clear()引用语义清空已存实参——MAX(1,2)展开双参同值(diff g000002 抓出)。 - 存根 include 行号公式:
L − N − 2 + k误套哨兵公式——存根路径 Rust 无哨兵包装,正确为L − N + k(负行号是 oracle 行号补偿缺陷的忠实复刻)。 - u8 前缀列号基准:Rust 的
string_literal()起点在引号处(不含 u8)而 byte_off 记账在 u8 起点——拆分span_start双基准。 - string/char 列号基准:Rust
make_token(ty, &源切片)用源切片长度反推列后才覆盖 text 为解码值——复刻源切片基准(value 与源拼写不等长时两口径分叉)。 - 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. 决策登记(与勘察报告的偏差)
- TokenType 维持 flat 116 变体(勘察 ⑤-4 曾建议分层):L1/L2 差分锚要求
to_name与 Rust Debug 逐字节一致,flat 消除映射噪声;diag 137 臂先例已证工程可行。 - 公共类型独立包
lexer/token:MoonBit internal 包不可被 facade 外导入且 facade↔internal 不得成环(实证:cmd/dump_tokens 导 host 被 internal 可见性规则拒绝)——勘察 ⑨ 的internal/{source,pp,host}三包调整为token(公共)+internal/{scanner,pp,host}。 - StubsFirstProvider 删除:trait 不能作字段类型(语言约束)——存根优先逻辑单源在 pp/resolver。
SourceProvider为pub(open) trait:默认 readonly trait 跨包不可 impl。substringdeprecated warning 留置 23 处:本安装 core 的切片形态(str[:])返回 StringView 且 raise,与 String 类型系统摩擦;substring 是可用形态,留置登记(非阻塞)。- 语料生成器为
.mbtx(总计划锚点名要求)——但受"front-matter import 无法解析本地 module 包"限制,生成器不 import lexer 包(纯生成逻辑 + x/fs 落盘),词法侧批量出口用 workspace 内 main 包(cmd/dump_tokens)。 - 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)。