里程碑明细(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章/尾随逗号✅
M3Python 生态桥接 + 关键字参数✅
M4a字节码编译器 + Python 参考虚拟机✅
M4bC 字节码虚拟机内核 + ctypes 绑定✅
M5a异常处理(尝试/捕获/最终/抛出)✅
M5b面向对象(类/继承/新建)✅
M6工程化:CI/编辑器插件/文档站/打包✅
M7表达力打磨 + 容错解析✅
M8AI 原生工具链(语言卡/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),跨宿主对拍补齐✅
M28Rust 宿主修可变容器(Rc<RefCell>,原为「静默算错」)+ 补齐 在/是 等比较运算✅
M29Rust 宿主补齐到 Python 侧:方法调用 / 高阶方法 / 集合 / 文件与上下文 / 精确小数✅
M30JS(Node)宿主补齐到 Python 侧:集合 / 高阶方法 / 成员测试 / 同一性 / 切片 / 标准库 / 输入✅
M31JS 宿主标准库补齐到与 Python 同宽:路径 / 文件 / 日期 / 时间 / 加密 / 压缩 / 网络✅
M32边界清单逐条实测核实:修五处静默算错(大整数 / 整数回绕 / 精度舍入 / 显示)+ 补两处明确报错✅
M33Rust 宿主标准库补齐到与 Python 同宽:14 个模块 / 约 130 个函数,零依赖自写正则引擎、四种摘要、zip 容器 + inflate、WinHTTP 网络✅
M34D 档第一批:模式匹配 匹配/情形 + 枚举 枚举(解析期脱糖,三执行器 + 两宿主零改动)✅
M35发布工程与收尾(语言图标进安装包与三平台发行物 + 类体校验统一到解析期 + 测试并发安全 + 文档一致性整理 + 路线图 v2)✅
M36语法面冻结:条件表达式(脱糖)+ 位运算内建 + 字典.合并 + 宿主同步 + 探针「无悬案」(42/50 可用,8 项全判定)✅
M37主线 B1:调试器(断点 / 单步 / 看变量 / 看调用栈 / 出错现场),树遍历执行器✅
M38主线 B 收尾:LSP 增量同步 + REPL 增强(历史/Tab 补全/多行)+ 格式化器边界✅
M39主线 B5:字节码执行器的调试器 + 用基石写静态站生成器(三执行器产物逐字节相同)✅
M40C VM 调试器(三执行器会话逐字对拍)+ 「赋值即局部」补齐 + f-string 遮蔽修复 + 五类真实项目(行数比基线 1.27)✅
M41标准库第一批 5 模块(容器/迭代/参数/日志/测试,五路同宽)+ 内建 带下标/配对 + 遍历 多变量解包 + 错误归类修正(行数比 1.21)✅
M42语言服务补六项(格式化 / 重命名 / 快速修复 / 签名提示 / 工作区符号 / 语义高亮)+ VS Code 扩展升级为语言客户端✅
M43调试器接进编辑器(jishi dap)+ 语法高亮与片段补齐 + 打包 .vsix 上架 Marketplace✅
M44机器可读的语言事实(错误码 35 条 / 静态检查码 / 等价 Python 写法 83 条)+ jishi 源码静态检查(零误报)✅
M45AI 跑分表(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-073182.2%(37/45)93.3%(42/45)1.120
glm-5.3-flash88.6%(39/44)100.0%(44/44)1.111

两个模型都在只读语言规格的前提下把 45 题做到 93%–100%,说明题目考的 「中文关键字怎么写、标准库有什么、报错怎么修」对模型是可达的。glm 那道跳过 不是模型的问题(HTTP 504 接口故障,已按纪律记「跳过」、不进分母)。

两条判分纪律值得单说:

会把这种「先打印对了再越界」判成通过。这题是写题集时真撞见的。

必须真的跑出标准输出(--自检 不花钱、不调模型)。题目本身错了,跑分表就是假的。

限流(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 是模型读语言事实的入口):

(E2010 输入结束、E2100 程序被中断),并用 internal 标出执行器内部信号 (E299x,不该展示给用户)。做这张表时才发现 E2008 被两个异常类共用 (RunAssertionError 与 RunCustomError)——已修:断言错误留 E2008, 自定义异常改 E2009。

(加了新检查项却忘了登记,测试会红)。

遍历→for、长度→len……)。模型最大的先验是 Python,这层显式映射比让它猜 准得多;对应关系有差异的照样点明(反转 → reversed(列表)/文本[::-1]、 集合 → Python 的 set 无序而基石这个保持首次出现顺序)。

(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 两条入口 共用同一套会话)。

threads / stackTrace / scopes / variables / evaluate / continue / next / stepIn / stepOut / pause / disconnect;事件 initialized / stopped / continued / output / exited / terminated。

唯一差异是 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 项能力逐条接上。

(分帧 + 请求应答 + 通知分发,约 150 行)。理由与 C 内核、正则引擎同一套取舍: 能自己写清楚的就别引依赖——.vsix 也因此小得多。

jishi lsp,把每项能力点一遍(25 项断言)。装进编辑器之前就能发现「点了没反应」。 这条自检也进了回归(tests/test_m42_vscode_client.py)。

三条有意的边界(写进 editors/vscode-jishi/README.md,不粉饰):

  1. 重命名只支持模块级符号(函数 / 类 / 顶层变量 / 导入名)。函数形参与函数内

局部变量不支持——AST 不记形参位置,而且没有作用域分析就改名会把「另一个 函数里同名的局部变量」一起改掉,那是改坏用户代码。做不到就返回 null (编辑器显示「无法重命名」),比给一个错的编辑安全得多。

  1. 跨文件跳转只在唯一命中时生效:同名符号出现在多个文件就放弃——跳错地方

比跳不过去更费时间。

  1. 工作区符号索引有封顶:最多 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.5x10.5x
函数调用密集13.3x15.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

顺带做的两件体检(都不是计划内的):

  1. examples/ai/few-shot.md 的 48 段示例从没跑过——逐段执行发现 4 段是真错

(变量名撞内建、闭包计数器撞「赋值即局部」、用了不存在的解包遍历与 字典.键值对())。已修,并加测试逐段钉住(片段不自包含的按「环境缺失」 放行,语法/API 错误必须报出来)。喂给 AI 的样本本身就是代码,必须进测试。

  1. 别扭点清单复盘:十条里 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 刚写、 真实项目在用)解析不了;真实项目里的变量 匹配组 被「关键字粘连检查」 拦下。修法是两条通用措施,不是特例补丁:

  1. . 后面的属性名允许关键字(属性名位置本来就没歧义)——

