Skip to content

规范

《ECMAScript® 语言规范》(持续演进的活文档)囊括了 JavaScript 语言的全部语义,任何人都可据此实现引擎。

为我们的解析器需要先阅读以下章节:

  • 第 5 章:符号约定
  • 第 11 章:ECMAScript 语言:源代码
  • 第 12 章:ECMAScript 语言:词法文法
  • 第 13–16 章:表达式、语句、函数、类、脚本与模块
  • Annex B:面向 Web 浏览器的附加 ECMAScript 特性
  • Annex C:ECMAScript 的严格模式

在规范中转跳时请记住:

  • 可点击条目都有锚点链接,形如 #sec-identifiers
  • 鼠标悬停可看到提示;点开 References 可列出所有引用。

符号约定

务必阅读 Chapter 5.1.5 Grammar Notation

本节重点:

递归

文法中用递归表示列表,例如:

ArgumentList :
  AssignmentExpression
  ArgumentList , AssignmentExpression

对应语义:

javascript
a, b = 1, c = 2
^_____________^ ArgumentList
   ^__________^ ArgumentList, AssignmentExpression,
          ^___^ AssignmentExpression

可选后缀 _opt_

前缀 _opt_ 表示片段可选:

VariableDeclaration :
  BindingIdentifier Initializer_opt

即可能存在也可能不存在 Initializer

javascript
var binding_identifier;
var binding_identifier = Initializer;
                       ______________ Initializer_opt

参数:[Yield][In][Return]……

这些是文法规则的「参数」,例如:

ScriptBody :
    StatementList[~Yield, ~Await, ~Return]

顶层脚本禁止顶层 yieldawaitreturn。而

ModuleItem :
  ImportDeclaration
  ExportDeclaration
  StatementListItem[~Yield, +Await, ~Return]

在模块语义下允许顶层 await

源代码类型

第 11.2 章 Types of Source Code 说明 脚本代码模块代码有本质差别;也可用 use strict 禁止旧时代的怪异行为以获得更清爽的语法表面。

Script Code并非默认严格——需要在文件头写 use strict 才可进入严格模式。
HTML 中写作 <script src="javascript.js"></script>

Module Code 自动处于严格模式。
HTML 中写作 <script type="module" src="main.mjs"></script>

ECMAScript 语言:词法文法

更深入的理解可参考 V8 博客 Understanding the ECMAScript spec

Automatic Semicolon Insertion

本节描述所有可以省略分号的情形,核心可归约为:

rust
    pub fn asi(&mut self) -> Result<()> {
        if self.eat(Kind::Semicolon) || self.can_insert_semicolon() {
            return Ok(());
        }
        let range = self.prev_node_end..self.cur_token().start;
        Err(SyntaxError::AutoSemicolonInsertion(range.into()))
    }

    pub const fn can_insert_semicolon(&self) -> bool {
        self.cur_token().is_on_new_line || matches!(self.cur_kind(), Kind::RCurly | Kind::Eof)
    }

asi() 须在适用位置手动调用,例如语句末尾:

rust
fn parse_debugger_statement(&mut self) -> Result<Statement<'a>> {
    let node = self.start_node();
    self.expect(Kind::Debugger)?;
    self.asi()?; 
    self.ast.debugger_statement(self.finish_node(node))
}

INFO

此节行文完全站在「从左向右解析源代码」的实现视角, 几乎决定了 Parser 的实现形态。jsparagus 作者对此有过吐槽 见此

ASI 的规范措辞既高层次又出人意料地像是过程式叙述(「当源代码从左向右解析时遇到……」),仿佛在给浏览器讲故事。据我所知这是规范唯一暗示解析器内部实现细节的地方——想换一种表述也很难。

Expressions, Statements, Functions, Classes, Scripts and Modules

熟练掌握句法文法尚需时日,但一旦理解,就能把规则映射到实际的 Parser 实现里。