进展与缺口
把「已经能跑」和「还没写」分清楚。这一页的价值在于,它不给任何还没实现的东西留想象空间。
把「已经能跑」和「还没写」分清楚。这一页的价值在于,它不给任何还没实现的东西留想象空间。
一张表
| 部分 | 状态 |
|---|---|
| 词法分析 | ✅ 完成 |
| 语法分析 | ✅ 完成 |
| 语法树导出 | ✅ 完成 |
| 数字的抽象操作 | ✅ 完成 |
| 值模型 | ❌ 没有 |
| 对象模型(原型链、属性描述符、取值器设值器) | ❌ 没有 |
| 闭包与环境记录 | ❌ 没有 |
| 执行器(解释器或字节码 VM) | ❌ 没有 |
| 内建对象 | ❌ 没有 |
| 垃圾回收 | ❌ 没有 |
| 宿主接口 | ❌ 没有 |
与内核接通(<script> 执行、DOM 绑定、事件派发) | ❌ 没有 |
词法分析做了什么
全部数字字面量形态:各进制、数字分隔符、指数、BigInt 后缀、旧式八进制。
字符串转义完整,含八进制转义与 \u{...} 码位转义。
模板串按四段切分(头部、中段、尾部、无插值),正则字面量靠解析器触发的重扫识别。
注释、标识符、关键字、运算符都在。换行标记单独记录,供自动分号插入判断用。
语法分析做了什么
表达式层覆盖 26 种节点,语句层 21 种。函数声明与表达式、类、try/catch、for/for-in/for-of、解构、展开、模板串、可选链,都在。
上下文相关的几处也处理了:箭头函数的试探、严格模式指令、with/yield/await 的上下文校验、自动分号插入。
运行期有什么
一个模块,runtime/number.rs,217 行,10 个测试。
它实现的是规范里 Number 那一组抽象操作:
number_to_string—— 最短往返十进制输出。这是 JS 里最麻烦的一个格式化函数,String(0.1 + 0.2)要得到"0.30000000000000004"而不是"0.3",String(1e21)要得到"1e+21"而不是一长串零。to_int32/to_uint32/to_integer_or_zero/to_length—— 规范里的类型转换is_integer/is_safe_integer
除此之外,runtime/ 是空的。
最要紧的那个缺口
没有执行器。 没有解释器循环,没有字节码,没有编译到别的中间形式。语法树解析出来之后就停在那里。
所以现在能做的是「解析并检查一段 JS 的语法是否正确」,不能做的是「跑它」。任何「执行脚本」的功能都无从谈起,因为缺的不是某个内建对象,是最底下那一层。
第二要紧的是值模型。没有 Value 类型、没有对象、没有原型链,内建对象就没有东西可挂。
宿主接口还不存在
lib.rs 的模块文档写着「引擎本身不认识浏览器。要让它访问 DOM 或 window,由宿主实现 Host 接口把自己的对象接进来」。
这是一句设计意图,不是现状。crate 里没有 trait 关键字,没有 Host,也没有 builtins 模块。
要接通 <script>,第一步是把 Host 定义出来并想清楚它的形状——宿主提供什么、引擎需要什么、两边怎么互相调用。这一步做完之前,「实现 Host」这个说法没有指涉的对象。
和内核的关系
内核现在会解析 <script>:脚本数据被正确读成文本节点,</script> 的边界、双重转义、<!-- 里的脚本,都按规范处理。然后就没有然后了——内容没人执行。
所以打开一个带脚本的页面,看到的是「没有脚本时应该有的样子」。脚本产生的所有效果都不会出现。
顺序
要让它跑起来,依赖链大致是这样:
值模型 → 对象模型 → 环境记录与闭包 → 执行器 → 内建对象 → 宿主接口 → 与内核接通中间每一环都压着后一环。想跳过值模型直接写执行器是不成立的——执行器执行的是值上的操作。
现在能用它做什么
解析。写工具的时候这一点有实际用途:检查一段代码的语法、从语法树里提取信息(比如某个文件里定义了什么函数)、把语法树 dump 出来做对比。
use jse::{ast::dump, parser};
match parser::parse_script(source) {
Ok(program) => println!("{}", dump::program_to_string(&program)),
Err(error) => eprintln!("语法错误:{error}"),
}ParseError 带位置信息,能报出行列。