以后加关键字不必再担心撞既有 API。变量名/参数名/字典键仍不许(与 Python 一致)。

  1. 匹配 / 枚举 加进粘连检查的跳过表——它们和 用 / 是 一样是

常用词前缀(匹配组/匹配器、枚举值/枚举类),粘连提示是净损失。

顺带撞出宿主侧三处既有缺陷

枚举脱糖要用「类体外给类设属性」,一写就撞出宿主本来就有的坑(都不是本轮引入):

缺口表现修法
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 在零依赖下都用不了。

自己写反而好对拍;拿 hashlib 当裁判,abc、空串这些公开向量逐字节一致。

打包写 stored(不压缩)——零依赖下没有 DEFLATE 压缩器,而 stored 在规范里本来就合法;解压躲不掉(别人打的包默认都是 deflate), 所以 inflate 是完整实现的(三种块都支持)。测试还双向互操作: Python 打的 deflate 包 Rust 解得开,Rust 打的包 zipfile.testzip() 校验通过。

→ 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=&quot;\n&quot;)、 系统.执行(改成按字节收再解码)、json.读文件/写文件(补 newline=&quot;&quot;)。

另外两处「内部实现细节漏给用户」也修了:正则.查找全部 多分组时漏出 Python 的元组(基石没有元组类型,统一成列表)、类型(表格) 给 _table 类名 (加类型名钩子,三个宿主统一显示「表格」)。

M32:核实「剩余边界」,把静默算错清零

docs/embed.md 里那份「剩余边界」清单,逐条横向实测了一遍——因为文档 会过时(上一轮就撞过:embed.md 给 JS 的成员测试标了 ✅,实际根本没实现)。 探测结果分三类,两类都动手了:

一、两条过时描述(删掉)

标记,decodeConst 早就按它包成 FloatBox 了。

