里程碑明细(M0–M45)
本文件是逐里程碑的详细记录,从 README 拆出来(2026-09-23)以便 README 专注「能力」。时间线速览见
PROGRESS.md, 未来规划见路线图.md(内部文档,未公开),逐版本变更见CHANGELOG.md。 ⚠️ 本文件记到 M45。M46 起(方向四 · 性能优化)的明细写在CHANGELOG.md的[0.2.0]段(按「① 迭代批量 / ② 回调路径 / ③ 列表延迟 合并写 / ④ 字典延迟合并写 / M47 原生字符串」分节,含实测表与如实记的代价), 那时起 README 也改成只保留「能力导向」内容。所以别把本文件的止点 当成「M46 没做」——时间线以PROGRESS.md为准。 每个里程碑都有测试护航;git 历史见git log。
里程碑全部有测试护航,git 历史见 git log:
| 阶段 | 内容 | 状态 |
|---|---|---|
| M0 | 词法/语法/AST/中文报错/树遍历解释器/CLI+REPL | ✅ |
| M1 | 对象方法系统/链式比较/REPL增强/教程前3章 | ✅ |
| M2 | 标准库6模块/教程全7章/尾随逗号 | ✅ |
| M3 | Python 生态桥接 + 关键字参数 | ✅ |
| M4a | 字节码编译器 + Python 参考虚拟机 | ✅ |
| M4b | C 字节码虚拟机内核 + ctypes 绑定 | ✅ |
| M5a | 异常处理(尝试/捕获/最终/抛出) | ✅ |
| M5b | 面向对象(类/继承/新建) | ✅ |
| M6 | 工程化:CI/编辑器插件/文档站/打包 | ✅ |
| M7 | 表达力打磨 + 容错解析 | ✅ |
| M8 | AI 原生工具链(语言卡/JSON 报错/few-shot/评测集) | ✅ |
| M9 | 安全沙箱 + 统一协议 + MCP Server + C VM 嵌入 | ✅ |
| M10 | 生态扩展(json/日期/正则/网络标准库 + 本地包 + 浏览器试玩) | ✅ |
| M11 | 性能与分发(C VM 热路径下沉 + wheel 内带预编译 DLL) | ✅ |
| M12 | 评测驱动闭环(评测集扩到 50 题 + --llm 接入 + 自愈评测 + CI 基线) | ✅ |
| M13 | 跨语言字节码分发(JSON 字节码 + Node/Rust 绑定) | ✅ |
| M14 | 包管理与生态(安装命令 + 依赖解析 + 4 新标准库) | ✅ |
| M15 | 真实项目驱动 + 语言收敛 1.0 | ✅ |
| M16 | 发行版与安装体验(PyInstaller + 安装脚本 + 首次体验) | ✅ |
| M17 | 排错体验补全(递归翻译 + 调用栈回溯 + 协议补全 + REPL 源码行) | ✅ |
| M18 | 表达便利(切片 / 命令行参数 / 最小最大多参数 / 遮蔽治理) | ✅ |
| M19 | 发行改进(Windows 安装程序 + 三平台自动构建) | ✅ |
| M20 | 性能:批量 JVal 转换优化(C VM 迭代/函数调用大幅提速) | ✅ |
| M21 | 开发者工具(LSP 语言服务器 + 代码格式化) | ✅ |
| M22 | 包生态(默认静态索引 + 发布工具 + 钻石依赖冲突检测) | ✅ |
| M23 | 语言功能扩展 A 档(在·不在 / 是·不是 / 超() / 断言 / 是实例 / 类变量) | ✅ |
| M24 | 语言功能扩展 B 档(集合 / 迭代协议 / 切片赋值 / 类型标注 / 匿名函数 / 数据流水线) | ✅ |
| M25 | 变长参数 */** + 星号解包(唯一需要改 C 的语法扩展,已同步 Node/Rust 宿主) | ✅ |
| M26 | 语言功能扩展 C 档(上下文管理器 用 … 为 + 文件对象 / 自定义对象打印 / 精确小数) | ✅ |
| M27 | 值→文本统一(空/真/假 中文;打印·文本·插值·回显收敛到一处实现) | ✅ |
| M27 收尾 | Rust 宿主首次真正跑起来(修 cargo shim + 两个阻塞级 bug),跨宿主对拍补齐 | ✅ |
| M28 | Rust 宿主修可变容器(Rc<RefCell>,原为「静默算错」)+ 补齐 在/是 等比较运算 | ✅ |
| M29 | Rust 宿主补齐到 Python 侧:方法调用 / 高阶方法 / 集合 / 文件与上下文 / 精确小数 | ✅ |
| M30 | JS(Node)宿主补齐到 Python 侧:集合 / 高阶方法 / 成员测试 / 同一性 / 切片 / 标准库 / 输入 | ✅ |
| M31 | JS 宿主标准库补齐到与 Python 同宽:路径 / 文件 / 日期 / 时间 / 加密 / 压缩 / 网络 | ✅ |
| M32 | 边界清单逐条实测核实:修五处静默算错(大整数 / 整数回绕 / 精度舍入 / 显示)+ 补两处明确报错 | ✅ |
| M33 | Rust 宿主标准库补齐到与 Python 同宽:14 个模块 / 约 130 个函数,零依赖自写正则引擎、四种摘要、zip 容器 + inflate、WinHTTP 网络 | ✅ |
| M34 | D 档第一批:模式匹配 匹配/情形 + 枚举 枚举(解析期脱糖,三执行器 + 两宿主零改动) | ✅ |
| M35 | 发布工程与收尾(语言图标进安装包与三平台发行物 + 类体校验统一到解析期 + 测试并发安全 + 文档一致性整理 + 路线图 v2) | ✅ |
| M36 | 语法面冻结:条件表达式(脱糖)+ 位运算内建 + 字典.合并 + 宿主同步 + 探针「无悬案」(42/50 可用,8 项全判定) | ✅ |
| M37 | 主线 B1:调试器(断点 / 单步 / 看变量 / 看调用栈 / 出错现场),树遍历执行器 | ✅ |
| M38 | 主线 B 收尾:LSP 增量同步 + REPL 增强(历史/Tab 补全/多行)+ 格式化器边界 | ✅ |
| M39 | 主线 B5:字节码执行器的调试器 + 用基石写静态站生成器(三执行器产物逐字节相同) | ✅ |
| M40 | C VM 调试器(三执行器会话逐字对拍)+ 「赋值即局部」补齐 + f-string 遮蔽修复 + 五类真实项目(行数比基线 1.27) | ✅ |
| M41 | 标准库第一批 5 模块(容器/迭代/参数/日志/测试,五路同宽)+ 内建 带下标/配对 + 遍历 多变量解包 + 错误归类修正(行数比 1.21) | ✅ |
| M42 | 语言服务补六项(格式化 / 重命名 / 快速修复 / 签名提示 / 工作区符号 / 语义高亮)+ VS Code 扩展升级为语言客户端 | ✅ |
| M43 | 调试器接进编辑器(jishi dap)+ 语法高亮与片段补齐 + 打包 .vsix 上架 Marketplace | ✅ |
| M44 | 机器可读的语言事实(错误码 35 条 / 静态检查码 / 等价 Python 写法 83 条)+ jishi 源码静态检查(零误报) | ✅ |
| M45 | AI 跑分表(45 题 × 2 模型,一次通过率 82.2% / 88.6%)+ MCP 工具面 3 → 7 个 + 三平台 GitHub Releases | ✅ |
测试现状:2066 项全部通过(python -m pytest tests/),其中 130 余项是三执行器 对拍(树遍历 / Python VM / C VM 输出逐字节一致),另有跨语言宿主对拍 (Rust 101 + 58 + 37 项、JS 62 + 43 项,都与 Python VM 逐字节一致,含三个真实示例 项目的端到端对比)。
M45 已交付:AI 跑分表 + MCP 工具面补齐(方向三 · AI 可用性第二轮)
① tools/eval_ai.py:题集 + 跑分表——回答一个问题: 「一个没读过本项目文档的模型,能不能用基石把活干对?」
45 道题分四类(语法 12 / 标准库 15 / 调试 10 / 重构 8),每道题自带标准输出 作为唯一判据:模型只看语言规格(不看仓库源码、不看题解)写代码,然后真跑基石, 输出逐字对得上且退出码为 0才算通过;跑错就把中文报错回灌让它改,最多 3 轮。
| 指标 | 含义 |
|---|---|
| 一次通过率 | 第 1 轮就正确的占比 |
| 最终通过率 | 允许回灌报错改 3 轮后正确的占比 |
| 平均修复轮数 | 通过的题平均用了几轮(1.0 = 全都一次过) |
跑分结果(判分=输出逐字相同且退出码为 0;语言规格指纹 92700b8c):
| 模型 | 一次通过率 | 最终通过率 | 平均修复轮数 | 跳过 |
|---|---|---|---|---|
deepseek-v4-flash-0731 | 82.2%(37/45) | 93.3%(42/45) | 1.12 | 0 |
glm-5.3-flash | 88.6%(39/44) | 100.0%(44/44) | 1.11 | 1 |
两个模型都在只读语言规格的前提下把 45 题做到 93%–100%,说明题目考的 「中文关键字怎么写、标准库有什么、报错怎么修」对模型是可达的。glm 那道跳过 不是模型的问题(HTTP 504 接口故障,已按纪律记「跳过」、不进分母)。
两条判分纪律值得单说:
- 崩溃不算过:
当 i <= 长度(数据)那道题输出对了、但退出码 1——只看 stdout
会把这种「先打印对了再越界」判成通过。这题是写题集时真撞见的。
- 题集先自检:调试题的「缺陷代码」必须真的失败,重构题的「原始代码」
必须真的跑出标准输出(--自检 不花钱、不调模型)。题目本身错了,跑分表就是假的。
限流(HTTP 429 / 503)按指数退避重试 3/6/12/24 秒——不能把「接口忙」混进 「模型不行」的结论里;确实拿不到结果的题记为「跳过」,不算通过也不算失败。 结果里同时记录语言规格指纹,只在同一指纹下跑分才可比。
② MCP 工具面从 3 个补到 7 个(jishi.mcp_server),把 agent 干活需要的四条路走全:
| 工具 | 作用 |
|---|---|
run_script / eval_expr | 沙箱跑片段(原有) |
describe_language | 取语言卡(原有) |
check_source | 静态检查(M44 checker 的同一份实现) |
lookup_error | 查错误码(E2002 → 中文标题 / 触发类 / 修法) |
lookup_stdlib | 读标准库文档(列模块 → 查单函数签名) |
format_source | 格式化源码(官方风格) |
全部工具的事实都从语言规格(ai.build_lang_spec())与既有实现取,不另抄一份—— 规格变了工具自动跟着变。format_source 另加一道解析前置校验:格式化器遇到 语法错不会报警(它只按缩进重排文本),会返回个半成品;工具层先解析一遍, 语法错就如实报错并给出中文提示+行列号(如果 真 → 「这里少了冒号」),不让 agent 以为拿到的是干净代码。
M44 已交付:机器可读的语言事实 + 源码静态检查(方向三 · AI 可用性第一轮)
① --lang-spec 补齐三块(jishi --lang-spec 是模型读语言事实的入口):
- 错误码表(35 条):
code/class/title,含两个「运行期临时挂上」的码
(E2010 输入结束、E2100 程序被中断),并用 internal 标出执行器内部信号 (E299x,不该展示给用户)。做这张表时才发现 E2008 被两个异常类共用 (RunAssertionError 与 RunCustomError)——已修:断言错误留 E2008, 自定义异常改 E2009。
- 静态检查码表:
源码静态检查会输出的五类问题码,与检查器同一份目录
(加了新检查项却忘了登记,测试会红)。
- 等价 Python 写法:关键字 29 / 运算符 29 / 内建 26 条映射(
与→and、
遍历→for、长度→len……)。模型最大的先验是 Python,这层显式映射比让它猜 准得多;对应关系有差异的照样点明(反转 → reversed(列表)/文本[::-1]、 集合 → Python 的 set 无序而基石这个保持首次出现顺序)。
--json-errors早在 M8.3 就有,本轮把它钉进测试:字段契约
(code/title/message/line/col/hint)、走 stderr(stdout 留给程序自己的输出)、 有修法时带机器可读的 fix。
② 新增 jishi 源码静态检查(别名 静态检查)——不运行程序,只看源码:
| 问题码 | 检查 |
|---|---|
name.undefined | 未定义名(与编辑器实时诊断同一份实现) |
name.shadow | 遮蔽内建(模块级绑定撞上内建函数 / 异常类型) |
name.unused | 未使用变量(函数体内绑定、全文件从未读取) |
compare.self | 可疑相等(甲 == 甲 这类恒定比较) |
可传文件或目录(默认当前目录,递归找 .jsh)、--json 机器可读输出、 发现问题退出码 1(供 CI 与提交钩子)。
口径是「宁可漏报,不可误报」——静态检查一旦误报,用户就学会忽略它。 第一版在仓库自己的语料上误报 38 条:导入 随机 被当成「遮蔽标准库」(33 条)、 脱糖内部名 __遍历项_57_5__ 被当成「未使用变量」(5 条),还有把 表.追加(1) 里的 表 当成没读过。逐条判为误报后收窄口径,现在 examples / tests / stdlib 的 30+ 个真实文件零误报;tests/error_cases/ 里 35 个故意错例中静态可查的 16 个全部逮住——另外 19 个要跑起来才知道(除以零、索引越界、赋值前读局部……), 不假装静态检查能做它做不到的事。
③ 作用域分析抽成 jishi/scope.py:编辑器诊断与命令行检查共用一份实现, 免得漂移成「编辑器说没事、命令行说有事」(有测试专门钉这条一致性)。
M43 已交付:调试器接进编辑器(jishi dap)+ 扩展打包
jishi dap:把现有调试器(树遍历 / 字节码 / C 虚拟机三个执行器)接到 Debug Adapter Protocol——编辑器侧于是有了断点、单步、变量面板、调用栈、 监视求值,而底层还是同一个调试内核(命令行 jishi 调试 与 DAP 两条入口 共用同一套会话)。
- 协议面:
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:31 项协议级 + 3 项契约);
唯一差异是 C 虚拟机不支持在暂停处求值——明说原因并给替代方案,不给错值。
- 顺带给调试器补了两样:按帧取变量(编辑器可点调用栈的任意一帧)与
运行中暂停(在下一个语句边界生效)。
扩展已上架 VS Code Marketplace(publisher jishi-lang,扩展 jishi-lang.vscode-jishi):在编辑器的扩展面板里搜 「基石语言」 就能装, 装完任意窗口、任意文件夹打开 .jsh 都能用,不必再走「F5 + 扩展宿主窗口」 那条路(从宿主窗口打开新文件夹会另开一个普通窗口,那个窗口不加载开发中的扩展)。 也可以自己打 .vsix(构建产物、不入库),命令见下文「编辑器接入」。
语法高亮与代码片段补齐并对齐语言事实:内建从 15 个补到 28 个 (含 M41 的 带下标 / 配对)、新增标准库模块名(19 个)与异常类型 (9 个)着色、关键字补 7 个(匹配 / 情形 / 枚举 / 是 / 不是 / 不在 / 用)、 片段补 7 条(遍历解包 / 带下标 / 模式匹配 / 用上下文 / 插值文本 / 导入 / 推导式)。 对齐由 tests/test_m43_highlight.py 钉住——以后新增内建却忘了同步高亮表, 测试会红,不会悄悄过时。
M42 已交付:语言服务补能力(格式化 / 重命名 / 快速修复 / 签名 / 工作区 / 语义高亮)
方向二(IDE 可用性)的第一轮:把 jishi lsp 从「诊断 + 补全 + 悬停 + 跳转」 补到「编辑器里该有的都有」,并把 VS Code 扩展从纯语法高亮升级成语言客户端。
| 能力 | 说明 |
|---|---|
textDocument/formatting | 把已有的格式化器接到 LSP 上(一份实现两处用)。语法有错时不动——正在打字,别把半截代码搅乱 |
rename + prepareRename | 整篇改名:定义处 + 所有引用。只做模块级符号(理由见下) |
codeAction | 把报错里的「修法建议」变成可点的快速修复:if → 如果、len → 长度、True → 真… |
signatureHelp | 打 ( 出形参表并高亮当前实参;文档解析不了时靠 token 兜底(那正是最需要它的时候) |
workspace/symbol + 跨文件跳转 | 全工作区搜符号;本地找不到定义时去别的文件找唯一命中的同名符号 |
semanticTokens | 关键字 / 标准库(标 defaultLibrary)/ 用户函数与变量 / 形参 / 属性,比纯语法高亮准 |
VS Code 扩展升级为语言客户端:editors/vscode-jishi 之前只有语法高亮与片段 (连 main 都没有),现在能起 jishi lsp 并把 11 项能力逐条接上。
- 零运行时依赖:没引
vscode-languageclient,自己写了lsp-transport.js
(分帧 + 请求应答 + 通知分发,约 150 行)。理由与 C 内核、正则引擎同一套取舍: 能自己写清楚的就别引依赖——.vsix 也因此小得多。
node verify-lsp.js可脱离 VSCode 自检:用扩展同一份传输代码真的起一个
jishi lsp,把每项能力点一遍(25 项断言)。装进编辑器之前就能发现「点了没反应」。 这条自检也进了回归(tests/test_m42_vscode_client.py)。
三条有意的边界(写进 editors/vscode-jishi/README.md,不粉饰):
- 重命名只支持模块级符号(函数 / 类 / 顶层变量 / 导入名)。函数形参与函数内
局部变量不支持——AST 不记形参位置,而且没有作用域分析就改名会把「另一个 函数里同名的局部变量」一起改掉,那是改坏用户代码。做不到就返回 null (编辑器显示「无法重命名」),比给一个错的编辑安全得多。
- 跨文件跳转只在唯一命中时生效:同名符号出现在多个文件就放弃——跳错地方
比跳不过去更费时间。
- 工作区符号索引有封顶:最多 400 个
.jsh、单文件 512KB,跳过.jishi
(第三方包缓存)与隐藏目录。做全项目数据库得上后台任务,不该卡住编辑器。
M41 已交付:标准库第一批 + 遍历 解包 + 错误归类修正 + 性能定位
方向一(语言能力)的第二轮:补标准库、修诊断归类、量性能。
标准库补 5 个模块(五路同宽:三执行器 + Node + Rust):
| 模块 | 内容 | 治的是 |
|---|---|---|
容器 | 计数 / 最多 / 按值排序 / 分组 / 取前 / 取后 / 分块 | Python Counter 式统计;日志统计 原先手写「有就加一、没有就置一」 |
迭代 | 分组 / 滑窗 / 去重 / 展开 / 取前 / 取后 / 求和 / 计数 | 遍历序列的常用整理动作 |
参数 | 新建 / 选项 / 标志 / 用法 / 解析 | 命令行选项解析(--名字 值、--名字=值、--标志、缺参报错) |
日志 | 级别们 / 设级别 / 取级别 / 设前缀 / 调试 / 信息 / 警告 / 错误 | 带级别过滤的输出,上线时调高一级就全安静 |
测试 | 用例 / 相等 / 不等 / 为真 / 为假 / 近似 / 包含 / 抛出 / 统计 / 汇总 | 「期望什么、实际什么」说清楚;一个用例失败不中断其它用例 |
模块名是「容器」不是「集合」:
集合是内建函数名(集合([1,2,3])去重造 集合),同名模块一导入就把它遮蔽——正是 M40 修掉的 f-string 遮蔽坑同一类问题。 这条已写进设计决策(D35):标准库模块名先查一遍内建名与关键字。
内建补 带下标 / 配对(同 Python 的 enumerate / zip,用起来不用导入):
jsh遍历 序, 名 在 带下标(["甲", "乙"], 1): # [[1,'甲'], [2,'乙']]
打印(序, 名)
遍历 甲, 乙 在 配对([1, 2, 3], ["a", "b"]): # 取最短 → [[1,'a'], [2,'b']]
打印(甲, 乙)
遍历 支持多变量解包(遍历 名字, 年龄 在 对们:):走解析期脱糖 (临时循环变量 + 循环体首句解包赋值),三执行器与两宿主一行都不用改。
错误归类修正:整数("abc") 原先抛「类型错误」,Python 是 ValueError—— 写 捕获 值错误 接不住。现在按 Python 归类:文本转不动 → 值错误; 类型本身不对(列表/字典)→ 类型错误,与既有的 "abc".转整数() 归到同一类。 待办清单 里那句 捕获 异常 已收窄成 捕获 值错误。
路径.递归列出(目录, 后缀):Python 一个 rglob 的事,原先要手写递归。
性能定位(C VM):按 M20 的做法先 cProfile 定位,不猜。结论是瓶颈仍是 ctypes 边界(占比 58%–87%),其中「批量迭代」回调里有一处两趟遍历 (先 all(...) 预扫描、再构造)——改成一趟(边判边装,遇非整数抛内部哨兵 回退)。实测:
| 场景 | 优化前 | 优化后 |
|---|---|---|
| 整数循环累加 | 9.5x | 10.5x |
| 函数调用密集 | 13.3x | 15.7x |
| 字典操作 | — | ctypes 边界占 86.8%(下一步的靶子) |
字符串拼接仍是 0.50x(架构边界:C 侧字符串是 HOST 句柄,每次拼接要回调 Python),如实记录、不当缺陷修。
真实项目收口:用新模块替换掉绕道写法(日志统计 的计数字典、排序跑分 的 对齐与小数位、待办清单 的下标取项),行数比 1.27 → 1.21:
| 项目 | 场景 | 行数比 |
|---|---|---|
| 网页采集 | 网页采集 | 1.02(最接近) |
| 日志统计 | 数据处理 | 1.18 |
| 待办清单 | CLI 工具 | 1.19 |
| 成绩分析 | 数据处理 | 1.21 |
| 排序跑分 | 算法练习 | 1.24 |
| 基石自述站 | 教学演示 | 1.26(差最多) |
| 加权平均 | 1.21 |
顺带做的两件体检(都不是计划内的):
examples/ai/few-shot.md的 48 段示例从没跑过——逐段执行发现 4 段是真错
(变量名撞内建、闭包计数器撞「赋值即局部」、用了不存在的解包遍历与 字典.键值对())。已修,并加测试逐段钉住(片段不自包含的按「环境缺失」 放行,语法/API 错误必须报出来)。喂给 AI 的样本本身就是代码,必须进测试。
- 别扭点清单复盘:十条里 7 条已解决,另有 2 条**其实是「能力早就有、只是
不知道」**(数学.四舍五入、文本.左对齐、集合 的交并差)。这暴露了一类 新问题——真实项目卡住的未必是「语言缺能力」,也可能是「有但找不到」, 正好是 M42–M45 两个外功方向的靶子。
M40 已交付:C VM 调试器 + 「赋值即局部」+ f-string 遮蔽修复 + 五类真实项目
穿插项收尾:M39 欠下的三件事 + 主线 C 的场景铺开。
C VM 的调试器补上了(B5 最后一格):给 C 侧加了四个只读入口——语句起始表 (单独一张 int32 数组,不动跨语言公开的指令宽度)、语句钩子、帧元信息、帧变量 导出。三个执行器的会话记录逐字相同(tests/test_m40_cvm_debugger.py 对拍)。 唯一没做的:就地求值(C 侧没有「在任意帧上求表达式」的入口),会话会明确说 「C 虚拟机暂不支持就地求值」,而不是给个错值。
bashjishi 调试 脚本.jsh --执行器 cvm # 三个执行器,命令完全一样
树遍历的「赋值即局部」补齐(M39 挖出的那条明确分歧):函数里给外层同名 变量赋值时,树遍历原先按「先向外找、找到就用」静默算出另一个结果,字节码执行器 却报错。现在进函数时给「本函数赋过值的名字」占上未绑定哨兵——三个执行器连报错 文案都逐字相同(文案是单一来源 runtime.unbound_local_error)。M39 那条 xfail(strict=True) 已转成正常断言。
f-string 被 导入 文本 打断(M39 记录的待修项):插值原先脱糖成内建 文本(值),而 文本 是本项目最常用的标准库名——一句导入就让所有插值报错。 现在脱糖到 $文本:$ 在词法阶段就是「无法识别的字符」,用户写不出来, 所以永远不会被遮蔽。顺带把内部名从语言规格 / 编辑器补全里过滤掉 (runtime.is_internal_builtin),免得 $文本 被当成可写的函数推荐给用户。
主线 C:五类真实项目 + 行数比基线。examples/projects/ 补齐五类场景: 数据处理(成绩分析 / 日志统计)、网页采集、CLI 工具(待办清单)、 教学演示(基石自述站 + 教程十课)、算法练习(排序跑分)。每个项目配一份 只用标准库的 Python 参考实现,tools/loc_ratio.py 统计行数比:
| 口径 | 结果 |
|---|---|
| 有效行(去空行去注释)加权比 | 1.27(基石 ÷ Python) |
| 最接近的一个 | 网页采集 1.02(主要是解析与字符串处理,两边几乎同构) |
| 差得最多的一个 | 日志统计 1.56(卡在字符串拼接与聚合写法上) |
口径、逐项数据与「别扭点清单」(写这五个项目时真正卡住的地方)见 docs/真实项目.md。
M39 已交付:调试器支持字节码执行器 + 用基石写一个静态站生成器
主线 B5:字节码执行器的调试器。树遍历那条路 M37 就通了,难的是栈机—— 它只知道指令,没有「语句边界」事件。做法是让编译器说话:在每个语句的 第一条指令上打一个标记,VM 主循环只在标记处回调调试器。标记只打首指令是 刻意的:循环的回边(遍历→FOR_ITER、当→条件、循环 n 次→计数器判断)都落在 语句中部,于是循环头只停一次、循环体每轮都停——与树遍历的暂停点严格对齐。
bashjishi 调试 脚本.jsh # 默认树遍历
jishi 调试 脚本.jsh --执行器 vm # 字节码执行器,命令完全一样
jishi 调试 脚本.jsh --执行器 cvm # C 虚拟机,命令同样一样
验收靠「逐字对拍」:同一份脚本、同一组断点、同一串命令,两个执行器的会话 记录一字不差(含变量表、调用栈、出错现场、退出 时 最终 照跑)。 C VM 的调试器判定为暂不做(帧与局部槽都在 C 侧,要结构体字段 + 宿主回调 + 中断约定,而收益只是「同样的体验再支持一个执行器」)——理由与设计草图写在 docs/调试器.md。
主线 C 开头:用基石写一个静态站生成器。 examples/projects/基石自述站/生成.jsh 读仓库里的真数据(版本号来自 pyproject.toml、测试数与能力探针来自 README.md、 里程碑来自 PROGRESS.md、模块规模与示例数是当场统计的),生成一套介绍基石的 静态网页。它同时是三件事的验收:真实项目能不能写、三个执行器产物是否一致 (逐字节相同)、调试器有没有靶子。
顺手修掉四个潜伏缺陷(共同点是:不报错、文档没写、一写就踩): 跨行三引号字符串的收尾引号独占一行时不补 NEWLINE(多行字符串整个特性原先不可用)、 类体里的方法定义行与空块 : 行两个执行器都停不住(语句行 却说它们可停)、 类变量求值顺序与源码顺序不一致(带副作用时两边执行顺序不同)、 导入 文本 会遮蔽内建 文本(...) 从而打断所有 f-string(尚未修,已记录)。
详见 docs/调试器.md 与 docs/设计决策.md (D30/D31)。
M38 已交付:工具链收尾(LSP 增量同步 / REPL 增强 / 格式化器边界)
主线 B「可信赖的工具链」在这一版收齐(B1 调试器见上一节)。
LSP 增量同步(jishi lsp):声明增量同步(客户端只发改动的那一段),并按行 复用前缀重新分词——编辑只影响改动行之后的内容。实测每敲一个字符的分词成本: 18001 行改末行 359.7ms → 8.5ms、1801 行 33.4ms → 0.5ms(改中间约快 1.9 倍, 因为后缀仍要重算;parse 也仍是全量的,如实标注在 docs/工具链.md)。 另有版本乱序保护,以及按 LSP 默认 UTF-16 码元换算的编辑范围。
REPL:↑/↓ 翻历史且跨会话(~/.jishi/repl_history.txt)、Tab 补全 (关键字 / 内建 / 标准库 / 已定义变量,模块. 给函数)、多行编辑更准 (引号没写完也续行,判定交给真词法器)。Windows 自己实现逐键编辑, POSIX 交给 readline,非终端退回 input()(脚本行为不变)。
格式化器:修掉四个真问题——字符串里的 # 被当注释、括号续行里的注释被吃掉、 带 BOM 的文件直接崩、jishi 格式化 <目录> 甩英文 traceback。顺带修掉一个潜伏的 词法器 bug(顶格的括号续行会被整行跳过)。仓库自己的 examples/ 与 tests/cases/ 全部合规(--check 全绿),这是「格式化器风格 = 项目风格」的验收。
详见 docs/工具链.md。
M37 已交付:调试器(断点 / 单步 / 变量 / 调用栈)
bashjishi 调试 脚本.jsh # 停在第一句,之后听你指挥
jishi 调试 脚本.jsh --断点 12 # 直接跑到第 12 行
jishi 调试 脚本.jsh -b 12 --命令 "变量;查看 单价 * 数量;继续" # 自动跑(演示/脚本化)
命令用中文(c/s/n/o/b/v/p/bt 是别名):
| 命令 | 作用 |
|---|---|
继续 / 单步 / 下一步 / 跳出 | 跑到下一个断点 / 进函数 / 不进函数 / 跑完当前函数 |
断点 12、取消断点 12、清空断点 | 断点的增删查 |
变量 / 查看 表达式 / 栈 / 源码 | 看当前帧变量 / 就地求值 / 调用栈(带行号)/ 源码上下文 |
程序出错时会先打调试现场(出错那一帧的变量 + 调用栈),再打正式的中文报错—— 「错在哪」与「错的时候变量是什么」两段互补。
两条边界如实说明:目前只支持树遍历执行器(Python VM / C VM 是栈机, 没有天然的「语句边界」事件,要另做一套),块关键字行(否则:/捕获:/最终:) 不能设断点(加断点时明确提示,不静默无效)。钩子是只读的:带调试器跑与不带, 程序输出逐字节相同(有用例钉住)。详见 docs/调试器.md。
M34:D 档第一批 —— 模式匹配与枚举
路线图对 D 档的定调是「重特性,先写最小原型测投入产出比」。这一轮挑了 模式匹配和枚举做原型,两个都走通了解析期脱糖,于是 三个执行器 + 两个跨语言宿主一行都不用改。
匹配 分数: 枚举 状态:
情形 90: → 优秀 待办 = "todo"
情形 80, 85: → 良好 完成 = "done"
情形 如果 分数 >= 60: → 及格
情形 其他: → 继续努力 打印(状态.待办) → 状态.待办
| 特性 | 脱糖成 | 改动量 |
|---|---|---|
| 模式匹配 | __匹配_N__ = 待匹配值 + 如果/否则如果/否则 链 | 只动 parser.py |
| 枚举 | 类 X:(初始化/文本)+ X.成员 = 新建 X(…) + X.全部 = […] | 只动 parser.py |
两个设计点值得记:
- 待匹配值只求值一次:
匹配 取下一个():落到临时变量,不会因为分支多
就跑多次(测试里锁住了)。
- 枚举成员是实例而不是裸值:所以
打印(颜色.红)给颜色.红(靠文本()
方法),还能取 .名字 / .值。若成员直接是 1,打印出来就是 1, 枚举「用名字代替魔法数字」的意义就没了一半。
加关键字撞掉了既有 API
匹配 一关键字化,两处立刻炸:标准库函数 正则.匹配(M33 刚写、 真实项目在用)解析不了;真实项目里的变量 匹配组 被「关键字粘连检查」 拦下。修法是两条通用措施,不是特例补丁:
.后面的属性名允许关键字(属性名位置本来就没歧义)——
以后加关键字不必再担心撞既有 API。变量名/参数名/字典键仍不许(与 Python 一致)。
匹配/枚举加进粘连检查的跳过表——它们和用/是一样是
常用词前缀(匹配组/匹配器、枚举值/枚举类),粘连提示是净损失。
顺带撞出宿主侧三处既有缺陷
枚举脱糖要用「类体外给类设属性」,一写就撞出宿主本来就有的坑(都不是本轮引入):
| 缺口 | 表现 | 修法 |
|---|---|---|
| Node / Rust 都没有类变量(M23.3 一直没同步) | 类 A: 里写 x = 1 报「不能给「<类 A>」的属性「x」赋值」 | 补 getAttr/setAttr 的类分支——Python 的类变量本来就是靠 SET_ATTR 实现的,字节码零改动 |
Rust 继续 之后再也 中断 不了 | 报「中断/继续没有对应的循环」 | dispatch_signal 多弹了一次循环块(弹的动作归 POP_BLOCK) |
Rust 的 类型() 给错名字 | 实例给「对象」、类给「类」,而 Python 给 A 实例/类 A | 补齐 + 三侧统一绑定方法名(此前三个都不一样) |
第二条尤其隐蔽:单支/两支 如果 里看不出来,必须有「继续 过了还要用 循环」才暴露——情形 脱糖恰好生成了那种形状。
D 档其余的 ROI 结论
生成器(脱糖路线走不通:要状态机/协程,三执行器实现方式完全不同, 且三大真实项目一个都没用到——「读大文件不炸内存」已由文件按行迭代解决)、 多继承(要 C3 线性化 + 重新设计 超(),与「简单」的定位冲突)、 全局声明 / 删除变量(成本低但需求弱)——都先搁置,理由写在 docs/设计决策.md 的 D26。
M33:Rust 宿主的「零依赖标准库」
Rust 宿主原本只有 数学/文本/随机 三个标准库模块(而且函数不全), 这一轮补到 14 个模块、约 130 个函数,与 Python 侧同宽。
M41 起是 19 个模块:新增
容器/迭代/参数/日志/测试, 仍是五路同宽(三执行器 + Node + Rust)。
Cargo.toml 的 [dependencies] 仍然是空的——所以重活全是自己写的:
- 正则:自写回溯引擎(字符类、量词与非贪婪、分组、
(?=)/(?!、
锚点、\b、反向引用)。Python 的 re / JS 的 RegExp 在零依赖下都用不了。
- 加密:自写 md5 / sha1 / sha256 / sha512。算法规范很死(常数表都在标准里),
自己写反而好对拍;拿 hashlib 当裁判,abc、空串这些公开向量逐字节一致。
- 压缩:手拼 zip 容器(本地头 / 中央目录 / EOCD / CRC-32)。
打包写 stored(不压缩)——零依赖下没有 DEFLATE 压缩器,而 stored 在规范里本来就合法;解压躲不掉(别人打的包默认都是 deflate), 所以 inflate 是完整实现的(三种块都支持)。测试还双向互操作: Python 打的 deflate 包 Rust 解得开,Rust 打的包 zipfile.testzip() 校验通过。
- 网络:Windows 上绑
winhttp.dll(extern "system",系统库、不是 crate)
→ https 也能用;其余平台裸 TcpStream(http:// 可用,https:// 明确报错,绝不静默降级成明文)。
日期/时间 还要绕两处 std 没有的东西:本地时区(绑 GetLocalTime / localtime_r)与 strftime 子集(%U/%W 照 CPython 公式)。
探针全绿之后,真实项目又撞出三块
14 个模块补完、113 例对拍 0 不一致之后,按 M30 定下的标准拿 examples/projects/ 的真实项目跑 —— 又撞出三块:
| 缺口 | 表现 |
|---|---|
BUILD_SLICE(切片)整条指令没有 | 排名[::-1] 报「未知指令 48」(M30 只补了 Node) |
输入() 内建不存在 | 三个真实项目里两个要读标准输入 |
列表.排序 静默失效 | Val::partial_cmp 只处理数字与精确小数,字符串/列表返回 None,而排序把 None 当成「相等」→ 排了等于没排 |
第三条最隐蔽:不报错、不错值,只是顺序不对(成绩分析的总分排名全乱)。 顺带修了 表格.排序按 的倒序 —— Python 的 sorted(..., reverse=True) 对并列项保持原顺序,而「排完再整体反转」会把并列项也倒过来。
教训:写完新模块一定要拿真实项目验一遍。「探针里没有的用例」 就是盲区(M30 的切片、M33 的排序都是这么撞出来的)。
对拍还暴露了三处「跨宿主不一致」
Python 侧有几处在 Windows 上做换行转换,于是同一份程序在 Python 侧输出 CRLF、在 Node / Rust 宿主输出 LF:CLI 输出(_setup_io 显式 newline="\n")、 系统.执行(改成按字节收再解码)、json.读文件/写文件(补 newline="")。
另外两处「内部实现细节漏给用户」也修了:正则.查找全部 多分组时漏出 Python 的元组(基石没有元组类型,统一成列表)、类型(表格) 给 _table 类名 (加类型名钩子,三个宿主统一显示「表格」)。
M32:核实「剩余边界」,把静默算错清零
docs/embed.md 里那份「剩余边界」清单,逐条横向实测了一遍——因为文档 会过时(上一轮就撞过:embed.md 给 JS 的成员测试标了 ✅,实际根本没实现)。 探测结果分三类,两类都动手了:
一、两条过时描述(删掉)
- 「JS 的
1与1.0无法区分」——字节码里的小数常量本来就带t: "float"
标记,decodeConst 早就按它包成 FloatBox 了。
- 「JS 的错误渲染没有源码行」——M30 已规范化成中文。
二、五处「静默算错」(修掉)
| 现象 | 根因 | 对策 |
|---|---|---|
9223372036854775807 显示成 ...776000、2 ** 64 算错 | JS 的 number 是双精度 | 必要时切 BigInt(任一边是 BigInt,或结果超出安全范围);字节码里超范围的 int 常量按字符串编码 |
9223372036854775807 + 1 给 负数 | Rust 的 i64 回绕 | 提到 i128 + checked_*,到边界报错 |
2 64 给 0** | ctypes 边界静默截断(C 侧已有溢出回落,是回落算完之后丢的) | _jint 里检查范围报错(JVal 的 16 字节布局不能动) |
打印(类) 给 [object Object] | 那几个类没有 toString | 七个类补 toString(),一处定义三处生效 |
| 「加减乘超 28 位」三边给不同的数 | 只有 div 做了 context 舍入 | 补齐 add/sub/mul + 按 CPython 的 __str__ 实现科学计数法 |
三、两处明确报错(补上)
- Rust 的类型标注校验:编译器早已把校验发射成函数的前导字节码
(检查实参),Rust 侧没实现这个内建——带标注的函数直接报「找不到名字」。
- Rust 的
是/不是对类与函数:创建时分配递增序号(Clone跟着复制),
等价于 Python 的 id()。绑定方法仍明确报错(Python 里 a.方法 是 a.方法 为假,Rust 侧判会得真——宁可明确不支持)。
顺带发现(未动,已如实记进 docs/embed.md):树遍历不支持类体里写 通过;JS 宿主没同步 M23 的「类变量」;类型标注表里的 空/函数 语法上写不出来(是关键字)。
M31:JS 标准库补齐到与 Python 同宽
M30 把 JS 宿主补到「5 个常用模块」,这一轮把剩下 7 个补完: 路径 / 文件 / 日期 / 时间 / 加密 / 压缩 / 网络。补完的结果是 examples/projects/ 里的全部项目都能在 JS 宿主上跑 —— 包括上一轮 还明确写着「跑不了」的「网页采集」。
三块各有各的坑,都是「照着 Python 写反而会错」的类型:
- 压缩:Node 没有内置 zip。
zlib.deflateRawSync/inflateRawSync
只管 deflate 流,本地文件头(30 字节)、中央目录项(46 字节)、EOCD (22 字节)、CRC-32 都得按 PKWARE 规范自己拼。所以这一块的测试不只对拍 输出,还拿 Python 的 zipfile 当裁判:Node 打的 zip 要被 zipfile 读出来 (zf.testzip() 校验 CRC),Python 打的 zip 要被 Node 解开 —— 双向都测。
- 网络:Node 没有同步 HTTP ——
fetch是异步的,而基石没有await,
标准库不能变成异步函数。方案是 spawnSync(process.execPath, ['-e', 脚本]) 起一个自身的子进程跑异步请求,结果经 stdout 回传。零第三方依赖, 也不要求系统装了 curl。代价是每次请求多一次进程启动(本机约 40ms), 换来与 Python 侧 urllib 一样的同步调用形态。
- 日期时间:JS 没有
strftime,toLocaleString又依赖系统 locale,
于是自己实现了一个子集(%U/%W 的周号照 CPython 的公式:新年第一个 周日/周一之前的日期算第 0 周)。时间.睡眠 用 Atomics.wait 同步阻塞。
顺手改掉 Python 侧两个跨平台不一致:
- 文件读写改成「换行保真」:Python 的文本模式在 Windows 上会把
\n
悄悄写成 \r\n,于是「同一份程序在两个平台产出的文件字节不同」,也与 Node 的 fs(不转换)对不上。标准库统一显式 newline="" —— 基石的定位是 「同一份程序到哪都一样」,那就得从字节层面保证。
- 值→文本统一:
文件.写文本/写行、加密.摘要之前走 Python 的
str(),于是 写文本(p, 真) 写出的是 True(而 文本(真) 给 真)。 改成 jishi_repr,与 M30 修的 文本.拼接 / 格式化 一致。
路径模块还有两个细节:后缀 不能直接用 path.extname(pathlib 对 a. 与 .bashrc 都算无后缀,而 JS 给 '.');Windows 上 Python 的 Path.__str__ 总吐 \ 而 JS 的 path 保留输入里的分隔符,所以 连接 / 父目录 的返回值 要显式规范化。
M30:JS(Node)宿主补齐到 Python 侧
Rust 补完补 JS。这次先写了个横向探测脚本:把 39 个用例在 Node 与 Python VM 上各跑一遍逐条比对,实测出 37 处不一致,再按实测范围补 ——不靠印象猜。探测立刻暴露了两件「原来早就坏了」的事:
- 成员测试
在/不在与同一性是/不是在 JS 侧根本没实现:
vm.js 的 CMPOP 只映射了 0–5(==…>=),M23.2 的语义比较码 6–9 落成 undefined,于是 2 在 [1, 2] 报「不支持的比较「undefined」」。 这与 Rust 侧 M28 暴露的是同一个问题——加语义比较运算符时,宿主侧那张 「码值映射表」是漏点重灾区。而 docs/embed.md 当时还给这两行标了 ✅。
Boolean([])是true,而 Python 的bool([])是False——
不特判,如果 列表: 的判断会整个反过来(静默算错级别)。 同理 === 是引用比较,[1, 2] == [1, 2] 原先给假。
补完这七块之后,我把 examples/projects/ 的真实项目拿来在 Node 上跑, 又撞出三块(探针有盲区):切片(BUILD_SLICE 这条指令 JS 侧压根没有, 对[::-1] 报「未知指令 48」)、二元运算的类型分派(JS 的 + 对数组是 字符串拼接、% 的符号与 Python 相反)、标准库(表格 / 文本 / json / 正则 / 系统,以及此前「永远返回空串」的 输入())。
于是验收标准固定下来:examples/projects/ 里的真实项目能在宿主上 原样跑出与 Python 逐字节相同的结果(含退出码)——这比任何探针都硬。
M29:Rust 宿主补齐到 Python 侧
把 Rust 宿主从「核心链路可跑」补到与 Python 侧逐字节对拍一致(剩下的 都是明确报错,不再有「静默算错」)。三块一起做:
- 用户方法调用(
对象.方法()/初始化/ 继承覆写 / 递归方法)。
前提是把 Instance 也换成 Rc<RefCell<…>>——否则 自身.字段 = 值 改的是副本、静默失效(与 M28 的容器是同一类坑)。 新增 VM::call_value:内建/method 能同步回调用户函数。
- 列表高阶方法:
映射/过滤/排序按/归约。实现要点是
回调前先快照容器,否则「拿着 RefCell 借用去回调」会撞借用冲突。
- 集合 / 文件与上下文 / 精确小数:集合(有序去重,11 个方法)、
打开() 真句柄 + 用 … 为、零依赖手写 BigInt 的精确小数 (28 位有效数字、ROUND_HALF_EVEN,精确("1")/精确("3") 与 Python 的 Decimal 逐字节一致)。
动手写第 3 块时抓到 Rust 侧两个真 bug(都不是本次引入的):
execute收到Err(e)直接return Err(e),绕过了异常分发——于是
尝试/捕获/最终 对内置错误(除零/越界/类型不符)完全失效。
返回不跑 finally(缺 Python 侧的_return_via_finally),所以
「用 块里 返回」会让文件不关。
两条都按 Python 的块栈语义补齐,tests/test_m27_rust_host.py 里各有一条 对拍锁住(含 中断 穿过 用 块时先清理)。
M28:Rust 宿主修掉可变容器
上一版留了一处「静默算错」:Rust 侧 列表.追加 改的是副本,原变量不动、 推导式返回空列表 —— 不报错但结果是错的,比直接报错危险得多。
根因是 Val::List(Vec<Val>) 是值类型、Clone 是深拷贝。 改成 Rc<RefCell<Vec<…>>>(Val::clone 只复制指针)后,方法里的 borrow_mut() 就能改到共享数据,令 b = a 也成了真正的别名。
一个容易漏的配套改动:Rust 版把「列表自身」当迭代器(iter_next 会 remove(0))。以前靠值拷贝侥幸正确,共享之后如果不显式快照, 遍历 x 在 a 会把 a 逐元素吃掉。所以 make_iterator 与 unpack_values 都改成取快照——test_rust_iteration_does_not_consume 专门锁这一点。
顺带补齐 Rust 侧完全没实现的两种比较运算(写「按首字母分组」这个真实 惯用法时才撞出来):
python如果 k 在 分组: # 成员测试:列表按值、字典按键、文本找子串
打印(p 是 q) # 同一性:数字文本按值,列表字典按身份
类/实例/函数的 是/不是 明确报错而不是猜:它们在 Rust 里是值类型、 没有身份可谈,硬按「同内容即同一」算会给出和 Python 相反的答案——那又是一次 静默算错。
M27 收尾:Rust 宿主首次真正跑起来
起因很小:cargo 命令「执行成功但什么都不做」。查下去发现 ~/.cargo/bin/ 下 13 个文件(cargo.exe / rustc.exe …)都是 0 字节 —— rustup 在 Windows 上把这些做成指向 rustup.exe 的硬链接,链接坏掉后退化成 空文件,执行它没有输出且 rc=0,极像「命令被沙箱吞了」(前两轮就是这么 记的,此处更正)。重建硬链接后 cargo 1.98.1 恢复正常。
第一次真正把 Rust 宿主跑起来,立刻抓到两个阻塞级既有 bug:
Flow::Return语义混淆 → 函数调用整个不可用。它同时表示「本帧结束,
请弹帧」和「刚压了新帧,别弹」,于是每次函数调用后新帧立刻被弹掉, 外层去执行那个还没拿到实参的 CALL,fn_base 下溢 → panic。 修前连 函数 加一(x):返回 x + 1 + 打印(加一(1)) 都跑不了。
MAKE_FUNCTION默认值对齐按全部形参算,有*参数时错位
(Python 侧在 M25 修过同一个坑,Rust 漏了)。
顺带更正一处夸大表述:Rust 宿主自 M13.3 起写着「最小可运行」,但 tests/ 里只有 Node 的对拍、Rust 从没运行验证过 —— 第 1 个 bug 说明它 连函数调用都跑不了。现在补上 tests/test_m27_rust_host.py(21 对拍 + 5 xfail),docs/embed.md 的支持程度表也按实测重写。
实测能力:显示规则 / 变长参数 / 星号解包 / 函数 / 递归 / 闭包 / 匿名函数 / 默认参数 / 基础控制流 —— 全部与 Python 逐字节一致。
⚠️ 一个「静默算错」:可变容器(列表.追加 改的是副本,推导式因此 返回空)。根因是 Rust 的 Val 是值类型、get_attr 拷一份再改;修它要把 List/Dict 换成 Rc<RefCell<…>>。这条比「报错」严重(拿到错结果却不知道), 已在 embed.md 与测试的 xfail 里明确标出。
M27 已交付:值→文本的统一
同一个值以前有两种写法:打印(空) 给 None、布尔给 True/False, 而 REPL 回显与 类型() 给中文「空/真/假」——用户得猜哪个算数。 根因是这件事有两份几乎一样的实现(runtime.jishi_repr 与 repl._repr_jishi),日子久了就漂移。
现在收敛成一处 runtime.jishi_repr,打印、文本()、字符串插值 ` {值} `、容器内元素、REPL 回显全走它:
python打印(空, 真, 假) # 空 真 假
打印([1, 真, 空, "甲"]) # [1, 真, 空, '甲']
打印(`结果是 {真}`) # 结果是 真
打印(路径.存在("docs")) # 真(桥接回来的原生 bool 也一样)
- 顶层文本不加引号、容器内加引号——与 Python 的
print/repr分工一致。 - 字典用半角
:,与源码{"甲": 1}写法一致,打印结果能直接抄回代码。 - 这三类字面值在文档、教程、示例、快照里全部跟着改到了中文。
M26 已交付:语言功能扩展 C 档
探针可用率 31/44 → 36/47。三项都是「标准库优先」型,只有 用 … 为 碰语法——而它在解析期脱糖,所以三个执行器一行都没改。
python# 上下文管理器:块一结束,文件自动关闭(返回/中断/抛异常都会关)
用 打开("数据.txt") 为 文件:
遍历 行 在 文件:
打印(行)
# 自定义对象打印:类里定义「文本」方法
类 分数:
函数 文本(自身):
返回 文本(自身.分子) + "/" + 文本(自身.分母)
打印(新建 分数(1, 3)) # 1/3(不再是 <分数 实例>)
# 精确小数:金额/对账场景不再「差一分钱」
打印(0.1 + 0.2 == 0.3) # 假
打印(精确("0.1") + 精确("0.2") == 精确("0.3")) # 真
令 价 = 精确("19.99")
打印(价 * 3) # 59.97
打印(价.舍入(2)) # 四舍五入
用 … 为 的脱糖(所以三执行器零改动):
python__用_1__ = 打开("数据.txt") # 临时变量存原对象
文件 = 进入上下文(__用_1__)
尝试:
……你的代码……
最终:
退出上下文(__用_1__)
写这批测试时抓到一个真实陷阱:初版把绑定后的名字传给 退出上下文, 于是 进入() 一返回别的对象(Python 的 __enter__ 也允许),退出就找错 对象、静默失效。现在退出作用在原来的对象上,与 Python 语义一致。
顺带做的:打开 返回真句柄(能按行遍历大文件,不必整个读进内存); 文件对象可迭代、有位置/定位/刷新;总和/最大/最小/排序 对精确小数 也都可用;文件不存在的报错去掉了英文原文(原来混着 No such file or directory)。
用新特性重写了一处真实项目:examples/projects/日志统计/ 改用 用 打开(…) 为 f: 流式按行读——不再把整份日志读进内存,也不用惦记关文件 (顺带删掉了一个 导入 文件)。
同时发现一处既有不一致:打印(空) 显示 None、布尔显示 True/False,而 REPL 回显与 类型() 用中文「空/真/假」。已在 M27 统一(见下)。
M25 已交付:变长参数与星号解包
python函数 求和(*数): # 收下所有位置实参,是个列表
令 总 = 0
遍历 x 在 数:
总 += x
返回 总
打印(求和(1, 2, 3)) # 6
函数 配置(**选项): # 收下未匹配的关键字,是个字典
返回 选项
函数 混合(甲, *余, **选): # 三者可组合
返回 [甲, 余, 选]
令 甲, *余 = [1, 2, 3, 4] # 星号解包:甲=1,余=[2, 3, 4]
令 首, *中, 尾 = [1, 2, 3, 4, 5]
这一项是唯一改动 C 代码的语法扩展,改动链路: code_params 每个形参从 3 个 int32 变 4 个(加「形参种类」)→ JsHost 末尾追加 pack_kwargs 回调 → bind_args 的所有权语义从 「转移」改为「借用」→ UNPACK 闲置的 b 操作数用来传星号位置 → 字节码序列化格式版本 1 → 2。
改完同步了两个跨语言宿主(node/ 的 JS 实现、rust/ 的 Rust 实现), 否则「字节码可移植」就名不副实。
顺带修掉三个既有缺陷:
//=/=复合赋值一直不可用(词法器把= 切成**+=);- 类方法的类型标注两端都没生效(树遍历漏传、编译器漏发射校验);
长度/最大/最小对自定义可迭代对象不可用。
M24 已交付:语言功能扩展 B 档
探针可用率 16/39 → 27/43(tools/probe_capabilities.py,带预期输出校验)。
python# 集合:有序去重,打印顺序稳定可复现
令 唯一 = {x % 3 遍历 x 在 范围(9)} # {0, 1, 2}
令 交集 = 集合([1, 2]).交集([2, 3]) # {2}
# 匿名函数 + 数据流水线
令 数 = [1, 2, 3, 4, 5]
数.映射(函数(x):x * x) # [1, 4, 9, 16, 25]
数.过滤(函数(x):x % 2 == 1) # [1, 3, 5]
数.归约(函数(累计, x):累计 + x, 0) # 15
# 类型标注:参数会在运行时校验(三执行器一致)
函数 折扣(价格: 小数) -> 小数:
返回 价格 * 0.8
# 切片赋值(读切片与写切片共用一条求值路径)
分数[1:3] = [95, 96]
# 迭代协议:自定义对象直接可用于「遍历」与推导式
类 成绩表:
函数 迭代(自身):
返回 自身.记录
两个设计取舍:
- 集合刻意不用无序哈希集合——那样同一程序两次运行输出会不同,
三执行器的逐字节对拍也会变成随机失败。有序集合牺牲一点性能换确定性。
- 类型标注定位是「运行时校验 + 文档」,不是静态类型系统:认不出的
标注名(自定义类型)只作文档、不误报;没写标注的参数零开销。
M23 已交付:语言功能扩展 A 档(六个高频缺口)
能力缺口用实测说话:tools/probe_capabilities.py 逐条试候选特性, 可用率从 9/38 升到 16/39(且校验预期输出,不只是「没报错」才算支持)。
python# 成员测试与同一性:判断意图直接写得出来
如果 名字 在 允许名单 与 结果 不是 空:
打印("通过")
# 断言:把「不该发生」尽早拦住,可被捕获
断言(数量 > 0, "数量要为正")
# 类变量:所有实例共享
类 计数器:
总数 = 0
函数 加(自身, n):
计数器.总数 += n
# 超():调父类方法(多级继承也正确,不会绕回自己)
类 狗 继承 动物:
函数 初始化(自身, 名字, 品种):
超().初始化(名字)
自身.品种 = 品种
# 是实例:类型判断(含子类)
如果 是实例(值, 动物):
打印("是动物")
顺带修掉一个静默错误:非 原先比比较运算更紧,导致 非 年龄 >= 18 被解析成 (非 年龄) >= 18(恒为假)。项目教程 docs/tutorial/03 就踩了这个坑,「未成年人」分支永不触发。现修正为 Python not 的语义:非 年龄 >= 18 = 非 (年龄 >= 18)。
M22 已交付:包生态(默认索引 + 发布 + 冲突检测)
- 开箱即用的装包:
jishi 安装 包名无需任何额外参数。索引是静态 JSON,
托管在本项目公开仓库(零服务端),可用 --index / 环境变量 JISHI_INDEX 指向自建索引,--no-index 关闭联网。
``bash jishi 安装 数学工具 # 走默认索引:读索引 → 下载 → 校验和 → 安装 jishi 列表 ``
- 发布工具:把本地包目录变成「能装的东西」。
``bash jishi 发布 我的包 --base-url https://…/下载 --index packages/索引.json jishi 索引 包集合目录 --base-url https://…/下载 --out-index packages/索引.json --checksum ``
发布时会校验 包.json(必填字段、字段名拼错、版本格式),打包只收 包.json 与 .jsh,并生成 sha256 供安装端校验。
- 钻石依赖冲突检测:
jishi 检查把「谁以什么约束要求了它」讲清楚:
`` 包「共同」的版本要求互相打架(当前装的是 1.5.0): · 甲 → 丙 → 共同 要求 >=2.0.0 · 乙 → 共同 要求 <2.0.0 建议:统一各上游对它的版本要求,或先卸载引起冲突的包 ``
只报告不自动求解——生态还小,把成因讲清楚比自动挑版本更有用。
M21 已交付:开发者工具(LSP + 格式化)
- 语言服务器
jishi lsp:stdio 上的 LSP,编辑器接入即得
实时诊断(复用中文报错,含错误码/行列/修改提示)、 补全(关键字 / 内建 / 标准库 / 文档内符号;随机. 给模块函数带签名)、 悬停文档、跳转定义、文档符号大纲。 补全与悬停的数据源取自 ai.build_lang_spec(),不另抄一份,避免与语言本体漂移。 M42 又补了六项:格式化 / 重命名 / 快速修复 / 签名提示 / 工作区符号 (含跨文件跳转)/ 语义高亮——详见上面的 M42 一节。
- 调试适配器
jishi dap(M43):stdio 上的 DAP,编辑器接入即得断点 / 单步 /
变量 / 调用栈 / 监视求值;与 jishi 调试 共用同一套调试内核。
- 代码格式化
jishi 格式化:统一缩进(默认 4 空格)与词间间距,
保留注释、三引号字符串与中文引号原样;支持 --write / --check / --stdin。 保证幂等与语义不变(全语料 token 流逐一对拍)。
编辑器接入:扩展已上架 VS Code Marketplace —— 在扩展面板里搜 「基石语言」 (或 jishi)直接安装,或走命令行:
bashcode --install-extension jishi-lang.vscode-jishi
页面:<https://marketplace.visualstudio.com/items?itemName=jishi-lang.vscode-jishi>
也可以从源码自己打 .vsix(构建产物、不入库;一条命令约 26KB, node_modules 不会进包):
bashcd editors/vscode-jishi && npx @vscode/vsce package
code --install-extension vscode-jishi-*.vsix
装完在任何窗口、任何文件夹打开 .jsh 都有效,不再需要开发宿主窗口。 改扩展代码时则用 VS Code 打开该目录按 F5 走「扩展开发宿主」 (那条路的限制见扩展自己的 README)。 jishi 不在 PATH 上时在设置里指明:
json{
"jishi.server.command": "D:/jishi/jishi.exe"
}
或把 --check 挂进 CI,防止风格漂移:
bashjishi 格式化 --check examples/ # 未格式化则退出码 1
M20 已交付:性能优化(批量 JVal 转换)
用 benchmark + cProfile 定位到 C VM 的真正瓶颈:每次迭代/调用的返回值都要 逐个跨 ctypes 边界构造 JVal(_jint 0.55µs + py2c 0.4µs ≈ 1µs/值)。
优化:iter_next_batch 中整批为整数时,用一次 struct.pack + memmove 写入 C 侧数组,省掉逐个构造(实测转换快 ~9x,混合类型/大端自动回退逐个)。
| 场景 | 优化前 | 优化后 |
|---|---|---|
| 整数循环累加 | 1.9x | 8.4x |
| 函数调用密集 | 4.5x | 10.7x |
| 算术混合运算 | 5.2x | 18.1x |
| 列表构建与遍历 | 1.6x | 2.6x |
| 递归斐波那契 | 18.3x | 18.1x |
| 当循环倒计数 | 44.4x | 45.9x |
| 字符串拼接 | 0.30x | 0.34x(架构边界) |
独立验证:遍历 范围(20000) 从 16.5ms → 3.8ms(4.4x)。
M19 已交付:发行改进(Windows 安装程序 + 三平台自动构建)
- Windows 安装程序:
tools/installer.iss(Inno Setup)+build_release.py
集成,产出 jishi-{版本}-windows-x64-setup.exe——用户级安装(免管理员)、 简体中文向导、开始菜单快捷方式、可选写入 PATH、注册标准卸载程序; 不再只提供压缩包。
- 三平台自动构建:
.github/workflows/release.yml,打 tag(v*)触发
windows / linux / macOS 矩阵构建,自动创建 Release 并附统一 SHA256SUMS。
- 发布脚本:
tools/publish_github.py——唯一发布通道(导出快照 → 隐私
屏蔽 → 孤儿提交 → force push;--tag 可触发三平台自动构建)。
M18 已交付:表达便利(切片 / 命令行参数 / 最小最大多参数 / 遮蔽治理)
- 切片语法:
列表[1:3]、[::-1]、[::2]、[:2]、[2:]、[:],
字符串同样支持;起/止/步长都可省略或用表达式;三执行器同步(新增 BUILD_SLICE 指令 + C VM make_slice 宿主回调)。
- 命令行参数 argv:
jishi 脚本.jsh 参数1 参数2→系统.参数()返回
["参数1", "参数2"];-- 之后全算脚本参数(与 Python 约定一致)。
最小/最大多参数:最小(a, b)、最小(a, b, c);单列表调用保持兼容。- 导入遮蔽治理:
导入 文本遮蔽内建时打印一次中文提示;普通导入新增
为 别名(导入 文本 为 t),彻底规避遮蔽。
M17 已交付:排错体验补全(「出问题能快速定位」)
- 调用栈回溯:深层调用出错时,报错下方显示「调用链(从外到内)」,
例如 外层 ← 第 8 行 → 中层 ← 第 7 行 → 里层 ← 第 5 行;协议 JSON 里 也带 trace 字段,agent 可程序化读取。
- 递归超限中文化:修复了
RecursionError绕过中文翻译、落到英文兜底的
bug;现在输出「递归层数太深了」+ 调用链 + 「是不是忘了写结束条件」建议。
- 沙箱/协议错误补全:超时错误带「可能是有死循环」提示;递归超限带
中文翻译 + 调用链,line/col/hint 不再空。
- REPL 报错显源码行:交互式报错现在也显示源码行 +
^指示,与文件模式一致。
M16 已交付:发行版与安装体验(「下载 → 安装 → 打开终端就能跑」)
- 独立可执行发行包:
tools/build_release.py(PyInstaller onedir)产出
16MB zip + SHA256SUMS;打包产物实测跑通 hello/递归/语言卡/JSON 报错/C VM/标准库/MCP。
- 安装/卸载脚本:
install.ps1/install.sh+uninstall,装~/.jishi+ 加 PATH。 jishi 医生环境自检 /jishi 新项目脚手架 /jishi 教程索引。- 版本号单一来源:
jishi/version.py;首个发行包随本轮发布
(现发布通道是 GitHub Releases)。
M15 已交付:真实项目驱动 + 语言收敛 1.0
- 三个真实项目(
examples/projects/):成绩分析 / 网页采集 / 日志统计,
全部跑通;反哺 5 个语法别扭点(无切片 / 最小二参 / 导入文本遮蔽等)。
- 语言规格 v1.0 冻结:
docs/语言规格.md标 v1.0,补「已知边界」节。 - 教程 10 章 + few-shot 45 对;设计决策补 D13–D16。
M14 已交付:包管理与生态(「语言 → 生态」的最后一环)
jishi 安装/卸载/列表命令:`jishi 安装 包名 --local-dir/--local-zip/
--from-index,从远端索引「读索引→下载→解压→安装」全自动;重复安装报错, --force` 覆盖。
- 包依赖解析:
包.json的「依赖」字段(名字→版本约束),导入时递归加载
依赖 + 版本校验(*/==/>=/^ 等)+ 循环依赖检测,报错全中文。
- 包清单扩充:「入口」(声明后只执行入口模块)/「许可」/「描述」字段。
- 标准库再扩 4 个:
路径(pathlib)/加密(hashlib)/压缩(zipfile)/
系统(os/subprocess),延续中文薄壳、零第三方依赖、沙箱白名单默认安全。
M13 已交付:跨语言字节码分发(脱离 Python 被任意宿主嵌入)
- JSON 字节码格式:
jishi 脚本.jsh --dump-bytecode产出版本化+校验和的
平台无关字节码(常量池只含标量,纯数据可被任意语言消费)。
- Node.js 绑定:
node/纯 JS 解释器(零依赖),消费 JSON 字节码,
71 个 SNIPPETS + 10 个 cases 与 Python VM 输出逐字节一致。
- Rust 绑定:
rust/零依赖 crate(自研 JSON 解析器),编译通过;
当前「最小可运行」(可变容器语义待 Rc<RefCell> 重构)。
- 嵌入文档:
docs/embed.md三宿主(C/Node/Rust)接入指南。
M12 已交付:评测驱动闭环(「被大模型调用」有数据可证)
- 评测集从 12 题扩到 50 题(覆盖文本/列表/文件/类异常/字典/推导式/Python 桥接/标准库);
--llm真实接入 + 自愈闭环评测 + CI 固化基线;唯一待办:跑一次真实模型出报告
(需环境变量 JISHI_EVAL_API_KEY)。
M11 已交付:性能与分发
- C VM 热路径下沉:新增
LIST_APPEND指令,把「列表.追加」的两次回调
(getattr+call)合并为一次;列表场景基准 0.58x → 1.7x。7 场景中 6 场景 ≥1.5x(最高 47x)。字符串拼接是透明架构边界(HOST 句柄无法直接操作 Python str,详见 tools/benchmark.py 输出说明)。
- wheel 内带预编译 DLL:
tools/build_wheel.py编译 DLL 到包内并打包,
装 wheel 即含 C VM,无需本机 gcc;干净 venv 实测三执行器全可用。 (当时写给 pip install jishi,但那个包从未发布到 PyPI——PyPI 通道已于 2026-09-22 放弃,见 docs/路线图.md「方向五 · 自主分发」。)
M10 已交付:生态扩展(「拿到就能写」的覆盖)
- 标准库扩充(4 个中文模块):
json(中文不转义)、日期(格式化/加减/
比较/解析)、正则(匹配/查找/替换/分组)、网络(获取/提交,urllib 薄封装 零第三方依赖,默认 10s 超时)。
- 包管理雏形:
导入 名字 从 本地包,包 =.jishi/packages/名字/(含
包.json + .jsh 模块),执行模块后导出顶层函数/变量。
- 浏览器试玩:
tools/build_playground.py把树遍历解释器内嵌成自包含 HTML,
pyodide(WASM Python)浏览器内直接跑基石;docs/ai/prompt-guide.md 教 LLM 写基石。
M9 已交付:安全执行与 MCP 集成(「被大模型调用」的落地形态)
- 统一执行结果协议
jishi/protocol.py:{ok,value,stdout,duration_ms}/
{ok:false,error:{code,line,col,message,hint,fix}},agent 程序化读取。
- 沙箱运行器
jishi/sandbox.py:超时(默认 5s)、stdout 上限、禁文件写、
模块白名单(标准库默认只放行无副作用模块,Python 桥接默认禁并拦截网络/危险模块)。
- MCP Server
jishi/mcp_server.py(stdio 传输):run_script(subprocess
隔离,真正超时 kill)/ eval_expr / describe_language;入口 jishi-mcp。 这是基石进入 WorkBuddy / 任意 MCP 宿主 agent 生态的入口。
- C VM 嵌入模式
cvm/include/jsvm.h:公开 C ABI(JSVM_ABI_VERSION=1、
JVal 值表示 + inline 构造器、JsHost 宿主回调表、导出函数声明), 第三方 C/Node/C#/Rust 宿主可直嵌基石引擎(examples/embed/hello.c 实测通过)。
- CLI 加
--sandbox/--json-result/--timeout/--stdin。
M8 已交付:AI 原生工具链(「被大模型调用」的差异化核心)
- 机器可读语言规格:
jishi --lang-spec --format json输出关键字表/运算符/
内建/标准库/异常的 JSON(版本+内容哈希),供 agent 校验代码。
- AI 语言卡:
jishi --ai-card输出 ~2K token Markdown 速查卡(语法全表 +
API + 惯用法示例 + 英文错误对照),可直接贴进 LLM 系统提示词。
- 结构化 JSON 报错:
jishi run x.jsh --json-errors输出
{"ok":false,"errors":[{code,line,col,message,hint,fix}]},fix 字段 (如 {"old":"def","new":"函数"})让 agent 可程序化文本替换自愈。
- few-shot 示例库:
examples/ai/few-shot.md20 个「任务→代码」对。 - 官方系统提示词:
docs/ai/system-prompt.md。 - llms.txt:文档站产出
site/llms.txt+llms-full.txt(遵循 llmstxt.org)。 - 基石评测集 JishiEval:
bench/ai-eval/50 题 + 自测脚本,作为
「语法改动不得使一次通过率下降」的回归基线。
M7 已交付:表达力打磨(语法糖,三执行器同步)
- 多赋值与解包:
令 a, b = [1, 2]、a, b = b, a(交换)、返回 a, b
→ 返回列表;新增 UNPACK 指令,数目不符中文报错。
- 默认参数:
函数 f(a, b = 10),定义处求值一次(与 Python 一致),
有默认值的参数须在最后;支持方法/关键字参数配默认。
- 列表/字典推导式:
[x*2 遍历 x 在 列表 如果 x>2]、{k: v 遍历 ...};
编译器脱糖为迭代+追加,C VM 复用现有指令零改动。
- 文本插值:反引号 `
你好 {名字},今年 {年龄} 岁,{表达式}` 求值
插值、{{/}} 字面花括号;parser 脱糖为拼接链,三执行器零改动。
- 容错解析:识别 LLM 写中文代码时 fallback 回英文的高频错误(
def/if/
print/True/and…),直接给出中文建议(「你是不是想写「函数」?」), 英文普通变量名零误伤。
- 顺带修复 C VM 一个潜伏的 use-after-free(链式调用
造()()必崩)。
M6 已交付:工程化
- CI 自动化:
tools/ci.py一键全检(C VM 构建 → pytest → C 对拍确认 →
基准冒烟);.github/workflows/ci.yml 备用(ubuntu+windows × py3.10/3.13)。
- 编辑器插件:
editors/vscode-jishi/—— .jsh 语法高亮(含中文引号/中文
关键字)、11 个代码片段、括号/注释/缩进配置;vsce 打包 vsix 已端到端验证。
- 文档站:
tools/build_site.py零依赖 Markdown→HTML 生成器,把 README +
语言规格 + 设计决策 + 教程 10 章渲染成带侧边栏的静态站(site/,已 gitignore)。
- 打包发布:pyproject 已验证 pip 可编辑安装 + wheel 构建,干净 venv 里
CLI/REPL/脚本全可用;无 C DLL 时 C VM 优雅降级、树遍历照常。
M5a 已交付:异常处理(三执行器同步支持)
- 语法:
尝试:/捕获 类型 为 e:(可多个)/最终:(可选)/抛出 值; - 异常对象即
JishiError实例,e.消息/e.类型可读;
类型名(值错误/类型错误/索引错误/键错误/除零错误/文件错误/运行期错误/异常) 是内建可调用对象(抛出 值错误("…")),捕获按 isinstance 匹配,基类抓子类;
- 非异常值
抛出会被包装成「异常」,原值留在e.消息; 最终语义与 Python 一致(正常/异常/返回/中断都执行,返回可覆盖);- 树遍历靠 Python try/except;字节码 VM 与 C VM 都在帧内维护异常处理器栈,
出错时逐层弹帧匹配(详见 cvm/bytecode.md 的异常协议)。
M5b 已交付:面向对象(三执行器同步支持)
- 语法:
类 名字 继承 基类:(方法即函数,构造方法约定名「初始化」,
方法首参「自身」)、新建 类(参数);
JishiClass/JishiInstance在runtime.py,实例字段 + 绑定方法
(实例.方法() 自动把实例塞进「自身」首参);
- 继承合并基类方法表,子类同名覆盖;构造方法「初始化」自动调用;
- 类方法内
抛出的异常能穿透调用栈、被外层尝试捕获,与 Python 一致
(重入边界 stop_at/stop_depth 保证三执行器一致)。
M4 已交付:字节码编译 + C 虚拟机
jishi/compiler.pyAST→字节码(作用域分析、闭包单元、指令带源码行列);jishi/vm.pyPython 参考虚拟机(M4a,语义参照与调试基线);cvm/src/jsvm.cC 栈式 VM(数值/比较/跳转/调用/循环/闭包快路径),
cvm/build.py 一条命令出 DLL,jishi/cvm_bind.py ctypes 绑定 + 宿主回调;
tests/test_cvm.py三执行器对拍(黄金用例/错误快照/示例全覆盖);tools/benchmark.py可复现基准:C VM 提速 2.6x–46x
(当循环 46x、递归 18x、算术混合 18x、函数调用 11x、列表 2.6x); 字符串拼接是透明架构边界(字符串是 HOST 句柄,C 侧无法直接操作 Python str, 详见 benchmark 输出说明)。
M3 已交付:Python 生态桥接 + 关键字参数
导入 x 从 python、别名导入、Python 对象属性/方法/下标桥接、 基石函数作 Python 回调(map/sorted)、Python 异常中文化、关键字参数 (json.dumps(x, ensure_ascii=假) 保住中文)、True/False/None 中文写法提示。