文档 / 架构
架构
运行时按转换阶段分层;标准库与解释器通过明确的导入接口连接。
解析器与求值器
源码执行流水线
Python 源码
→ py/tokenizer 词法分析,产出 token
→ py/parser 语法分析,构造 AST
→ py/interp 求值、作用域、函数与模块导入
→ 输出 / 异常这条流水线把语法结构和运行行为分开。tokenizer 只解释字符层面的边界,不决定表达式优先级;parser 消费 token 并建立 AST,不执行表达式;interpreter 消费 AST 并维护运行期状态。这样,调用方可以单独使用 tokenizer 做语法高亮、单独使用 parser 检查结构,也可以把三者组合起来执行程序。发生错误时,来源层会保留必要的行列信息,便于上层显示诊断。
Tokenizer:字符到 token
词法器读取源码字符,识别名称、数字、字符串、运算符、缩进和换行等类别,并记录原文及源位置。缩进属于 Python 语法的一部分,因此不能简单丢弃空白:词法器需要把块边界转换成结构化的 INDENT/DEDENT 类 token。多行字符串、转义字符和文件末尾的换行也会影响后续解析。消费者应使用 token 的类型和位置字段,而不是自行扫描 text 字段来猜测语法。
Parser:token 到 AST
parser 根据语法规则组合 token,形成表达式、语句、函数定义、控制流和导入等 AST 节点。AST 是结构,不是求值结果:二元运算节点会保存左右操作数与运算符,调用节点保存被调用对象和实参。语法不成立时,parser 应产生可定位的语法错误,而不是返回部分树并让解释器猜测。作为独立模块,它适合用于格式检查、语法工具以及对照测试。
Interpreter:AST 到运行结果
解释器在运行期维护作用域、局部变量、函数调用帧、模块缓存和异常传播。表达式按 Python 的求值顺序执行,语句负责绑定名称、进入控制分支或离开循环。Python 的异常需要跨函数边界传播,直到被匹配的处理器捕获;未捕获异常则成为程序结果的一部分。解释器不依赖宿主 Python,它通过 JavaScript 实现语义,并把宿主环境能力限定在显式提供的接口内。
libstd 与接线
libstd/ 中的模块使用 ESM。Python 代码通过导入器请求模块名,导入器查找对应的 JavaScript 模块,完成初始化并把导出名称放入 Python 模块对象。模块实现应遵守 Python 可见的名称、参数、返回值和异常契约;内部可以采用 JavaScript 结构,但不能把实现细节泄漏为不稳定的 Python API。标准库模块不应反向依赖解释器内部目录,否则独立测试和替换运行时都会变得困难。
| 类别 | 职责 | 接入方式 |
|---|---|---|
| 纯函数模块 | 字符串、数学或编码等输入输出转换 | 导出与 Python 名称对应的函数,并复用共享值转换 |
| 状态型模块 | 维护缓存、注册表或流对象状态 | 通过模块实例或工厂隔离每个运行环境的状态 |
| 运行时支撑 | 异常、对象适配和内建协议 | 由运行时层提供稳定契约,模块通过公共接口调用 |
| 宿主相关模块 | 文件、进程或平台能力 | 按需注入宿主适配器;不可用时明确报告能力边界 |
共享的 Python 异常类型集中在 libstd/_runtime/errors.js。按需注入可以让浏览器环境不必加载 Node 专属实现,也使调用方能明确选择开放哪些宿主能力。模块清单记录当前实现状态;导入成功表示模块入口可用,不代表所有 API 和边界都已经与 CPython 完全一致。
GUI 渲染层
GUI 能力把 Python 可见接口和绘制宿主分开。Python 侧通过控件与场景图对象描述状态;JavaScript 渲染核心将对象属性转换为绘制指令;浏览器宿主使用 Canvas 完成图形绘制并接收指针、键盘和尺寸事件。适配契约负责把宿主事件转成运行时可以处理的事件,避免每个控件直接依赖浏览器全局对象。
同一套控件模型可以接到不同宿主,但渲染精度、字体、输入法和系统窗口行为仍受宿主影响。Canvas 绘制不等于浏览器 DOM 控件:无障碍树、原生文本选择和平台菜单需要额外适配。GUI 能力的公开覆盖范围以仓库 docs/GUI-COVERAGE.md 为准,不能仅依据模块名称推断所有桌面行为都可用。
浏览器游乐场
浏览器游乐场将解释器核心打包为 classic Web Worker 可加载的脚本。主线程负责编辑器、运行按钮、输出区域和画布宿主;Worker 负责解析与执行 Python 程序,并通过消息返回结果。这样同步计算不会直接占用页面绘制线程,用户仍能看到运行状态并操作停止按钮。
消息边界需要传递纯数据,例如源码、运行标识、输出片段和结构化错误;不能假设 DOM 节点或函数可以跨 Worker 克隆。同步死循环无法处理 Worker 内部的停止消息,所以硬中断通过 worker.terminate() 结束执行上下文,再创建新的 Worker 继续运行。中断也会清除该 Worker 的临时状态,因此需要保留的编辑内容应由主线程管理。
性能与加载取舍
模块按需导入可以缩短首次加载时间,并避免简单程序为未使用的标准库支付解析和初始化成本。需要跨浏览器加载的 Worker 包则倾向于自包含:classic Worker 的加载环境比主页面模块图更受限,把入口依赖打进单一脚本可降低部署路径和 MIME 配置问题。自包含包体积更大,因此页面本身应延迟创建 Worker,只有用户开始执行时才加载运行时。
跨层转换也有成本。token、AST 节点、Python 对象与宿主值之间需要保持语义,而不能为了减少分配而共享可变对象。性能评估应使用代表性程序测量解析、求值、模块初始化和绘制等阶段;小型交互任务优先保证响应和可中断性,大量计算任务则需要结合实际负载判断是否适合在浏览器端执行。
实现契约与扩展边界
实现约定
API 命名沿用 Python 的 snake_case;Python 的 None 映射为 JavaScript null;关键边界与异常消息由测试约束。完整约定见 docs/CONVENTIONS.md。