AGENTS.md「当前任务与状态」历史明细(M35–R8.2)
⚠️ 这是归档材料,不作为设计参考。
AGENTS.md的 §3 原本是一份任务流水:每完成一轮就往里加一段,涨到近 1000 行, 把「当前该干什么」整个埋掉了。2026-10-08 整段搬到这儿,§3 只留 「现在在哪、下一步做什么、怎么量」。 原文未做删改;里面的行号/章节引用已经漂移。 活着的硬约束在AGENTS.md§1/§2;设计取舍在docs/设计决策.md。
- ✅ M0–M37 全部完成(语言核心 → AI 工具链 → 沙箱/MCP → 生态 → 性能分发 →
评测闭环 → 跨语言分发 → 包管理 → 语言收敛 1.0 → 发行版 → 排错体验补全 → 表达便利 → 发行改进 → 性能优化 → 开发者工具 → 包生态 → 语言功能 A/B/C 档 → 变长参数 → 值→文本统一 → Rust / JS 宿主补齐(含标准库同宽)→ 边界清单核实 → 模式匹配与枚举 → 发布工程与三平台 Release → 语法冻结 → 调试器)。 里程碑明细与各轮教训见 PROGRESS.md(末尾「里程碑明细 / 路线图历史归档」)。
- 📌 M35(发布工程与收尾):语言图标进发行物(多尺寸 ico / icns / Linux
桌面项 / Inno Setup 图标);类体校验统一到解析期(原先三执行器各不相同, 甚至 x += 5 静默算错);修掉「测试里写死开发机事实」这条根因, 把长期全红的 CI 修到四平台全绿。
- 📌 M36(主线 A 收敛 → 语法冻结):条件表达式
甲 如果 条件 否则 乙
(脱糖,三执行器 + 两宿主零改动)+ 位运算内建 6 个 + 字典.合并 + Node/Rust 宿主同步(顺手补上 Rust 一直缺的 字典.更新/弹出)+ 探针「无悬案」判据;三份路线图合并成一份。语法面就此冻结。
- 📌 M39(主线 B 收尾 + 主线 C 开头):
- B5 字节码执行器的调试器:
jishi 调试 --执行器 vm。栈机没有「语句边界」
事件,于是让编译器在每条语句的首指令上打标(Instr.stmt_line),VM 主循环 只在标记处回调调试器。循环回边落在语句中部,所以循环头只停一次——与树遍历的 暂停点严格对齐,两个执行器的会话记录逐字相同(tests/test_m39_vm_debugger.py 逐项对拍)。C VM 当时判为暂不做(帧在 C 侧,要结构体 + 宿主回调)—— M40 已补齐(见下);
- 顺手修掉四个潜伏缺陷(都不报错、只是你以为有):跨行三引号字符串的
收尾引号独占一行时不补 NEWLINE(多行字符串整个特性不可用)、类体里的方法 定义行两个执行器都停不住、类变量求值顺序与源码顺序不一致、 导入 文本 会打断 f-string(最后一个尚未修,已列入路线图);
- 主线 C 开头:
examples/projects/基石自述站/—— 用基石写的静态站生成器,
读仓库真数据(版本号 / 测试数 / 探针 / 里程碑 / 模块规模)生成一套介绍基石的 网页;三个执行器跑出来的产物逐字节相同。
- 📌 M38(主线 B):工具链收尾(主线 B 就此收齐):
- B2 LSP 增量同步:声明增量同步(客户端只发改动的那一段)+ **按行复用前缀的
增量分词(jishi.incremental,依据是 tokenizer.LineAnchor 记的七个状态值)。 实测每键分词成本:18001 行改末行 359.7ms → 8.5ms;1801 行 33.4 → 0.5ms。 parse 仍是全量的(如实标注在 docs/工具链.md);编辑范围按 LSP 默认的 UTF-16 码元**换算;版本乱序直接忽略。
- B3 REPL 增强:↑/↓ 跨会话历史(
~/.jishi/repl_history.txt,JISHI_HISTORY
可改)、Tab 补全(数据来自 ai.build_lang_spec(),不另抄一份)、多行编辑 判定交给真词法器。Windows 自实现逐键编辑 / POSIX 交给 readline / 非终端退回 input()。
- B4 格式化器边界:修掉「字符串里的
#被当注释」「括号续行里的注释被吃掉」
「带 BOM 的文件直接崩」「格式化 <目录> 甩英文 traceback」四个真问题。
- 📌 M37(主线 B1):调试器 ——
jishi 调试 脚本.jsh:
断点 / 单步(进·过·出)/ 看变量 / 看调用栈 / 查看 表达式;出错时先打 「出错那一帧的变量 + 调用栈」的现场,再打正式中文报错。 目前只支持树遍历执行器(字节码 VM / C VM 是栈机、没有语句边界事件, 要另做一套);钩子只读,带调试器跑与不带,程序输出逐字节相同。 用法与边界见 docs/调试器.md。
- 📌 M43(本轮):方向二 · IDE 可用性第二轮:
- 新增
jishi dap:把三个执行器的调试器接到 Debug Adapter Protocol。
覆盖 initialize / launch / setBreakpoints / configurationDone / threads / stackTrace / scopes / variables / evaluate / continue / next / stepIn / stepOut / pause / disconnect 与 initialized、stopped、continued、output、 exited、terminated 六个事件。三执行器逐项一致(tests/test_m43_dap.py 34 项);唯一差异是 C 虚拟机不支持就地求值——明说原因并给替代方案,不给错值。
- 线程模型是这轮唯一要动脑的地方(D42):主线程跑 DAP 消息循环,脚本线程
跑程序;暂停时脚本线程阻塞在命令队列上,所以主线程读栈与变量时执行状态 是静止的。stopped 事件必须在暂停判定一成立就发(编辑器收到就来问栈)。
- 扩展接调试适配器(D43):用 VS Code 内置的
DebugAdapterExecutable,
没有自己写 DAP 传输层——语言服务那条路要自己写是因为要在编辑器外也能跑 同一份代码,调试这条路没有这个需求。
- 打包
.vsix(26KB):装完在任何窗口、任何文件夹打开.jsh都有效,
不必再走「F5 + 扩展宿主窗口」(从宿主窗口打开新文件夹会另开一个普通窗口, 那个窗口不加载开发中的扩展)。.vscodeignore 已排掉 99MB 的 node_modules。
- 高亮与片段对齐语言事实(D44):内建 15 → 28、新增标准库模块名 19 个
与异常类型 9 个着色、关键字补 7 个、片段补 7 条; tests/test_m43_highlight.py 钉住「新增内建忘了同步高亮表就红」。
- 📌 M42:方向二 · IDE 可用性第一轮:
- 语言服务补六项能力:
formatting(接上已有格式化器)/rename+
prepareRename / codeAction(报错里的修法建议→可点快速修复)/ signatureHelp(文档解析不了时走 token 兜底)/ workspace/symbol + 跨文件 跳转 / semanticTokens(关键字/标准库/用户符号/形参/属性五类)。
- VS Code 扩展升级为语言客户端(此前连
main都没有,只有语法高亮):
自己写 lsp-transport.js(约 150 行:分帧/请求应答/通知分发), 零运行时依赖(没引 vscode-languageclient,见 D39),11 项能力逐条接上。
editors/vscode-jishi/verify-lsp.js端到端自检(25 项断言):用扩展**同一份
传输代码**真的起一个 jishi lsp,把每项能力点一遍——脱离 VSCode 就能验 「点了有没有反应」。已进回归(tests/test_m42_vscode_client.py)。
- 三条有意的边界(宁可少做也不改坏用户代码,见 D40):
① 重命名只做模块级符号(AST 不记形参位置,且没有作用域分析会误伤同名 局部变量 → 返回 null 让编辑器明确拒绝)② 跨文件跳转只在唯一命中时生效 ③ 工作区符号索引有封顶(400 文件 / 512KB / 跳过 .jishi 与隐藏目录)。
- 📌 M41:方向一 · 语言能力第二轮:
- 标准库补 5 个模块(
容器/迭代/参数/日志/测试),
五路同宽(三执行器 + Node + Rust)。模块名是「容器」不是路线图里写的 「集合」——集合 是内建函数名,同名模块会把内建遮蔽(D35)。
- 内建
带下标/配对(同 Python 的enumerate/zip)。 遍历支持多变量解包(遍历 名字, 年龄 在 对们:):解析期脱糖,
三执行器与两宿主零改动(D36)。
- 错误归类修正:
整数("abc")从「类型错误」改「值错误」(对齐 Python
与既有的 "abc".转整数());待办清单 的 捕获 异常 已收窄。
路径.递归列出(目录, 后缀);真实项目替换绕道写法后 行数比 1.27 → 1.21。- C VM 性能定位:瓶颈仍是 ctypes 边界(58%–87%);批量迭代回调由
两趟遍历改一趟,整数循环 9.5x → 10.5x、函数调用 13.3x → 15.7x。
- 📌 M40:穿插项收尾 + 主线 C 铺开:
- C VM 的调试器(B5 最后一格):C 侧加四个只读入口(语句起始表 /
语句钩子 / 帧元信息 / 帧变量导出),三执行器会话记录逐字对拍 (tests/test_m40_cvm_debugger.py)。唯一没做的是 C VM 的就地求值 (C 侧没有「在任意帧上求表达式」的入口),会话明确说明而不是给错值。 顺带补上 C VM 一直缺的报错调用链(M17 给另两个执行器做了,C 侧漏了)。
- 树遍历「赋值即局部」补齐:进函数时给「本函数赋过值的名字」占未绑定
哨兵;判定复用编译器的收集器,报错文案走单一来源 (runtime.unbound_local_error)。M39 那条 xfail(strict=True) 已转正常断言。
- f-string 遮蔽修复:脱糖目标从内建
文本改成用户写不出的$文本;
内部名从语言规格 / 补全里过滤掉(runtime.is_internal_builtin)。
- 主线 C:五类真实项目 + 行数比基线 1.27(有效行加权,合计 688 ÷ 542)。
口径、逐项数据与「别扭点清单」见 docs/真实项目.md。
- 📌 下一步(新阶段三方向):路线图 §3.6 定的是「语言能力 / IDE 可用性 /
AI 可用性」三个方向,M40–M45 顺序推进。 M42 / M43 已完成(语言服务六项能力 + VS Code 语言客户端;jishi dap 调试 适配器 + 扩展打包)。M44 / M45 已完成(机器可读语言事实 + 静态检查; AI 跑分表 + MCP 工具面 7 个)。方向三就此收齐。 M41 的复盘给 M42–M45 提供了新靶子:别扭点清单里 2 条其实是 「能力早就有、只是不知道」(数学.四舍五入、文本.左对齐、集合交并差) ——治它靠补全与机器可读事实,不靠加能力。
- ✅ M44 已交付(方向三 · AI 可用性第一轮,2026-09-22):
--lang-spec补
错误码表 / 静态检查码表 / 等价 Python 写法;新增 jishi 源码静态检查 (四类问题,真实语料零误报);作用域分析抽成 jishi/scope.py 供编辑器与命令行 共用。顺手修掉 E2008 重码(自定义异常改 E2009)。
- ✅ M45 已交付(方向三 · AI 可用性第二轮,2026-09-22):
① tools/eval_ai.py AI 跑分表——45 题(语法 12 / 标准库 15 / 调试 10 / 重构 8)在 evals/tasks/*.json;模型只看语言规格,写完真跑基石判分 (输出逐字相同且退出码 0),跑错回灌中文报错最多改 3 轮。 题集先自检(--自检):调试题的缺陷代码必须真失败、重构题原始代码必须 真跑通——题目错了跑分表就是假的。限流按 3/6/12/24 秒退避重试。 ② MCP 工具面 3 → 7 个:check_source / lookup_error / lookup_stdlib / format_source,事实全从语言规格与既有实现取,不另抄一份。 ③ 顺带修掉树遍历复合下标赋值漏裸 KeyError (字典[新键] += 1 → 原来报「程序内部出现未预料的问题(KeyError)」, 现报 E2004 找不到这个键)——写调试题集时抓到的。
- 📌 M45 的收尾动作:打包 GitHub Releases。
(不再含「PyPI 首次上传」——2026-09-22 用户决定放弃 PyPI,改自建分发。)
- ✅ 方向六 · 标准库继续加强(M50)已交付(两批):第一批新增
统计/编码/
配置 / html / 对比 / 标识 + 既有模块补缺(迭代 +4、文本 +2、 时间 +1)——19 → 25 个模块、161 → 231 个函数;第二批补齐 Node / Rust 宿主,五引擎同宽(tests/test_m50_hosts.py 39 项逐字节对拍)。 验收落到真实项目:生成.jsh 的手写 转义()、排序.jsh 的手写计时都换掉了。
- 📌 当前任务:方向七 · 架构优化(M51–M53)——2026-09-25 用户提出
「三执行器、五引擎继续维持的代价是不是太大了?要不要优化架构?」 完整评估见 docs/架构评估.md(内部文档,不发布)。三条实测结论: ① 执行器与宿主都不砍——三执行器是最值钱的验证资产(对拍抓过 M47 内存泄漏、 M48 错误列号、M50 上下文管理器缺口);两个宿主是跨语言分发的落地形态。 ② 真正的重复是「标准库写三遍」:宿主层 17365 行里 11161 行是标准库 (Node 2282 + Rust 8879),同一份逻辑三种语言各写一遍。 ③ 纯逻辑库可以用基石写一遍、五引擎共用(POC 已验证):统计 用基石重写 68 行 → 编译成字节码 → Node / Rust 输出与官方 Python 版逐字节一致,零宿主代码。 代价是慢约 7 倍,但真实项目里标准库调用只有 4–26 次(耗时大头在循环体内), 实际影响 <10%。性能敏感的库(如排序类)留在宿主层——要分层,不一刀切。 ⚠️ 最危险的不是「贵」,是「错了不报警」:全仓没有「某模块必须五引擎都有」 的检测测试——M50 第一批的宿主缺失是手工测出来的。所以 M51 先止血。 ✅ M51 一致性止血已交付(2026-09-26):漂移检测测试(宿主自报家底) + 宿主命名参数缺口修复(五个引擎同一结果、映射不了就明确报错) + 顺带补齐 13 个静默缺失的函数。细节见 §2 的「宿主命名参数缺口」与 「漂移检测已建」两条。 ✅ M52 基石库层第一步已交付(2026-09-26):纯逻辑标准库只写一份 (jishi/stdlib-jishi/统计.jsh,就是基石源码),编译期展开成外壳函数, 于是五引擎共用同一份字节码、三执行器与两宿主零改动。 机制、走过的弯路、代价都写在 docs/设计决策.md D45 与 §2 的 「基石库层已建」。⚠️ 实测代价:比宿主层慢 25–38 倍 —— 热路径函数必须留在宿主层,这是 M53「性能分层」的存在理由。 ✅ M53 基石库层批量迁移已交付(2026-09-26):又搬了 容器(7)、 迭代(12)、参数(5)—— 加上 统计,基石库层共 4 模块 / 34 函数。 命名约定扩展 __X → 导出 X;性能分层第一例(迭代.求和 转发内建 总和)。 顺手抓到一个影响四个引擎的真 bug(编译器的 尝试/捕获 把栈搞脏, 详见 §2)并修复 + 加回归测试。 ✅ M54 把「五引擎的账」结清已交付(2026-09-27):抓到 4 个真 bug + 补上 1 个机制缺口 —— ① Rust 的闭包单元不共享(值语义的 Cell, 导致嵌套递归/闭包收函数回调全崩);② Rust 未绑定局部静默给 空; ③ C VM 的未绑定单元报错类型与另两个执行器不一致; ④ C VM CvmFunction.__del__ 的用后释放崩溃(崩在 GC 里); ⑤ 方法漂移检测(M51 那份只覆盖标准库函数名)→ 顺带补齐 Rust 文本.查找、Node 文件.位置/定位/刷新。详见 docs/设计决策.md D46。 ✅ M55 定下「字符是什么」已交付(2026-09-27):① 新增内建 序数(字符) / 字符(码点)(三处实现,五引擎逐字节一致); ② 🔴 修掉 Node 的「文本 = UTF-16 码元」真 bug —— 长度("😀") 曾给 2 (另两个给 1)、"😀"[0] 只拿到半个代理项、"a😀b".查找("b") 给 3, 漏的正是 长度/单下标/查找/反转 四处(切片与遍历本来就对); ③ 新增 --dump-builtins(第三个「宿主自报家底」的口子)—— 建起来当场抓到 断言 / 是实例 在 Node 与 Rust 上根本没有, 断言错误 这个捕获类型也缺,都已补齐; ④ Rust 12 个内建参数个数不对会 panic、Python 侧漏英文原生错误 → 一并收口(文案与 Python 侧逐字对齐)。详见 docs/设计决策.md D47。 ✅ M56 第一批 · 内建体检已交付(2026-09-27):新建 tools/probe_builtins.py —— 给每个内建一条最小程序,五个引擎真跑 (正常用法 + 参数个数错 + Rust panic 扫描),不再只比名字。 一建起来就抓到 M55 漏掉的账:⚠️ M55 把 超 当成「编译器形式、宿主 不需要有它」而排除在比对之外(旧 BUILTIN_EXEMPT = {"超"})—— 判断错了:它是 LOAD_GLOBAL 超 + CALL 的真运行期内建, 于是 Node 与 Rust 上 超() 一直不可用(报「找不到名字「超」」), 而三个执行器全对、对拍又因豁免覆盖不到。已修:Node 补 JishiSuper, Rust 给 Class 补 base 字段(那边建类时展开合并基类方法表后 把基类指针丢了,而 超() 必须沿链找「定义类」)。 详见 docs/设计决策.md D48。教训:豁免必须能被复核, 否则它就是藏 bug 的地方(BUILTIN_EXEMPT 现为空,常量保留)。 ✅ M56 第二批已交付(2026-09-28):① Node 19 个内建补参数个数检查 (体检的 16 条 → 0;含 3 条曾是「静默成功」的 —— 文本() / 类型() / 退出上下文() 在 Node 上直接返回值)。⚠️ 根因是签名写成 (v) => …: 0 个实参时形参是 undefined、函数体照跑 —— 必须用 (...args) => 才拦得住。 顺带收口 Rust 4 处自己抄的文案(精确 少「但传了 N 个」、范围 没书名号、 两个上下文多空格)→ 参数个数错现在五引擎逐字一致。② Rust 异常对象模型: 捕获时改压带消息的异常值(以前只压类型名 → e.类型/e.消息 报「没有属性」)。 ③ jishi 错误 [码]:32 个码的「什么意思 / 常见成因 / 怎么改」 (码从 errors.py 自动抽,说明在 jishi/errcodes.py,测试钉住不许漂移)。 详见 docs/路线图.md §9.4.0b 与 docs/设计决策.md D48。 ✅ M56 第三批已交付(2026-09-28):第 3 / 4 / 5 条 —— ① 文件.位置/定位 统一成字符数(Python 原来给 tell() 的不透明字节 cookie; Rust 给字节偏移);② 换行符统一 \n(Python 原来在 Windows 上写 \r\n, 同一台机器上五引擎写出的文件字节不同 ⇒ 用 newline="\n"); ③ 🔴 顺手抓到 Rust 的 文件.读(n) 按字节读 —— 非 ASCII 上把汉字切两半, 报「文件内容不是 UTF-8 文本」,而文件明明合法(误导性报错比不报错更耗时); ④ Rust 的报错终于有行列(原来全部没有;根因是 JishiError 缺字段 + 反序列化把指令的 line/col 丢了)+ 两宿主补错误码 (错误 E2001:不能除以零 + ← 第 3 行第 6 列;码能直接贴给 jishi 错误 <码>)。详见 docs/路线图.md §9.4.0c。 ⚠️ 错误码只映射能确定的 7 个(除零/类型/索引/键/值/文件/断言)—— 宿主的「异常」太笼统(找不到名字、属性不存在…都归它),宁可不给码也不瞎标。 ✅ M56 第四批已交付(2026-09-29)—— 🎉 M56 四批至此收完。 这一批给两宿主补上 hint(提示语 + 候选名): 找不到名字「长渡」 → 「提示:你是不是想写「长度」?」」。 ⚠️ 难点不在加字段,而在候选项必须三侧一样:Python 用 difflib, 而 Node / Rust 都没有 —— 各逐字复刻了一份 Ratcliff-Obershelp (不一致比不给候选更糟)。两个抄错就分叉的点: ① 最长公共块要用 difflib 的 DP 版(k > bestsize 严格大于,并列取先遇到的); ② get_close_matches 收集的是 (分数, 名字) → 同分时按名字降序 (抄错这点,3000 组随机对拍里 103 组顺序不同)。 📌 得意之作的方法论:先在 Python 里把复刻版与 difflib 对拍 (4000 组 ratio + 3000 组候选全一致),确认算法钉死,再翻译成 JS / Rust。 ⚠️ Node 的字符串比较要按码点(cpCompare,JS 的 < 是 UTF-16 码元序); Rust 的 UTF-8 字节序恰好与码点序一致,直接用 cmp。 ⚠️ 宁可少给,不要给错:prnit 跟池里谁都不像就不给提示行。 详见 docs/路线图.md §9.4.0d。 ✅ M49 · 自主分发已落地(2026-09-29):ICP 备案完成**(苏ICP备2026073372号), 官网与自建分发源上线 <https://基石.cn/>(源站=大陆轻量云 Nginx,前面 EdgeOne CDN)。
jishi 安装改走「自建源优先、GitHub 兜底」(DEFAULT_INDEX_URLS):
自建源那份索引的「下载地址」也指同一域名 ⇒ 索引 + 包全在境内(实测 0.11s)。 两份索引各自自洽,所以自建源挂了是整条退到 GitHub,不会半路断。
- 🔴 HTTPS 521 的真因:源站只听 80,而 CDN 对 HTTPS 请求走 HTTPS 回源
(443)⇒ 必然 521。判据是源站 access_log 里有没有那条请求(HTTP 有、HTTPS 没有)。 给源站装证书 + 开 443 后两条路都通(tools/nginx-jishi-tls.conf)。
- M49a 收口:多源回退 + 可操作提示(列清试过的地址、给
--index/
--local-zip / --local-dir)+ jishi 医生 的索引可达性自检。
- 安装包放上官网:<https://基石.cn/download.html>(四个平台包 + 逐版本升级说明)。
发行包由 tools/fetch_release_mirror.py 从 GitHub Releases 取(开发机拉得动、 服务器拉不稳)到 dist/release/,SHA256SUMS 逐个校验; ⚠️ deploy_push.sh 只在服务器缺文件时才传(109MB,不能每次都推)。
- 部署脚本:
tools/site_sync.sh(服务器侧)/tools/deploy_push.sh(本地推)
/ tools/nginx-jishi{,-tls,-locations}.conf。⚠️ 站点规则单独成片段, 80 与 443 两块 include 同一份(两处复制迟早漂)。 🔒 密钥与服务器地址只从环境变量读。
- ⚠️ 云助手会拦命令(
curl/rm -rf/ 复合长命令 /tar解包到系统目录),
有 SSH 就优先 SSH(用户名 ubuntu、免密 sudo)。见 操作细节.md 附一/附三。
- ⚠️ 大陆拉 GitHub 不稳:
git fetch300 秒超时、tarball 也会中途断
⇒ 本地构建 + scp(deploy_push.sh)才是可靠路径。
- 📌 详见
docs/设计决策.mdD52 与docs/路线图.md§9.3.6。
🚩 2026-09-30 转向:M57–M59 全部作废,改走 R 线「前端换 Rust」 (作者定:前端换 Rust + 桥接层 + 保留并增强 C VM + 单二进制解释器; R 线全程在 0.2.x 号段;架构变更完成后才发 0.3.0(进入 0.3.x 阶段))。
- 两份内部文档(在发布屏蔽列表里):内部架构迁移评估(未公开)
回答「值不值得」(全部同机实测),内部架构迁移路径(未公开) 回答「怎么做」(R0–R8 + 三条纪律 + 中途检查点 + 待拍板决策)。 决策与承接见 docs/路线图.md §十一。
- 关键实测:四语言合计 43274 行;宿主层两语言 **19792 行 =
Python 全套的 0.96 倍;启动(打包版)284.9 ms、Linux 包解开 102 MB (最大两块是 两份 30.6 MB 的 libpython);Rust 宿主二进制只有 2.63 MB; 前端跑 2 万行 2132 ms(编译占 1246 ms);Rust 侧 ≈7900 行可直接复用**。
- ✅ R0 一致性夹具已交付(2026-09-30):把 Python 前端现在的四阶段行为
(tokens / AST / 字节码 / 诊断)在 73 个语料上逐字冻结成基线 —— Rust 版每写一步就拿它比,一致才叫这一步做完。 工具 tools/conformance.py(freeze / check / list)、基线 tests/conformance/、 守门测试 tests/test_m57_conformance.py(17 项)、说明 docs/一致性夹具.md(内部)。 实测:冻结/比对各约 1.4 秒;产物 4.2 MB(gzip 0.3 MB); Python 3.10.9 与 3.13.14 产物逐项一致(73/73)。
- ⚠️ 两条硬闸门(都来自实测踩坑,别再拆掉):
① conformance.py 里前端抛非基石错误时拒绝冻结 —— 工具分不清「事实」与「事故」,「把当前状态升格为标准」的动作必须有这道闸门 (第一次试跑就把我写错的 dumps_json 冻成了 73 份「标准」); ② fetch_release_mirror.py 里清单没有该资产名时拒绝发布 —— 「跳过校验」与「校验通过」在 CI 里长得一模一样(它曾拿上一版清单静默跳过四条校验)。
- ✅ R1 前端契约固化已交付(2026-10-01):把「前端每个阶段要产出什么」写成
带版本号的契约(docs/前端契约.md,内部),并给出能定位的尺子 —— tools/conformance.py compare --from <目录>(外地产物 vs 基线,差异定位到 路径.字段[下标] 并说出那个元素是什么);--stage 支持只比某一阶段。 不改行为,只定义接口 —— R2–R5 的验收方式由此固定。
- 📌 R 线的六个决策已按建议拍板(目标形态=单二进制+wasm / 迁移通道=Rust 作
Python 扩展 / 保留 Rust 树遍历参考执行器 / C VM 去向留 R6 / 新开 R 线 / R0 立刻做)—— 见内部路径文档 §八。
- ✅ R2 分词器已交付(2026-10-01):新建
core/(Rust crate,pyo3 编成
Python 扩展),tokenize() 在 JISHI_FRONTEND=rust 时走 Rust。
- 逐 token 一致:85/85 语料 ✅(+ 2 万组随机源码对拍 0 分歧)。
语料从 73 → 85(R2 补 12 个,见下;R3 又补 3 个 → 88)。
- 吞吐:端到端 3.72x(490.8→132.0 ms/2.07 万行)· 纯核心 7.05x。
⚠️ 端到端天花板 = 4.23x(把 Token 交回 Python 有 46 ms 的地板)—— 「≥5 倍」这层接口够不着。 ✅ 2026-10-01 已拍板:这层验收线 = 「纯核心 ≥5x 且产物逐项一致」, 「端到端 ≥5x」移到 R5 —— 那时下游不再要 Python 对象,端到端才有意义。
- 🔴 补了 12 个边角语料:R0 的 73 个里一个 emoji 都没有(等于没测
位置);另有 CRLF / 孤立 \r / BOM 这类文件表达不了的输入,走 字符串级测试;并新增 tests/frontend_cases/(只给前端看:像超 i64 整数这种 C VM 有意拒绝、但前端必须处理的语料)。
- 工具:
python core/build.py(构建)/tools/bench_frontend.py(吞吐)/
conformance.py emit --frontend rust --out 目录 → compare --stage tokens。
- 守门测试
tests/test_m59_tokenizer_rust.py(78 项:全语料一致 + 24 条字符串级边角
+ 11 条报错逐字 + 8 条端到端 + 720 组随机对拍 + 三张表漂移 + 开关语义)。
- ✅ R3 语法 → AST 已交付(2026-10-01):
core/src/parser.rs(2600 行,逐行
移植 jishi/parser.py),parse_source() 在 JISHI_FRONTEND=rust 时走 Rust。
- AST 一致:88/88 语料 ✅(+ 5 万组随机源码对拍 0 分歧;40 个脚本端到端
跑出来也一样)。compare --stage ast 与 --stage diagnostics 都全绿。
- 吞吐(2.07 万行 / 735 KB):语法 端到端 823→271 ms(3.04x)· 纯核心 5.64x
(天花板 4.73x);分词仍是 3.63x / 6.47x。
- 🔴 接口改了一次(这是 R3 最有价值的一条):Rust 侧
parse吃源码文本,
而 Python 入口原来只有 tokens + source_lines —— 后者是有损的载体 (incremental.split_lines 去掉末尾空行),反拼回去报错行号整体偏一位 (实测 9 个样例错 4 个)。于是新增 parse_source(源码) 作为生产入口, parse(tokens, lines, f) 保留给 LSP 增量分词,并加精确守卫 (拿 EOF 行号当标尺)——丢了内容就当场报错,不猜。
- 🔴 顺手抓到的两处「泄内部结构 / 两套口径」:① 插值字符串的
value是
分段表,直接插进中文报错会把 Python 的 repr 泄给用户 → 两侧统一说 「插值字符串」(_tok_text / tok_text);② 类外 超() 那条错漏了 with_source 与下划线(它是解析之后补的检查,不走 _err)→ 已补齐, 报错的补全要认「一个出口」,别按「谁抛的」分岔。
- 守门测试
tests/test_m60_parser_rust.py(111 项)+ 3 个新错误语料
(41_块首后空行 / 42_类外超 / 43_整行插值字符串,都被冻结进基线)。
- ✅ R4 前端报错已交付(2026-10-01):先把范围核清楚了再动手 ——
R2 比过词法报错、R3 顺手比过语法报错,所以 R4 只剩「把比较面补全」这半边, 「执行期/编译期」那些要等 R5/R6 才有产物。
- 契约升 v2:
diagnostics补下划线/修正/渲染
(渲染 = 用户真正看到的那段多行文本)。⚠️ 渲染 看着冗余,其实是兜底: 哪天给错误加了事实字段却忘了列进契约,两者的渲染立刻不一样 —— 而「字段没进契约」本身是看不出来的(R3 就栽在「漏了一个字段,比对还是绿的」)。
- ✅ 错误码体检(新
tools/probe_errcodes.py):**「表里有这个码」≠
「这个码真的会出现」,这件事以前从来没人查过。第一次跑就抓到两笔: E0205「括号没有闭合」是死码(ParseUnclosedError 定义 + 说明都在, 全仓库没有任何触发点 —— 括号没闭合其实报 E0201); E0204 可达却一个语料都没有(八个形状能触发)。 「不可达」必须登记且经得起复核(verify_unreachable() 会静态复核 「是不是真没人引用」),登记过期就报红。 ✅ E0205 已拍板接上(2026-10-01):期望右括号而已到文件末尾 ⇒ E0205, 且报错位置指向那个没闭合的开括号(EOF 的位置是「末行第 1 列」、 连源码行都显示不出来,指着它没用)。⚠️ 只认「走到末尾都没等到」 —— 中途撞上别的 token([1, 2 后面换行写下一句)仍报 E0201 并说出「看到了什么」, 那时那条信息更有用,别为了把码归拢而把信息丢掉。 顺带修掉「到文件末尾把 Python 的 None 印进中文报错」(_tok_text 补 EOF 标签)。 现在:11 个可达码全部有语料钉住,0 死码。**
- ✅ 统一了「扩展事实 → JishiError」的出口(
errors.from_extension):
以前词法/语法各抄一份字段清单,而词法那份漏了「修正」—— 恰好当时 没有一个词法错误带 fix,所以怎么测都是绿的。 新建 tests/test_m61_frontend_errors.py 用字段表往返把这个缺口堵死。
- 语料 88 → 92(补 E0204 两个形状 + E0205 两个形状),基线按 v2 重冻;
两个前端在 92 个语料上 tokens / ast / diagnostics 三阶段全绿(R5.1 后 93)。
- ✅ R5 编译器 → 字节码已交付(2026-10-02):
core/src/新增
compiler.rs(jishi/compiler.py 1151 行的逐行移植)、jishilib.rs (基石库层展开)、serialize.rs(字节码 JSON + Python 的 repr(float))、 opcodes.rs、sha256.rs。
- 四阶段全绿:92/92 语料(tokens / ast / bytecode / diagnostics;
R5.1 后 93/93); 字节码逐字节相同(连 checksum —— 它是 payload 的 SHA-256, 差一个字节就不一样),另 3 万组随机源码对拍 0 分歧。
- 🔴 接口换了,这是 R5 最关键的一步:产物定成 JSON 文本而不是
Python 对象 —— 「把对象交回 Python」正是 R2/R3 那堵天花板的来源, 文本没有这个地板。实测编译端到端 2035 → 379 ms(5.37x), 「端到端 ≥5x」这条 2026-10-01 从 R2 挪过来的验收线兑现了。 Python 侧两个入口:compile_to_json()(原生,给 --dump-bytecode 与夹具) 与 compile_source()(仍给真对象,VM/CVM 那条路要用)。
- ⚠️
compile_module()不能省(本可以「从 JSON 反序列化回来」了事):
local_hints 不在字节码 JSON 里,而字节码执行器报错要用它 (「赋值前读局部」那句提示)。只给 JSON 的话,rust 前端跑 VM/CVM 时 那句话会静默消失 —— 两个前端就有了语义差异。有测试盯着。
- 顺带搬了两件「不能靠眼看」的:指令表(Rust 侧是那份编号的第三份拷贝,
opcode_table() 与 OP_NAMES 对拍)与 repr(float)(JSON 里的浮点文本, 拿 1 万多个值逐个比)。
- ✅ R5.1 已把移植时挖出的四个既有 bug 全修掉(详见
docs/设计决策.mdD59):
① 编译器名字收集漏 Try 分支(赋值漏成全局 / 闭包捕获不到 / 块内定义函数类直接崩);② Python VM 的 BUILD_CLASS 用局部名 base 撞掉帧的栈基址(「函数里定义类」必炸);③ 树遍历给「捕获 … 为 名字」 偷偷开了子作用域(捕获体里的赋值出不去);④ 调用链挂载时机三处不一 (「显式 抛出」的错误两个引擎丢链)。 ⚠️ 它们在两个前端上一直是一致的 —— 只在三个执行器之间露出来, 所以一致性夹具全程绿也照样存在。
- ✅ 新增
tools/check_engines.py(三执行器体检):扫全仓库语料
(含 error_cases,报错文本也是可观察行为)逐字节比三个执行器。 93 个语料 / 对拍 91 / 豁免 2 / 不一致 0(两个前端各一遍)。 改完任何引擎行为都跑它。
- 🚧 R6 进行中:R6.0 侦察与度量已交付(2026-10-02) ——
tools/benchmark.py 与 tools/check_engines.py 都接进了 Rust VM(第四个 执行器)。⚠️ Rust VM 那一列必须扣掉进程启动(实测空程序 8~10 ms), 否则量的是「进程启动快慢」而不是「VM 快慢」。
- 🔴 摸出四个真缺口(全在 Rust VM 一侧):① ~~
遍历是 O(n²)~~
✅ R6.1 已修;②③④ ✅ R6.2/R6.3 已修(见下)(迭代器原来是「列表 + remove(0)」,改成快照反转 + 尾部 pop();n=20000 净耗时 2490 → 4.82 ms,翻倍倍率回到 ×2 线性); ⚠️ 零点要当场量:进程启动成本会漂(8 ms → 85 ms,刚编出来的 exe 被实时扫描),拿旧零点减新数据会把结论读反; ② 捕获 运行期错误 接不住 键错误/索引错误(语言规格明写「基类能抓子类」); ③ 报错时 stdout 一个字都不冲出来(崩了看不到跑到哪);④ 报错渲染缺 源码行/^/调用链(宿主设计,但核心二要求它必须解决 → 按 filename+行号 读源文件最省)。
- ✅ R6.2/R6.3 已把缺口 ②③④ 全部收掉(
docs/设计决策.mdD61): - ② 宿主异常父子表补全(
捕获 运行期错误接得住键错误/索引错误); - ③ 报错时先吐已打印的 stdout(以前只在正常结束时打,崩了看不到跑到哪);
- ④ 宿主自长出与
render()逐字相同的报错渲染(源码行 /^/ 调用链)——
⚠️ 不把源码塞进字节码,而是出错那一刻按 filename 读一次源文件; 读不到就退回「没有源码行」,不崩。
- 🔴 顺手量出一件更大的事:新探针
tools/probe_runtime_errors.py第一次跑,
19 个常见运行期错误形状里 18 个四引擎不一致 —— 两类根因:① Python 侧 漏内部表示(list index out of range 英文、'乙' 键的 repr、 「([],)」 元组 repr)②宿主笼统化(找不到这个名字 等一律报「异常」, 连坐 e.类型 / 捕获 X / 错误码三处)。现已 0/19。
- 📌 判据决定视野:
check_engines.py原来「只在没出错时比 stdout」,
所以缺口③根本比不出来;改成「stdout + 用户看到的报错渲染」之后, 可见问题从 0 个变成 26 个。要比就比用户看到的那一份。
- ✅ R6.6 把最后 5 条也收掉了(用户拍板,
docs/设计决策.mdD63):
4 条「^ 落在哪一列」根本不用动格式 —— 登记里写的「宿主没有 AST」 是当时的猜测、不是事实:两边手里都有那两个位置(GET_ATTR @2:2 / CALL @2:5),差的是「报错时挑了哪一个」。修法是照 Python 的机制来: 让绑定方法值随身带取属性的位置。⚠️ 模块属性(数.开方)是裸 Builtin、两边都用调用点 —— 不能按「名字里有 .」一刀切。 1 条 local_hints 才真动格式:字节码升 v3,版本号四处同步 (有测试钉着)。 📌 教训:登记里写的「原因」是待验的假设 —— 真去修时必须把现象重量一遍, 否则会照着错的原因去做一件大得多、而且并不解决问题的事。 ⚠️ 现在登记只剩 3 条,全是「有意边界」(宿主是单二进制、没有 Python)。
- ✅ R6.7 把 D63 §三 那条登记也收掉了 —— 实例方法「参数个数错」:
树遍历/两宿主报调用点、VM/C VM 报函数定义处。🔴 根因不是一个,是三个 (分散在 Python / C 两种语言里):VM 的 call_function 把绑定位置写死成 firstlineno, col=1;C 侧 jsvm_call_function 里 bind_args(..., 0, 0) 把宿主传下来的调用点丢了;_cb_method_call 没记 _pending_call_site。 修法一律「照树遍历的机制来」:回流入口用调用点。 🔴 顺带:Rust 的用户方法调用静默丢 kwargs(&[], &[]);树遍历的 类构造参数错没有行列。 🔴 还有一个工具链级的(更阴):cvm/build.py 产出的 cvm/bin/jsvm.dll 与 wheel 预编译的 jishi/_native/jsvm.dll 是两份,而 _lib_path() 原 优先后者 —— 改了 C 源码并重建,跑的还是九天前那份、一句提示都没有。 已改成源码树优先(发布链不受影响)。📌 又一次「静默用错产物」。
- ✅ R6.4 已把「同一件事」在 Node 宿主上做了一遍(
docs/设计决策.mdD62): - 报错渲染:Node 现在也长出完整那一份(
┌─+ 源码行 +^+ 调用链 +
提示,与 Python 逐字相同),做法与 Rust 一致(按 filename 现读源文件);
- 类型名细分:
找不到这个名字(E0301)/找不到这个函数(E0302)/
这个东西不能调用(E2006),不再笼统报「异常」「运行期错误」; 输入结束 补码 E2010;
- 🔴 修掉三条「静默出错」(比报错危险得多):
列[9] = 3
(JS 数组自动补长 → 不报错还把列表撑长了)、[1].弹出(9) (给 undefined)、1 < "a"(给 假);
- 🔴 这一轮真正的动作是「把 Node 拉进判据」:两个体检工具以前都只带
「三个 Python + Rust」—— Node 的那些问题一个都抓不到。 拉进来的当天,探针从「0 不一致」变成 11 个不一致。 📌 与 R6.2 换判据是同一条教训:判据的视野有多宽,能看见的问题就有多少。
- 两个宿主的已知缺口登记合成一张
HOST_KNOWN_GAPS(R6.6 后只剩 3 条,全是有意边界;当时是 8 条同构:
4 条「^ 落在哪一列」+ 1 条 local_hints 不在字节码里 + 3 条「宿主不支持 从 python 导入」),且机制上改成两个宿主都对齐了才算「登记过期」。
- ⚠️ 新登记一条语义分叉(待拍板):负索引 —— 三个 Python 执行器
列[-1] 给最后一个元素,两个宿主报「索引错误」(语料里从没用过, 所以一直没发现)。定下来之前,这个形状不要进语料。
- ⚠️ 顺手撞出第 4 个分岔(预存,待拍板):「**遍历时往被遍历的那个列表里
加东西」—— 树遍历 / Python VM / C VM / Node 都是活迭代器(与 Python 一致,会一直循环),只有 Rust VM 是快照。四个对一个是票数不是判据; ⚠️ 别把它写成测试也别进语料**(写了就等于让三个执行器死循环,pytest 会挂住)。
- ⚠️ 「Rust VM 反超 C VM」目前无证据:17 个场景里除
递归斐波那契(7.8x vs
18.3x)、当循环倒计数(7.1x vs 37.7x) 外全面落后。但不是「Rust 不行」—— 现有 Rust VM 是 M27/M33 的兼容宿主,不是为核心重写过的。先量再说。
- ✅ R6 主体续:集合 O(n) → O(1)(
docs/设计决策.mdD65):JSet从裸
Vec<Val> 改成「保序 Vec<Option<Val>> + 哈希索引」——添加 去重 / 包含 / 移除 定位都 O(1),而顺序照旧(集合是有序的,规格里写着)。 ⚠️ 删除必须打墓碑:保序集合删中间元素若前移尾巴就是 O(n)(「删 n 个」仍 O(n²))—— 第一版就这么写的,n=16000 批量删除 1845ms;改成置 None + 空洞过半压缩一次(摊还 O(1))→ 37ms。压缩要同时重建 items 与 index。 🔴 顺带撞出三个既有分叉:① 真 == 1 只有 Rust 给「假」(Val::eq 少布尔分支,且键归一没跟着改 —— 「== 与哈希键是一对」,改一个必须改另一个; Node 侧一直是对的,注释里就写着「Python 里 True == 1」);② 「不能放进集合」 的报错三个说法;③ Node 的提示语不显示(方法表 catch 后重建错误只为补 位置,把 hint 丢了 —— ⚠️ 重建错误对象时要把字段显式带过来)。 📌 真正的教训:tests/cases/ 里一个集合语料都没有,所以集合从未进过 四阶段夹具与全语料体检 —— 这些分叉一个都露不出来(与 R6.4「Node 不进判据」 同类,缺的是语料)。先补 tests/cases/18_集合.jsh 再修代码。 ⏳ 新登记(待拍板):d[精确("1")] —— 三 Python 执行器报英文 unhashable type(内部类名泄漏),Rust/Node 允许。定下来之前不要进语料。
- ✅ R6 主体续二:整数循环 —— 全局变量表 + 整数比较(
docs/设计决策.mdD66):
先量再改(把循环拆段、并把同一逻辑换写法量)发现 —— Rust 的 globals 是 HashMap<String, Val> 且每次 LOAD_GLOBAL 先 names[a].clone()(分配 String) 再哈希查找,而 **C VM 一直是 JVal *globals(按名字下标索引的数组)。 ins.a 就是 names 下标(已验证去重)→ 改 Vec<Option<Val>> + 内建加载时预填。 交错 A/B:当循环 3.42x、纯读写 2.32x、整数累加 2.11x。 🔴 顺手修既有 bug:compare 把所有数值比较都转 f64 → 2 62 > 2 62 - 1 只有 Rust 给「假」(另四个给「真」)→ 整数对整数直接走 i128(既提速又修正确性)。 📌 语料里从没有大整数比较 → 补 tests/cases/19_整数比较.jsh。 ⚠️ benchmark 的口径坑:净耗时 < 启动成本时那次减法会放大误差 (「整数循环累加」显示 55ms,放大规模实测只有 4.2ms/20000 次)→ 这种格子打 ⚠。 📌 精确倍数只认「同机同时段交错 A/B + 足够大的规模」。 ⏳ 没做(如实记):函数调用密集(帧的 2~3 次堆分配)、Val 瘦身 —— 都是"另一件大事"。 🔴 全量回归抓到一处我改出来的回归:候选名池被我缩小(names 只装「本文件 出现过的名字」)→ 打印(长渡([1])) 的「你是不是想写「长度」?」没了;而我自建的 用例恰好避开了这个形状(它把「长度」写进了文件里)。修法照 C VM(pool = 全部内建 + 用户全局)。📌 用例要挑「会让口径分叉的数据」;单项验收绿 ≠ 没坏。 🔴 全量回归抓到一处我改出来的回归:候选名池被我缩小(names 只装「本文件 出现过的名字」)→ 打印(长渡([1])) 的「你是不是想写「长度」?」没了;而我自建的 用例恰好避开了这个形状(它把「长度」写进了文件里)。修法照 C VM(pool = 全部内建 + 用户全局)。📌 用例要挑「会让口径分叉的数据」;单项验收绿 ≠ 没坏。**
- ✅ R6 主体续三:函数调用 —— 帧创建的三处开销(
docs/设计决策.mdD67):
📌 先量(按参数个数拆场景):Rust 一参比零参多 350ns、三参多 595ns, 而 C VM 与参数个数无关 → 差在「每次调用的固定开销」。三处根因: ① instrs.clone() —— 每进一帧克隆整个指令数组(为绕开借用检查)→ 换持有方式:module: Rc<Module>,Rc::clone + 借引用(单这一项 1.41x); ② HashMap<usize, Val> 装参数(1~3 个参数)→ Vec<Option<Val>>,并省掉 中间的值表;③ 每帧两次堆分配 → 帧池。 ⚠️ 帧池最危险两处:locals_set 残留 → 读到上一帧的值(不报错的错答案); cells 复用 → 闭包看到别的函数的单元 → pop_frame 全清、bind_args 重置 (test_m74 钉住)。 ⚠️ 更正 D66 的错误估算:那儿说「instrs.clone() 只占 ~2%」,实际它是最主要的 一处。📌 「某处只占 x%」——不改掉量一遍就别写。 交错 A/B:斐波(24) 1.48x、一参 1.47x、三参 1.44x; 对 C VM 的差距 2.0~2.3x → 1.2~1.5x。 ⏳ 仍未做:Val 瘦身(要动表示、收益未量,独立一件)。
- 🚧 R6 主体续四:树遍历参考执行器(第一个切片)(
docs/设计决策.mdD68):
jishi-rs --walk AST.json —— 直接遍历 AST 求值、不经过字节码,与字节码 VM 互为参考实现(字节码 VM 的天生盲区:字节码生成错了它照着错的跑、"一切正常")。 📌 关键设计决策:吃 AST JSON,而不是让宿主自己 parse —— AST 已是契约产物 (两前端都产它),吃 JSON 就不必把 core 的 pyo3 依赖解耦进宿主 (查过:parser.rs/compiler.rs 其实不依赖 pyo3,只有 lib.rs 那 30 处是包装层), 也与 R5「产物优先给文本」同一思路。 ⚠️ 第一个切片只实现 8 种节点;未实现的节点明确报错,不静默给错值。 Val/binop/display/内建表是共用的(有意如此:要抓的是「编译器 vs 求值」, 不是两份 + 谁对)。 📌 新工具 tools/check_walk.py(进度仪表):语料 70 · 一致 7 · 还没实现 63 · 不一致 0 —— 「还没实现」那列就是下一步的清单。 ⏳ 没做完:① 控制流+字面量 ② 内建调用接上(✅ 见续五)③ 函数与闭包 ④ 类 ⑤ 异常与导入 ⑥ 报错渲染对齐。
- 🚧 R6 主体续五:内建宿主接口 —— 内建只有一份实现(
docs/设计决策.mdD69,
用户拍板走「改签名」那条):内建签名 fn(&[Val], &mut VM) → &mut dyn BuiltinHost, VM 与 Walker 各实现它 → 内建语义只有一份实现(new_builtins() 那张表), 互拍不再夹着「两份 + / 两份 打印」的噪音。 📌 接口是「先把用法列出来」再定的:数了一遍,内建只用到 5 个能力 (format_value / lookup_method / make_iter / call_value / output) → 接口就 5 个方法。⚠️ 加方法要「有意识地加」,有结构测试钉着这条。 ⚠️ 规模比想的大:lib.rs 只有 4 处,标准库有 217 处(23 个文件)—— 批量替换前先断言「所有 &mut VM| 的处数 == 预期」(217 == 217), 形态不同就停下来看清楚(真用 vm 的只有 6 处)。 顺手把 format_value 的「容器递归 + 标量显示」抽成自由函数 format_value_with (两个执行器共用,只有「自定义对象怎么算文本」是宿主特有的)。 ⚠️ 树遍历侧做不到的两个能力明确报错(回调用户函数 / 自定义对象的「文本()」), 不静默给错值。 🔴 接上之后立刻撞出一处真问题:run_walk 把出错前已经打印的内容丢了 (返回 Result<String,_> 而不是元组)—— 字节码 VM 在 R6.2 就统一过 「出错前写进输出流的要吐出来」,于是 02_计算器 在空 stdin 下被判成不一致。 📌 判据扩大覆盖面的那一刻,就是旧实现露馅的那一刻(同 R6.4「把 Node 拉进判据」)。 📌 顺带把树遍历的「找不到名字」候选池补上内建名(长渡 → 提示「长度」—— 与上一轮字节码 VM 那个坑同源)。 ⏳ 顺带发现:字面量 真 / 假(前端编成 Num 且 value 是布尔)与 空(是 Name)还没接 —— 归下一步「字面量」。 📌 进度仪表:一致 7 → 11(还没实现 63 → 60,不一致 0)。 新增 tests/test_m76_walk_builtins.py(22 项)+ 语料 21_内建调用.jsh; 两条结构钉住:walk.rs 里不许有注册内建的动作、BuiltinHost 的方法数必须是 5。
- 🚧 R6 主体续六:树遍历参考执行器 —— ① 控制流 + 字面量(
docs/设计决策.mdD70):
补上 If(否则如果 = 多一条 branch + 单独的 orelse)/ While / For / Loop / Break / Continue / Pass / Compare(含链式)/ BoolOp / UnaryOp / List / Dict / Subscript(读)/ TargetList(解包)/ 复合赋值 += 那一族。 📌 三个「藏在别处」的字面量(照节点类型的直觉写一定翻车): ① 真/假 是 Num 且 value 是布尔(不是 Bool 节点); ② 空 是 Name(前端在编译期换成 none 常量,AST 里只剩个名字); ③ 中断 才是 break(不是 跳出 —— 我第一版对拍脚本就写错,结果两边 一起报「找不到名字」,看起来像通过)。 📌 两处「容易想当然」的语义:链式比较要短路(1 > 2 > d["键"] 的第二节 求值根本不该发生);与/或 结果一定是真/假(1 或 2 给「真」不是 1)。 🔴 顺手修掉一个「不报错的错答案」:unary_neg 原来返回 Val、非数字一律给 空 → -「x」 在三个 Python 执行器上都报「不能取负号」,而 Rust 打印出「空」。 改成返回 Result 并报同一句话;-真 也对齐成 -1。 📌 共用也是共债:树遍历要调这个函数,才发现「共用的那一份」自己就是分叉源。 ⏳ 顺手量出 5 处既有分叉(都没修,登记待拍板):-「文本」 / -列表 (Node 给 NaN / -1,都是不报错的错答案)、-空(Python 那三个打的是 「None」 —— 内部表示泄漏)、循环 "3"(Rust 报错)、循环 3.7(Node 报错)。 ⚠️ 定下来之前这些形状不进语料(22_控制流.jsh 的文件头就写明了避开了哪些)。 📌 仪表:一致 11 → 25(还没实现 60 → 47、不一致 0)。 新增 tests/test_m77_walk_control.py(73 项)+ 语料 22_控制流.jsh; 三条结构/契约钉住:语义函数不许在 walk.rs 里重写、真/假/空 的节点类型、 链式比较短路。
- 🚧 R6 主体续七:树遍历参考执行器 —— ③ 函数与闭包(
docs/设计决策.mdD71):
补上 FuncDef / Return / Lambda + 词法作用域链(walk.rs 的 Scope)。 📌 两套做法、同一套语义:字节码用「编译器算好的槽位 + 单元表」,树遍历用 「读沿链找、写只写当前作用域」(= Python 侧 Environment)—— 能共用的是「判断」不是「存储」:形参绑定(含全部报错文案)抽成了 bind_params,两个执行器都调它。 ⚠️ Val 多了一个变体 Val::WalkFn(不能复用 Val::Func:那装的是字节码的 code_idx),但显示/类型/同一性/可调用判定必须与 Val::Func 完全一致。 顺带:内建现在能回调用户函数(映射/过滤/排序按/归约)。 🔴 最容易写错的一条:「赋值即局部」必须先判(在沿链找之前)—— 一个名字在本函数赋过值,读它就不许穿透到外层。顺序放反的症状是 静默拿到外层的值(不报错、答案错);我第一版就是这么写的,对拍一条就露了。 ⚠️ 配套的「扫本函数赋过值的名字」不能递归进嵌套的 FuncDef/Lambda 体。 另外两处:返回 要穿出循环(写成 _ => {} 会吞掉);默认值在定义处求值。 🔴 语料第一次「问类型」,C VM 的内裤就露出来了:打印(类型(加倍)) 在 C VM 上 打的是内部类名 CvmFunction(另四个都是「函数」)—— 根因是 runtime.type_name 的兜底手写清单漏了 CvmFunction(只有 UserFunction/ VmFunction)。它一直躺着,因为此前没有任何语料拿 类型() 问过函数。 已修 + 补五执行器测试。📌 又是「判据没覆盖到 = 没实现」。 📌 仪表:一致 25 → 39(还没实现 47 → 34、不一致 0)。 新增 tests/test_m78_walk_functions.py(46 项)+ 语料 23_函数与闭包.jsh; 两条结构钉住:walk.rs 不许自己拼形参错误文案(必须调 bind_params)、 「赋值即局部」的判定顺序。
- 🚧 R6 主体续八:树遍历参考执行器 —— ④ 类与继承(
docs/设计决策.mdD72):
ClassDef / Attr / Subscript 作赋值目标全通,顺带接上内建类型的方法 (列.追加(…) 那些)。📌 核心决定:不另造一套「类」 —— 树遍历产出的就是 宿主那个 Class(方法表里放 Val::WalkFn),于是 是实例 / 超() / get_attr / set_attr / display / type_label / identity_key 一行都不用改就能用。📌 加 10 行 pub(crate)、省下几百行重复语义 (而且省下的正是最容易悄悄漂的那种)。 📌 self 用「WalkFn + self_val」表达(字节码侧是 BoundMethod); ⚠️ 显示 / 类型 / 同一性三处必须跟着走(绑定方法仍明确报错 —— Python 里 a.方法 是 a.方法 为假)。 📌 顺带把内建方法表(248 行 / 73 分支)从 impl VM 搬成自由函数 (call_builtin_method(host: &mut dyn BuiltinHost, …))—— 先量出它只依赖 一个宿主能力(call_value,4 处)才敢搬;self. → host.,逻辑一行没动。 ⚠️ 另外两处「差一点静默错」:遍历 自定义对象 必须走宿主的 make_iter (否则「定义了 迭代() 的对象」遍历不了);调用类 = 造实例要在 eval_call 里加分支(新建 甲(…) 只是个普通 Call)。 📌 仪表:一致 39 → 53(还没实现 34 → 21、不一致 0)。 新增 tests/test_m79_walk_classes.py(47 项)+ 语料 24_类.jsh; 结构钉住:walk.rs 不许另实现 get_attr/set_attr/call_builtin_method。 ⏳ 顺手记一笔(没改):新语料里写个叫 文本 的方法会被静态检查的 零误报闸门拦下(name.shadow)——「方法名遮蔽内建」算不算误报可以再议, 本轮先照闸门走(改名 介绍,文本() 钩子改在测试里覆盖)。 📌 仪表:一致 53 → 75(还没实现 2 → 0、不一致 0)—— ✅ 语料里出现过的节点类型全部实现了。 📌 顺手修一个真 bug:宿主把不存在的异常类型名 数值错误 当类型用了三处 → 捕获 值错误 在 Rust 宿主上接不住切片错误(其余四个都接得住)。 📌 共用了一处报错「头行」(error_head)——同一段源码两个宿主曾打出不同的字。 🔴 参考执行器抓到一个真 bug(登记,本轮不修):「捕获体里 返回」时四个 字节码宿主都不跑「最终」块,规范跑(根因在编译器 c_try)。登记在 tests/test_m80_walk_exceptions.py 的一条测试(会过期报红)+ tools/check_walk.py; ⚠️ 定下来之前这个形状不进语料。📌 这正是参考执行器存在的理由。 新增 tests/test_m80_walk_exceptions.py(46 项)+ 语料 25_异常与导入.jsh。
- ✅ ⑥ 报错渲染对齐(D74):
--walk加「源文件路径」参数 → 报错渲染与
字节码 VM 逐字一致(源码行 / ^ / 调用链 / 提示,都走共用的 render_error_with)。check_walk.py 升级成「两个实现完整行为互拍」 (比 stdout + 报错渲染)。新增 tests/test_m81_walk_render.py(18 项)。 🔴 两个实现细节:① 错误对象补位置 —— 共用的 binop/get_item 等返回的错误 不带行列,在 eval/exec_stmt 分派层用「出错节点」位置补(⚠️ match arm 里的 ? 会提前 return 绕过补位置,得改成 .map(...));② 方法调用报错 位置用 BoundMethod 的「取属性」处(不是方法名)。 🔴 登记一个既有分叉(不修):方法调用 / 类实例化的调用链 —— Python 规范 没有方法帧(实例.方法() 走 UserFunction.__call__、不动 _call_stack), 字节码 VM 有;参考执行器站在规范这边。另量到 递归无深度限制(Python 有, 两个 Rust 宿主一个爆内存一个栈溢出)。登记在 tests/test_m81_walk_render.py。
- 📌 R6 主体全部完成(树遍历参考执行器六步 + 字典/集合/整数循环/函数调用)。
下一步:实测定 C VM 去向。 ✅ R6 主体已开工(2026-10-03):第一步字典 O(n²) → O(n)(保序 Vec + 哈希索引;⚠️ 不能直接 HashMap<Val,_> —— Val 的 == 跨类型数值相等, 哈希键要单独规范化成 DictKey)。顺带修 Rust 字典 == 顺序相关、Node 字典 跨类型键两个分叉(docs/设计决策.md D64)。 ⏳ 主体继续:集合 O(n)→O(1)、整数循环/算术 vs C VM、Rust 树遍历参考执行器。 ⚠️ M49b 那个「选型决策」已随备案完成而结掉(方案 b:大陆服务器 + 域名)。 📌 (旧)M56 收完后,下一步:M49a 分发客户端收口 或 M57 性能(C VM 容器读侧, 先量再说)→ M58/M59。 📌 M49a · 分发客户端收口(可插队,与 ICP 备案无关,现在就能做)—— ⚠️ 先纠正一个旧判断:M49 的主体早就做完了 —— jishi 安装 <包> 默认走 https://raw.githubusercontent.com/…/packages/索引.json,实测能装通 (jishi 安装 基础库 → 已安装 1.0.0)。ICP 备案只挡「在中国大陆服务器上、 用域名对外提供下载」这一条路(腾讯云口径:与端口无关), 换香港镜像或只做客户端多源都不需要备案。 ① 多索引回退(默认 GitHub → 备用镜像 → 报错); ② 失败给中文可操作提示(换个镜像 / 用 --local-zip / 检查网络); ③ jishi 医生 加「索引可达性」自检。 → 再往后 M57 性能单独立项(C VM 容器读侧;先量,收益不够就如实写 「不做」)→ M58/M59 AI 可用性深化 + 文档示例。 ⚠️ 战略修正(2026-09-27 复核):~~「分发是『有没有人用』的闸门」~~ —— 现在已有三个入口(Marketplace 扩展已上架 / jishi 安装 能装 / 源码装),真正的闸门是「有没有人知道」,别再把它当阻塞项。 完整规划见 docs/路线图.md §九(含 §十「三条被改判的历史结论」)。 ⚠️ 时间.计时 那个「宿主对象模型」缺口仍未收(M51 记在 test_m51_consistency.py 的 KNOWN_GAPS):它要的是「标准库直接返回的 宿主对象能被上下文协议认出来」,与基石库层是两件事,别混在一起做。 ⚠️ 标准库不做 C / Rust 化(2026-09-25 实测判定,别再提): ① 161 个函数里 127 个是薄包装——底层 math / re / hashlib / json / zlib 本身就是 C 实现,「用 C 重写」等于把 C 换成 C; ② 一次调用的耗时里编组开销占 90%+:数学.开方 每次 5.69µs,其中 math.sqrt 本体只 0.071µs;即使函数体变成 0 也就能快 ~5%; ③ Rust 宿主已经用 Rust 重写了整套标准库(自写正则/SHA/inflate,见 rust/src/stdlib/),实测标准库密集 1932ms vs C VM 435ms——反而慢 4.4 倍; 顺带量到该宿主纯计算慢约 1000 倍(5 万次循环 11.7s vs C VM 10.7ms)—— 它是「最小可运行」,不是性能选项。 👉 真要提性能,靶子是「减少宿主回调次数」,不是「把标准库写成 C」 (同 §2 的结论)。
- ✅ 宿主命名参数缺口 —— M51 已修(2026-09-26)。原状:Node 的
_callValue
拿到了 kwargs 却只写 fn(...args)、Rust 的 Val::Builtin 签名里根本没放 关键字的位置 —— 于是 加密.摘要("abc", 算法="md5") 在两个宿主上都静默用 默认 sha256(Python 给 900150…、两宿主给 ba7816bf…),一声不响。 这是本项目最危险的一类 bug:错了不报警。
- 修法:不在宿主里手抄参数名(那又是「同一件事写三遍」+ 漂移源),而是
tools/gen_stdlib_sigs.py 从 jishi/stdlib/*.py 生成签名表 (node/stdlib_sigs.js + rust/src/stdlib_sigs.rs),两宿主按 「模块.函数」查表把命名参数映射回位置参数。 ⚠️ 键必须带模块前缀:全库有 19 组跨模块同名函数(解析 在 json / 参数 / 日期 都有),只按函数名查会歧义。
- 映射不了一律明确报错(异常类型「类型错误」,与 Python 侧一致,对拍才比得上):
名字对不上 / 被给两次 / 缺必填 / 跳过了前面的可选参数(宿主按位置调用, 中间留空就得补 空 顶上,而 空 未必等价于「不给」—— 宁可报错也不猜)。
- ⚠️ **改了
jishi/stdlib/*.py的函数签名必须重新生成**:
python tools/gen_stdlib_sigs.py;忘了会被 test_签名表与标准库源码同步 直接报红。
- ⚠️ 模块名是编译期拼进函数名的:Rust 走
table!宏的第一个参数
(concat!,零运行时开销)、Node 在 R.Module 构造函数里统一加前缀。 不要去几百处 new R.Builtin(...) / Val::Builtin(...) 手写前缀。
- ⚠️
Val::Builtin的 name 现在带模块前缀("加密.摘要"),
内建函数(打印 等)没有前缀 —— 所以宿主独有内建收到命名参数会走 「查不到签名表」那条路报错,而不是静默。<br> 仍然的边界:选项 形式的函数(纯变参)不在签名表里,命名调用会报错 (不是静默);数学.圆周率 这类常量**没纳入漂移检测(见下条)。
- ✅ 漂移检测已建 —— M51(2026-09-26):
tests/test_m51_consistency.py。
两宿主各加了 --dump-stdlib(由宿主自己报家底,比让测试去正则解析宿主源码 可靠得多:解析一改就崩,还会把注释里的名字也算进去)。测试断言三件事: ① 模块名 + 函数名对 Python 侧(jishi --lang-spec)全覆盖、不多不少; ② 两宿主彼此也对得上(防「只修了一边」);③ 签名表与源码同步。
- 验收方式(都实测过):故意从 Node 的
STDLIB删掉统计→ 3 条报红并
点名「Node 缺:统计」;把 Node 的 mapKwargs 退回「静默丢 kwargs」→ 7 条报红(含 md5 vs sha256 那条)。检测真的会响,不是空壳。
- 顺带补齐 13 个静默缺失的函数(M50 及更早留下的洞):Node 补
数学
(正弦/余弦/正切/对数/常用对数/角度转弧度)+ 文本(填充/截断)+ 迭代 (累积/组合/排列/笛卡尔积)共 12 个;Rust 补后两类共 6 个。三边逐字节一致。
- ⚠️ 已知缺口只剩一个:
时间.计时(两宿主都没有)。它返回带方法的对象,
还要支持上下文协议(用 … 为 t: 脱糖成 进入 / 退出),而两宿主的 ctxMethod 只认「Jishi 实例」和「内建容器」—— 标准库直接返的宿主对象 走不通。这不是补一个函数的事,是宿主对象模型的缺口。 归属 M52/M53:把 计时 改写成基石模块后它天然是 Jishi 实例, 五引擎都能跑,这类缺口会结构性地消失。缺口写在测试的 KNOWN_GAPS 里 —— 不掩盖,也不让它静默。
- ⚠️ 没纳入检测的边界(如实记):①
数学.圆周率这类常量
(--dump-stdlib 只报 Val::Builtin,常量一致性靠别的对拍测试); ② 同名函数的参数个数与顺序(本测试只比名字;参数个数一致性 留给 M52 的基石库层从根上解决)。
- ✅ 语言元数据的漂移检测已建 —— R7.2(2026-10-06):
tests/test_m90_langspec_rust.py。
宿主加了 --lang-spec / --ai-card(由宿主自己报家底),与 jishi --lang-spec / jishi --ai-card 逐字节一致;另有一条不经 CLI 的 结构化对比(直接比 ai.build_lang_spec() + content_hash 的算法)。
- ⚠️ Rust 侧那张表是生成的:
rust/src/langdata.rs由
python tools/gen_langdata_rs.py 一次性搬运(关键字 / 运算符 / 内建文档 / 标准库 229 条函数说明 / 错误码 / 等价 Python 写法)。改了 Python 侧元数据必须 重跑生成器再重编宿主,忘了会被 test_m90 当场报红 —— 这就是拍板①说的 「Rust 侧显式表 + 两侧各 --dump 自报家底来比」。
- 📌 输出要逐字节对齐 Python 的
json.dumps(..., ensure_ascii=False, indent=2)
—— 键顺序也是内容的一部分;content_hash 还是「紧凑 + 键排序」的 sha256 前 8 位。所以 langspec.rs 自带一个保序 JSON 值 + 两个序列化器 (缩进版 / 排序紧凑版)。
- ⚠️ Windows 上 Python 的 stdout 默认把
\n翻成\r\n,逐字节比会「看着
全红」;oracle.jishi.cli._setup_io() 已经 reconfigure(newline="\n"),所以走 CLI 那条路是干净的(自己写脚本比对时别忘了这一点)。
- ✅ 语言服务器已搬进 Rust —— R7.3(2026-10-06):
jishi-rs lsp(rust/src/lsp.rs)。 - 判据两条都要跑:
tests/test_m91_lsp_rust.py(33 条报文逐字节对拍 +
独立判据 + 计时 护栏);以及编辑器自带的 editors/vscode-jishi/verify-lsp.js(两边各跑一遍,各 25 项)。 JISHI_BIN=<二进制> JISHI_ARGS="lsp" node editors/vscode-jishi/verify-lsp.js。
- ⚠️ 改 LSP 行为要跑这两条;只跑一条会漏(
verify-lsp.js只看「有没有结果」,对拍才看内容)。 - 🔴 有一条 Python 的怪行为是「有意照抄」的:**词法错的
didOpen会让整个
on_did_open 抛出、文档根本不注册(IncrementalDocument 在构造里就分词, 失败即抛出,dispatch 又把通知的异常吞掉)。想让「编辑器能打开一份有词法错的 文件」,那是先改 Python 侧**、再两边一起搬 —— 别只在 Rust 这边「顺手修好」, 那样报文就不一致了。判据里两个方向都钉着(语法错要能开 / 词法错不注册)。
- 📌 没有搬
incremental.py:Rust 全量重分词 + 重解析就比 Python 的增量
快 5~6 倍(20003 行每次编辑 1224ms → 207ms,同机同时段实测)。少一份状态机 = 少一处会漂的地方。这条要重开得靠新实测数据。
- 📌 顺带抽了两个共享模块:
rust/src/scope.rs(作用域分析,checker 与 LSP 一份,
对应 Python 的 scope.py)、rust/src/jsonw.rs(保序 JSON 写出器, --lang-spec 与 LSP 共用)。
- ✅ CLI 与 MCP 已搬进 Rust —— R7.4(2026-10-06):
rust/src/run.rs(CLI 主入口)
与 rust/src/mcp.rs(jishi-rs mcp)。
- 🔴 位置参数的语义变了:
jishi-rs <文件>从「字节码 JSON」改成 源码
(与 jishi <文件> 对齐),字节码要走 --load-bytecode。tests/ 与 tools/ 里 25 个文件、35 处调用点跟着补了开关。加新测试时别再直接喂字节码。
- 两个闸门:
python tools/check_cli.py(89 语料 × 3 模式 × 两引擎逐字节;
另有 10 个显式登记的跳过 —— 6 个读标准输入、3 个 从 python 导入 (单二进制的既定能力边界)、1 个 时间.计时(m51 的 KNOWN_GAPS)+ 非确定性 跑分输出;补掉一条就回去删一行)与 tests/test_m92_cli_rust.py / tests/test_m93_mcp_rust.py(对拍 + 不依赖 Python 的独立判据)。改 CLI/MCP 行为三处都要跑。
- ⚠️ Windows 上给子进程的 stdin 造管道会稳定失败(
ERROR_PIPE_BUSY,
os error 231)——jishi-rs --mcp-spawn-probe 是复现入口,实测只有 stdin 方向有问题(stdout/stderr 换 null 都不影响)。run_script 因此用临时文件 当 stdin(--stdin 语义不变,只是不走管道),临时目录写不进去还会退到当前目录。
- ⚠️ Python 侧两条如实登记(不是宿主的问题):① CLI 的沙箱超时会丢输出
(死循环时 stdout 全空 + 退出码 1;0.5 秒的超时要跑十几秒 —— 泄漏的守护线程抢 GIL),所以「超时」那一格不进对拍,只钉宿主;② mcp_server 的 stdout 以前 没像 cli 那样 reconfigure(newline="\n"),Windows 上吐 CRLF(已修)。
- 📌 顺手修掉 6 处「只有逐字节对拍才照得出来」的差异:
① 宿主把 值错误("x") 当立刻抛而不是构造异常(令 e = 值错误("x") 在宿主上直接崩,三个 Python 执行器都能跑);② TraceFrame 少 col (调用链里那一项恒为 null);③ 树遍历的「找不到名字」hint 给 ""、 另两个执行器给 null;④ 空源码行的报错渲染多印两行(Python 是 if self.source_line:,空串为假 → 连源码行带 ^ 一起跳过);⑤ --ast 少印 _super_ok(Python 侧是 setattr 动态挂的,排在 vars() 末尾); ⑥ 两个路径串联函数都没做归一:文件.路径拼接 要用 PurePosixPath (吃 ./空段、保留 ..、恒用 /)、路径.连接 要用 pathlib.Path (吃 ./空段、保留 ..、平台原生分隔符,且「只带根不带盘符的段要 保留已有盘符」:C:\a / /b → C:\b)。宿主以前是手拼 / PathBuf::push, 于是 文件.路径拼接(".", "x") 给 ./x、路径.连接(".", "x") 给 .\x —— 而前者还会渗进「找不到文件「X」,无法读取」这种用户可见的报错。 📌 判据在 test_m92(每组都是「对拍 + 写死的独立期望」两半)。
- 🔴 教训一(这轮最值钱的):「位置参数语义」这种全局**改动,
调用点数不准。 我按「25 个文件 / 35 处」收的工,全量回归又抓出 15 处 漏的 —— 它们藏在 _run([str(bc_p)]) 这类局部助手里,变量名不在我 grep 的模式里,check_engines / check_walk / check_cli 三条闸门一条都碰不到 (它们自己都用了新语义)。这种改动只能靠全量回归收口,不能靠清点。**
- 🔴 教训二:别在跑测试的同时
cargo build。 二进制被替换会让正在跑的用例
集体报红(那次的「235 failed」全是假的)。跑回归 / 量性能前先确认没有构建在后台跑。
- ✅ DAP 已搬进 Rust —— R7.5(2026-10-07):
rust/src/dap.rs(jishi-rs dap)+
rust/src/debugger.rs(调试会话)+ walk.rs 的调试钩子。
- 判据:
tests/test_m94_dap_rust.py(对拍 + 不依赖 Python 的独立判据),
以及 build/_dap_cmp.py(开发期的对拍台,同样需要同步客户端)。
- 🔑 Rust 版与 Python 版的线程模型不一样、报文一样:Python 是「主线程跑协议 +
脚本线程跑程序」;Rust 的 Walker 不是 Send,所以是「读线程只做分帧 + 主线程既跑协议又跑程序」,暂停时由 PauseDriver 串行泵消息。
- ⚠️ 对拍必须用「同步客户端」(发一条、等它该来的应答/事件,再发下一条):
程序自由跑时收到的读请求,Python 当场就答、Rust 下一个语句边界才答 —— 那是时序差异(两边都合理)。用事件把两边卡在同一点,报文序列才可比。
- 🔴 改了
walk.rs的钩子/call_walk_fn签名:call_site从Option<i64>
变成 Option<(i64, i64)>(行 + 列)。顺带补齐了树遍历 trace 的 col (Python 的 Frame(name, line, col) 三个字段都在,宿主以前给 null)。
- 🔴
打印的写出粒度是契约:Python 的打印是print(*[...]),CPython 的
print 对每个实参各写一次、分隔符与结尾各写一次。拼接结果一样,但 DAP 把 每次 write 变成一条 output 事件 —— 条数不同报文就对不上。 加「会被 DAP 看见的输出」时,先想清楚写了几次。
- ⚠️ 没搬的一处(如实登记):字节码执行器的调试 ——
jishi-rs dap/
jishi-rs 调试 的 --执行器 vm/cvm 明确拒绝(不静默降级)。
- ✅
jishi-rs 调试(交互式命令行)已补齐 —— R7.5 收尾(2026-10-07):
会话核心 R7.5 已就绪,这一轮补的是命令表(断点/取消断点/清空断点/变量/ 查看/栈/源码/帮助 + gdb 风格别名)与「读一行」的驱动(LineDriver —— PauseDriver 当初就是为它留的口子)。判据 tests/test_m95_debug_cli_rust.py (20 个场景逐字节对拍 + 独立判据 + stdin 路径)。
- 🔴 顺带抓到并修掉一个宿主真 bug:宿主把内建渲染成
<内建函数 打印>、
异常类型渲染成裸的 值错误,而 Python/Node 是 <内建 打印> / <异常类型 值错误>。旧测试没覆盖到(test_m32 的 _agree 只比 Python VM 与 Node,没算上 Rust)—— 是 变量 全部 的对拍照出来的。已补进 test_m32_boundaries.py::test_display_of_builtin_and_module(带 Rust 一起比)。
- 📌
变量 全部在模块顶层要列出整套内建:Python 把内建装进模块环境,
树遍历的内建在 Walker::builtins 另一张表 —— Walker::builtin_entries() 是纯显示用的合并(不往作用域里塞东西)。
- ✅ 包管理已搬进 Rust —— R7.6(2026-10-07):
rust/src/packages.rs+
jishi-rs 安装/卸载/列表/发布/索引/检查。
- 判据:
tests/test_m96_packages_rust.py(六个子命令逐字节对拍 +
不依赖 Python 的独立判据 + file:// 本地镜像走完整条安装链)。
- 🔴
检查换回了包管理的语义:R7.1-c 当初把源码 lint 挂在检查上,
但 Python 侧的 检查 是包依赖冲突、源码检查叫 源码静态检查(别名 静态检查)。R7.6 把 检查 让回包管理,源码检查改用 源码静态检查 —— 否则等 Rust 顶上 jishi 这个名字时,脚本会静默走错命令。 调用点跟着改了 test_m86 / test_m87。
- 🔴 一处刻意保留的差异:打包出来的 zip 字节不同(Python 用
ZIP_DEFLATED、宿主用自己那份 stored 写出器,与 压缩.打包 同一条)。 所以 发布 / 索引 --checksum 的体积与 sha256 两边对不上(测试里归一化 这两处再比)。内容完全一样,stored 是合规 zip(拿 Python 的 zipfile 当裁判验过)。不影响安装。
- ⚠️ 另外两处已登记的文案差异(都在测试里写明了):① 缺必填参数时
Python 走 argparse(usage: … + error: …,换行跟终端宽度有关), 宿主打的是一行中文用法 —— 只对齐退出码 2;② 网络错误的具体文案 (Python URLError: <urlopen error [WinError …]> vs 宿主「发请求失败…」), 外层结构逐字一致。
- 📌 复用的底层:
zip.rs(build_stored/read_all)、crypto.rs
(sha256_hex)、http.rs(fetch_bytes,顺带把 client::request 的返回 从 String 改成原始字节,文本调用方走新的 request_text)。
- 📌
file://是预期用法(本地镜像)—— 判据靠它做到「不联网也能验完整条链」。 - ⏳ R7 六件(R7.1–R7.6)全部完成。
- 🚧 R8 已开工 —— R8.0 侦察 + R8.1 wasm 引擎 + R8.2 试玩页换 wasm:目标 = 单二进制 +
wasm + 退役 Python/JS,收尾后进 0.3.x。验收 体积 ≤5MB / 启动 ≤100ms (开工实测 4.60MB / 92ms —— 两条都擦线,要盯着别退;现有 Python 发行包 20MB)。
- R8.0 侦察在
docs/路线图.md§11.10(含 4 条待拍板 → 已拍板)。 - 🔴 高风险单点验证当场做了、且通过了:
rustup target add wasm32-unknown-unknown
之后,jishi-frontend / jishi-ffi 零改动就能编到 wasm; 不需要 wasm-bindgen(裸 C ABI + 手写 shim 就够)。
- ✅ R8.1 已交付:
wasm/crate(ABI +site/jishi-wasm.jsshim)+
tools/build_wasm.py。判据 tests/test_m97_wasm_playground.py: 12 个语料在 wasm 里的结果 JSON 与 jishi-rs --sandbox --json-result 逐字节一致 + 独立判据(写死期望 / 死循环被预算兜住 / 默认预算有限 / 无限递归被接住 / 体积)。
- 🔴 wasm 上踩到的两个坑(都写进代码注释了):
① std::time::Instant::now() 会 panic(time not implemented on this platform) —— 而 panic = "abort" 下它表现成一句英文 RuntimeError: unreachable, 看着完全不像时钟问题。⇒ sandbox.rs 的计时在 wasm 上恒 0, 真实耗时由 shim 用 performance.now() 量。 ② 没有线程 ⇒ sandbox 的「另起线程 + 超时」用不了(std::thread::spawn 直接 panic)⇒ 走 run_sandboxed_direct + 步数预算。
- 🔴 步数预算做成了 cargo 特征
budget,只有wasm/开:
「一步」= 回跳一次(循环走完一圈),在跳转指令处判。 ⚠️ 这是量出来的决定:判断放在指令循环顶(每条指令都过一遍)实测 1.068x; 改成「只在回跳处判」还是 1.058x(同一轮里「同一份二进制的两份拷贝」只差 1.005x,所以不是噪声)。⇒ 干脆把整个功能关进特征:特征关掉时与加它之前 逐位同效(1.000x),只有 wasm 那条路付那 6%(换来「页面不冻住」)。
- ⚠️
opt-level="z"+ LTO 那套没敢用:配上之后 wasm 只有 16KB 且一跑就 trap
—— 根因是上面那个 Instant::now(),不是 profile。但现在它没被验证过, 所以 wasm/Cargo.toml 里暂时不写 [profile.release](1.47MB), 要瘦身得先单独量过再开。
- ⚠️ 一处如实说明:wasm 上判「跑飞了」的单位是循环圈数,
所以兜不住没有回跳的失控(无限递归)—— 那种撞 wasm 栈上限, 由 shim 接住并翻成「程序跑飞了」,不是引擎给的错。
- ⏳ 接下去 ~~R8.2~~ → R8.3(三平台发行包)
→ R8.4(退役 Python/Node + 改名 jishi)→ R8.5(收尾)。
- ✅ R8.2 已交付(2026-10-08):在线试玩页换成 wasm。
site/playground.html
不再内嵌 Python 源码、不再用 Pyodide:现在只引用 jishi-wasm.js(胶水) + jishi_wasm.wasm(引擎),页面 432KB → 7KB,不再从 CDN 拉约 10MB 的 Pyodide。 ⇒ 在线试玩与 jishi / 编辑器是同一份引擎(Rust 前端 + 字节码 VM + 原生标准库)。
- 判据分两层:
test_m10_playground.py管生成出来的 HTML(无 Pyodide 残留 /
引用 wasm 三件 / 体积量级);test_m97_wasm_playground.py 有一条端到端 —— 拿 DOM 桩把生成出来的那个页面在 Node 里真跑(真 shim + 真 wasm), 点「运行」比输出文本。📌 只比字符串「页面里有 JishiWasm.load」是糊不住错的 —— 渲染逻辑、错误分支、事件绑定全不在字符串里。
- 🔧 顺带对齐了协议形状:胶水与 wasm 自己兜的两种意外(wasm trap / 返回不是
合法 JSON)以前是 errors 数组,与引擎的 error 对象不同形,页面得判两支 ⇒ 统一成引擎那份 {ok:false, error:{code,title,message,line,col,hint}}。
- 📌 实机验证:无头 Edge 加载本地站点(HTTP 取到 1.47MB wasm)→ 引擎就绪 →
示例跑出正确输出。fetch 得到 + 实例化 + ABI 自检这条路是通的 (file:// 直接双击打不开 —— 浏览器不许读 wasm,页面里已写明改用本地服务器)。
- ✅ 基石库层已建 —— M52(2026-09-26):纯逻辑标准库只写一份
(jishi/stdlib-jishi/*.jsh,目前有 统计),编译期展开进主程序, 五个引擎共用同一份字节码。完整设计见 docs/设计决策.md D45。
- 机制一句话:
导入 统计被就地改写成
统计 = $造模块("统计", __基石库_统计__()),其中 __基石库_统计__ 是把模块源码整体包成的外壳函数(返回导出字典)。 三个执行器与两个跨语言宿主一个字都没改 —— 它们跑的只是普通字节码。
- ⚠️ 写
jishi/stdlib-jishi/的模块要守三条:① 引别的模块写成
导入 X 为 _X(导出会排除下划线开头的名字);② 报错类型与 Python 侧 一致(对拍比类型);③ 热路径别放这里 —— 实测比宿主层慢 25–38 倍 (C VM 上 5.77µs → 159.67µs),原因是库代码内部每次调内建在 C VM 上都是 一次宿主回调。判据:只用列表/字典/文本/数字 + 现有内建能表达的才进这层 (编码 就因为要 UTF-8 编解码而搬不了 —— 基石没有 序数/字符 内建)。
- ⚠️ 改动落点:
jishilib.expand要在compile_program与
Interpreter.run 两条路径各调一次(漏一条就是「产物里的库和实际跑的 库不是一份」);$造模块 是内部内建,三处都有 (runtime.new_builtins / node/runtime.js / rust/src/lib.rs)。
- ⚠️
JISHI_NO_JISHILIB=1关掉整层(导入回落宿主层)—— 诊断、对拍、
性能对比都靠它;tests/test_m52_jishilib.py 用它做「迁移没改行为」的对照。
- ⚠️ 宿主回调里用户能触发的错误要显式抛
JishiError:别让 Python 原生
异常冒到 cvm_bind._fail —— 那层会包成「内部错误(宿主回调)」, 用户看到的是 TypeError: 'int' object is not iterable 这种天书。 M52 就是这么抓到 _cb_iter_new 里 _Iter(o) 的构造在 try 外面的。
- ✅ 方向四 · 性能优化(M46–M48)已收官:M46 迭代批量 + 回调路径 + 容器写入
延迟合并写;M47 C 侧原生字符串(拼接 2.94x / 比较 12.86x)+ AST 缓存; M48 方法调用融合(1.83x / 端到端 1.56x)。判定不做:M47 ② C 侧 JSON、 M48 ① 常量折叠与窥孔、M48 ③ 文件 I/O 快路径(都量过,见 docs/路线图.md)。 剩余短板:容器读侧(字典查找 0.96x)——要治它得让 C 侧真正持有容器内容 (别名 + 逃逸问题,属另一个量级的改造)。
- ✅ 方向五 · 自主分发(M49)已落地(2026-09-29,ICP 备案完成后启动):
官网 + 自建分发源上线 <https://基石.cn/>(源站=大陆轻量云 Nginx,前面 EdgeOne CDN)。 jishi 安装 改成自建源优先、GitHub 兜底;索引与包都在境内。 🔴 关键教训:HTTPS 521 的真因是「源站只听 80、而 CDN 对 HTTPS 走 HTTPS 回源」 —— 判据是源站 access_log 里有没有那条请求,别猜配置。 细节见 docs/路线图.md §9.3.6、docs/设计决策.md D52 与 .workbuddy/memory/操作细节.md 附一/附三。 ⚠️ 有 SSH 就优先 SSH(用户名 ubuntu、免密 sudo):云助手会拦一堆命令。 ⚠️ 本机到该服务器的 80/443 TCP 之前不通,2026-09-29 复测已通(22/80); 验收以「域名 + 源站日志」为准。
- ✅ 两个「已记录待修项」已清零(M40):① f-string 改用内部保留名
$文本;
② 树遍历补齐「赋值即局部」。当前没有已知的未修分歧——tests/test_cvm.py 里那条 xfail 也按约定转成了正常断言。 下一批待办(M41 起)见 docs/路线图.md §3.6 与 docs/真实项目.md 的别扭点清单。