YSU
JSE

进展与缺口

把「已经能跑」和「还没写」分清楚。这一页的价值在于,它不给任何还没实现的东西留想象空间。

把「已经能跑」和「还没写」分清楚。这一页的价值在于,它不给任何还没实现的东西留想象空间。

一张表

部分状态
词法分析✅ 完成
语法分析✅ 完成
语法树导出✅ 完成
数字的抽象操作✅ 完成
值模型❌ 没有
对象模型(原型链、属性描述符、取值器设值器)❌ 没有
闭包与环境记录❌ 没有
执行器(解释器或字节码 VM)❌ 没有
内建对象❌ 没有
垃圾回收❌ 没有
宿主接口❌ 没有
与内核接通(<script> 执行、DOM 绑定、事件派发)❌ 没有

词法分析做了什么

全部数字字面量形态:各进制、数字分隔符、指数、BigInt 后缀、旧式八进制。

字符串转义完整,含八进制转义与 \u{...} 码位转义。

模板串按四段切分(头部、中段、尾部、无插值),正则字面量靠解析器触发的重扫识别。

注释、标识符、关键字、运算符都在。换行标记单独记录,供自动分号插入判断用。

语法分析做了什么

表达式层覆盖 26 种节点,语句层 21 种。函数声明与表达式、类、try/catchfor/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 带位置信息,能报出行列。

On this page