二、五处「静默算错」(修掉)

现象根因对策
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 侧没实现这个内建——带标注的函数直接报「找不到名字」。

等价于 Python 的 id()。绑定方法仍明确报错(Python 里 a.方法 是 a.方法 为假,Rust 侧判会得真——宁可明确不支持)。

顺带发现(未动,已如实记进 docs/embed.md):树遍历不支持类体里写 通过;JS 宿主没同步 M23 的「类变量」;类型标注表里的 空/函数 语法上写不出来(是关键字)。

M31:JS 标准库补齐到与 Python 同宽

M30 把 JS 宿主补到「5 个常用模块」,这一轮把剩下 7 个补完: 路径 / 文件 / 日期 / 时间 / 加密 / 压缩 / 网络。补完的结果是 examples/projects/ 里的全部项目都能在 JS 宿主上跑 —— 包括上一轮 还明确写着「跑不了」的「网页采集」。

三块各有各的坑,都是「照着 Python 写反而会错」的类型:

只管 deflate 流,本地文件头(30 字节)、中央目录项(46 字节)、EOCD (22 字节)、CRC-32 都得按 PKWARE 规范自己拼。所以这一块的测试不只对拍 输出,还拿 Python 的 zipfile 当裁判:Node 打的 zip 要被 zipfile 读出来 (zf.testzip() 校验 CRC),Python 打的 zip 要被 Node 解开 —— 双向都测。

标准库不能变成异步函数。方案是 spawnSync(process.execPath, [&#x27;-e&#x27;, 脚本]) 起一个自身的子进程跑异步请求,结果经 stdout 回传。零第三方依赖, 也不要求系统装了 curl。代价是每次请求多一次进程启动(本机约 40ms), 换来与 Python 侧 urllib 一样的同步调用形态。

于是自己实现了一个子集(%U/%W 的周号照 CPython 的公式:新年第一个 周日/周一之前的日期算第 0 周)。时间.睡眠 用 Atomics.wait 同步阻塞。

顺手改掉 Python 侧两个跨平台不一致:

  1. 文件读写改成「换行保真」:Python 的文本模式在 Windows 上会把 \n

悄悄写成 \r\n,于是「同一份程序在两个平台产出的文件字节不同」,也与 Node 的 fs(不转换)对不上。标准库统一显式 newline=&quot;&quot; —— 基石的定位是 「同一份程序到哪都一样」,那就得从字节层面保证。

  1. 值→文本统一:文件.写文本 / 写行、加密.摘要 之前走 Python 的

str(),于是 写文本(p, 真) 写出的是 True(而 文本(真) 给 真)。 改成 jishi_repr,与 M30 修的 文本.拼接 / 格式化 一致。

路径模块还有两个细节:后缀 不能直接用 path.extname(pathlib 对 a. 与 .bashrc 都算无后缀,而 JS 给 &#x27;.&#x27;);Windows 上 Python 的 Path.__str__ 总吐 \ 而 JS 的 path 保留输入里的分隔符,所以 连接 / 父目录 的返回值 要显式规范化。

M30:JS(Node)宿主补齐到 Python 侧

Rust 补完补 JS。这次先写了个横向探测脚本:把 39 个用例在 Node 与 Python VM 上各跑一遍逐条比对,实测出 37 处不一致,再按实测范围补 ——不靠印象猜。探测立刻暴露了两件「原来早就坏了」的事:

vm.js 的 CMPOP 只映射了 0–5(==…&gt;=),M23.2 的语义比较码 6–9 落成 undefined,于是 2 在 [1, 2] 报「不支持的比较「undefined」」。 这与 Rust 侧 M28 暴露的是同一个问题——加语义比较运算符时,宿主侧那张 「码值映射表」是漏点重灾区。而 docs/embed.md 当时还给这两行标了 ✅。

不特判,如果 列表: 的判断会整个反过来(静默算错级别)。 同理 === 是引用比较,[1, 2] == [1, 2] 原先给假。

补完这七块之后,我把 examples/projects/ 的真实项目拿来在 Node 上跑, 又撞出三块(探针有盲区):切片(BUILD_SLICE 这条指令 JS 侧压根没有, 对[::-1] 报「未知指令 48」)、二元运算的类型分派(JS 的 + 对数组是 字符串拼接、% 的符号与 Python 相反)、标准库(表格 / 文本 / json / 正则 / 系统,以及此前「永远返回空串」的 输入())。

于是验收标准固定下来:examples/projects/ 里的真实项目能在宿主上 原样跑出与 Python 逐字节相同的结果(含退出码)——这比任何探针都硬。

M29:Rust 宿主补齐到 Python 侧

把 Rust 宿主从「核心链路可跑」补到与 Python 侧逐字节对拍一致(剩下的 都是明确报错,不再有「静默算错」)。三块一起做:

  1. 用户方法调用(对象.方法() / 初始化 / 继承覆写 / 递归方法)。

前提是把 Instance 也换成 Rc&lt;RefCell&lt;…&gt;&gt;——否则 自身.字段 = 值 改的是副本、静默失效(与 M28 的容器是同一类坑)。 新增 VM::call_value:内建/method 能同步回调用户函数。

  1. 列表高阶方法:映射 / 过滤 / 排序按 / 归约。实现要点是

回调前先快照容器,否则「拿着 RefCell 借用去回调」会撞借用冲突。

  1. 集合 / 文件与上下文 / 精确小数:集合(有序去重,11 个方法)、

打开() 真句柄 + 用 … 为、零依赖手写 BigInt 的精确小数 (28 位有效数字、ROUND_HALF_EVEN,精确(&quot;1&quot;)/精确(&quot;3&quot;) 与 Python 的 Decimal 逐字节一致)。

动手写第 3 块时抓到 Rust 侧两个真 bug(都不是本次引入的):

尝试/捕获/最终 对内置错误(除零/越界/类型不符)完全失效。

「用 块里 返回」会让文件不关。

两条都按 Python 的块栈语义补齐,tests/test_m27_rust_host.py 里各有一条 对拍锁住(含 中断 穿过 用 块时先清理)。

M28:Rust 宿主修掉可变容器

上一版留了一处「静默算错」:Rust 侧 列表.追加 改的是副本,原变量不动、 推导式返回空列表 —— 不报错但结果是错的,比直接报错危险得多。

根因是 Val::List(Vec&lt;Val&gt;) 是值类型、Clone 是深拷贝。 改成 Rc&lt;RefCell&lt;Vec&lt;…&gt;&gt;&gt;(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:

  1. Flow::Return 语义混淆 → 函数调用整个不可用。它同时表示「本帧结束,

请弹帧」和「刚压了新帧,别弹」,于是每次函数调用后新帧立刻被弹掉, 外层去执行那个还没拿到实参的 CALL,fn_base 下溢 → panic。 修前连 函数 加一(x):返回 x + 1 + 打印(加一(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&lt;RefCell&lt;…&gt;&gt;。这条比「报错」严重(拿到错结果却不知道), 已在 embed.md 与测试的 xfail 里明确标出。

M27 已交付:值→文本的统一

同一个值以前有两种写法:打印(空) 给 None、布尔给 True/False, 而 REPL 回显与 类型() 给中文「空/真/假」——用户得猜哪个算数。 根因是这件事有两份几乎一样的实现(runtime.jishi_repr 与 repl._repr_jishi),日子久了就漂移。

现在收敛成一处 runtime.jishi_repr,打印、文本()、字符串插值 ` {值} `、容器内元素、REPL 回显全走它:

python打印(空, 真, 假)              # 空 真 假
打印([1, 真, 空, "甲"])        # [1, 真, 空, '甲']
打印(`结果是 {真}`)            # 结果是 真
打印(路径.存在("docs"))        # 真(桥接回来的原生 bool 也一样)

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

# 超():调父类方法(多级继承也正确,不会绕回自己)
类 狗 继承 动物:
    函数 初始化(自身, 名字, 品种):
        超().初始化(名字)
        自身.品种 = 品种

# 是实例:类型判断(含子类)
如果 是实例(值, 动物):
    打印("是动物")

顺带修掉一个静默错误:非 原先比比较运算更紧,导致 非 年龄 &gt;= 18 被解析成 (非 年龄) &gt;= 18(恒为假)。项目教程 docs/tutorial/03 就踩了这个坑,「未成年人」分支永不触发。现修正为 Python not 的语义:非 年龄 &gt;= 18 = 非 (年龄 &gt;= 18)。

M22 已交付:包生态(默认索引 + 发布 + 冲突检测)

托管在本项目公开仓库(零服务端),可用 --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 供安装端校验。

`` 包「共同」的版本要求互相打架(当前装的是 1.5.0): · 甲 → 丙 → 共同 要求 &gt;=2.0.0 · 乙 → 共同 要求 &lt;2.0.0 建议:统一各上游对它的版本要求,或先卸载引起冲突的包 ``

只报告不自动求解——生态还小,把成因讲清楚比自动挑版本更有用。

M21 已交付:开发者工具(LSP + 格式化)

实时诊断(复用中文报错,含错误码/行列/修改提示)、 补全(关键字 / 内建 / 标准库 / 文档内符号;随机. 给模块函数带签名)、 悬停文档、跳转定义、文档符号大纲。 补全与悬停的数据源取自 ai.build_lang_spec(),不另抄一份,避免与语言本体漂移。 M42 又补了六项:格式化 / 重命名 / 快速修复 / 签名提示 / 工作区符号 (含跨文件跳转)/ 语义高亮——详见上面的 M42 一节。

变量 / 调用栈 / 监视求值;与 jishi 调试 共用同一套调试内核。

保留注释、三引号字符串与中文引号原样;支持 --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.9x8.4x
函数调用密集4.5x10.7x
算术混合运算5.2x18.1x
列表构建与遍历1.6x2.6x
递归斐波那契18.3x18.1x
当循环倒计数44.4x45.9x
字符串拼接0.30x0.34x(架构边界)

独立验证:遍历 范围(20000) 从 16.5ms → 3.8ms(4.4x)。

M19 已交付:发行改进(Windows 安装程序 + 三平台自动构建)

集成,产出 jishi-{版本}-windows-x64-setup.exe——用户级安装(免管理员)、 简体中文向导、开始菜单快捷方式、可选写入 PATH、注册标准卸载程序; 不再只提供压缩包。

windows / linux / macOS 矩阵构建,自动创建 Release 并附统一 SHA256SUMS。

屏蔽 → 孤儿提交 → force push;--tag 可触发三平台自动构建)。

M18 已交付:表达便利(切片 / 命令行参数 / 最小最大多参数 / 遮蔽治理)

字符串同样支持;起/止/步长都可省略或用表达式;三执行器同步(新增 BUILD_SLICE 指令 + C VM make_slice 宿主回调)。

[&quot;参数1&quot;, &quot;参数2&quot;];-- 之后全算脚本参数(与 Python 约定一致)。

为 别名(导入 文本 为 t),彻底规避遮蔽。

M17 已交付:排错体验补全(「出问题能快速定位」)

例如 外层 ← 第 8 行 → 中层 ← 第 7 行 → 里层 ← 第 5 行;协议 JSON 里 也带 trace 字段,agent 可程序化读取。

bug;现在输出「递归层数太深了」+ 调用链 + 「是不是忘了写结束条件」建议。

中文翻译 + 调用链,line/col/hint 不再空。

M16 已交付:发行版与安装体验(「下载 → 安装 → 打开终端就能跑」)

16MB zip + SHA256SUMS;打包产物实测跑通 hello/递归/语言卡/JSON 报错/C VM/标准库/MCP。

(现发布通道是 GitHub Releases)。

M15 已交付:真实项目驱动 + 语言收敛 1.0

全部跑通;反哺 5 个语法别扭点(无切片 / 最小二参 / 导入文本遮蔽等)。

M14 已交付:包管理与生态(「语言 → 生态」的最后一环)

--from-index,从远端索引「读索引→下载→解压→安装」全自动;重复安装报错, --force` 覆盖。

依赖 + 版本校验(*/==/&gt;=/^ 等)+ 循环依赖检测,报错全中文。

系统(os/subprocess),延续中文薄壳、零第三方依赖、沙箱白名单默认安全。

M13 已交付:跨语言字节码分发(脱离 Python 被任意宿主嵌入)

平台无关字节码(常量池只含标量,纯数据可被任意语言消费)。

71 个 SNIPPETS + 10 个 cases 与 Python VM 输出逐字节一致。

当前「最小可运行」(可变容器语义待 Rc<RefCell> 重构)。

M12 已交付:评测驱动闭环(「被大模型调用」有数据可证)

(需环境变量 JISHI_EVAL_API_KEY)。

M11 已交付:性能与分发

(getattr+call)合并为一次;列表场景基准 0.58x → 1.7x。7 场景中 6 场景 ≥1.5x(最高 47x)。字符串拼接是透明架构边界(HOST 句柄无法直接操作 Python str,详见 tools/benchmark.py 输出说明)。

装 wheel 即含 C VM,无需本机 gcc;干净 venv 实测三执行器全可用。 (当时写给 pip install jishi,但那个包从未发布到 PyPI——PyPI 通道已于 2026-09-22 放弃,见 docs/路线图.md「方向五 · 自主分发」。)

M10 已交付:生态扩展(「拿到就能写」的覆盖)

比较/解析)、正则(匹配/查找/替换/分组)、网络(获取/提交,urllib 薄封装 零第三方依赖,默认 10s 超时)。

包.json + .jsh 模块),执行模块后导出顶层函数/变量。

pyodide(WASM Python)浏览器内直接跑基石;docs/ai/prompt-guide.md 教 LLM 写基石。

M9 已交付:安全执行与 MCP 集成(「被大模型调用」的落地形态)

{ok:false,error:{code,line,col,message,hint,fix}},agent 程序化读取。

模块白名单(标准库默认只放行无副作用模块,Python 桥接默认禁并拦截网络/危险模块)。

隔离,真正超时 kill)/ eval_expr / describe_language;入口 jishi-mcp。 这是基石进入 WorkBuddy / 任意 MCP 宿主 agent 生态的入口。

JVal 值表示 + inline 构造器、JsHost 宿主回调表、导出函数声明), 第三方 C/Node/C#/Rust 宿主可直嵌基石引擎(examples/embed/hello.c 实测通过)。

M8 已交付:AI 原生工具链(「被大模型调用」的差异化核心)

内建/标准库/异常的 JSON(版本+内容哈希),供 agent 校验代码。

API + 惯用法示例 + 英文错误对照),可直接贴进 LLM 系统提示词。

{&quot;ok&quot;:false,&quot;errors&quot;:[{code,line,col,message,hint,fix}]},fix 字段 (如 {&quot;old&quot;:&quot;def&quot;,&quot;new&quot;:&quot;函数&quot;})让 agent 可程序化文本替换自愈。

「语法改动不得使一次通过率下降」的回归基线。

M7 已交付:表达力打磨(语法糖,三执行器同步)

→ 返回列表;新增 UNPACK 指令,数目不符中文报错。

有默认值的参数须在最后;支持方法/关键字参数配默认。

编译器脱糖为迭代+追加,C VM 复用现有指令零改动。

插值、{{/}} 字面花括号;parser 脱糖为拼接链,三执行器零改动。

print/True/and…),直接给出中文建议(「你是不是想写「函数」?」), 英文普通变量名零误伤。

M6 已交付:工程化

基准冒烟);.github/workflows/ci.yml 备用(ubuntu+windows × py3.10/3.13)。

关键字)、11 个代码片段、括号/注释/缩进配置;vsce 打包 vsix 已端到端验证。

语言规格 + 设计决策 + 教程 10 章渲染成带侧边栏的静态站(site/,已 gitignore)。

CLI/REPL/脚本全可用;无 C DLL 时 C VM 优雅降级、树遍历照常。

M5a 已交付:异常处理(三执行器同步支持)

类型名(值错误/类型错误/索引错误/键错误/除零错误/文件错误/运行期错误/异常) 是内建可调用对象(抛出 值错误(&quot;…&quot;)),捕获按 isinstance 匹配,基类抓子类;

出错时逐层弹帧匹配(详见 cvm/bytecode.md 的异常协议)。

M5b 已交付:面向对象(三执行器同步支持)

方法首参「自身」)、新建 类(参数);

(实例.方法() 自动把实例塞进「自身」首参);

(重入边界 stop_at/stop_depth 保证三执行器一致)。

M4 已交付:字节码编译 + C 虚拟机

cvm/build.py 一条命令出 DLL,jishi/cvm_bind.py ctypes 绑定 + 宿主回调;

(当循环 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 中文写法提示。