語義分析
語義分析是檢查我們的原始碼是否正確的過程。我們需要根據 ECMAScript 規範中的所有「早期錯誤」規則進行檢查。
上下文
對於像是 [Yield]
或 [Await]
這種語法上下文,當語法禁止它們時,需要引發錯誤,例如
BindingIdentifier[Yield, Await] :
Identifier
yield
await
13.1.1 Static Semantics: Early Errors
BindingIdentifier[Yield, Await] : yield
* It is a Syntax Error if this production has a [Yield] parameter.
* BindingIdentifier[Yield, Await] : await
It is a Syntax Error if this production has an [Await] parameter.
需要對以下程式碼引發錯誤
javascript
async *
function foo() {
var yield, await;
};
因為 AsyncGeneratorDeclaration
的 AsyncGeneratorBody
有 [+Yield]
和 [+Await]
AsyncGeneratorBody :
FunctionBody[+Yield, +Await]
一個在 Biome 中檢查 yield
關鍵字的範例
rust
// https://github.com/rome/tools/blob/5a059c0413baf1d54436ac0c149a829f0dfd1f4d/crates/rome_js_parser/src/syntax/expr.rs#L1368-L1377
pub(super) fn parse_identifier(p: &mut Parser, kind: JsSyntaxKind) -> ParsedSyntax {
if !is_at_identifier(p) {
return Absent;
}
let error = match p.cur() {
T![yield] if p.state.in_generator() => Some(
p.err_builder("Illegal use of `yield` as an identifier in generator function")
.primary(p.cur_range(), ""),
),
作用域
對於宣告錯誤
14.2.1 Static Semantics: Early Errors
Block : { StatementList }
* It is a Syntax Error if the LexicallyDeclaredNames of StatementList contains any duplicate entries.
* It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList also occurs in the VarDeclaredNames of StatementList.
我們需要添加一個作用域樹。作用域樹包含在其中宣告的所有 var
和 let
。它也是一個父節點指標樹,我們可以在其中向上導航樹,並在父作用域中搜尋繫結識別符。我們可以使用 indextree
這個資料結構。
rust
use indextree::{Arena, Node, NodeId};
use bitflags::bitflags;
pub type Scopes = Arena<Scope>;
pub type ScopeId = NodeId;
bitflags! {
#[derive(Default)]
pub struct ScopeFlags: u8 {
const TOP = 1 << 0;
const FUNCTION = 1 << 1;
const ARROW = 1 << 2;
const CLASS_STATIC_BLOCK = 1 << 4;
const VAR = Self::TOP.bits | Self::FUNCTION.bits | Self::CLASS_STATIC_BLOCK.bits;
}
}
#[derive(Debug, Clone)]
pub struct Scope {
/// [Strict Mode Code](https://tc39.es/ecma262/#sec-strict-mode-code)
/// [Use Strict Directive Prologue](https://tc39.es/ecma262/#sec-directive-prologues-and-the-use-strict-directive)
pub strict_mode: bool,
pub flags: ScopeFlags,
/// [Lexically Declared Names](https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames)
pub lexical: IndexMap<Atom, SymbolId, FxBuildHasher>,
/// [Var Declared Names](https://tc39.es/ecma262/#sec-static-semantics-vardeclarednames)
pub var: IndexMap<Atom, SymbolId, FxBuildHasher>,
/// Function Declarations
pub function: IndexMap<Atom, SymbolId, FxBuildHasher>,
}
作用域樹可以在解析器內部建立以提高效能,也可以在單獨的 AST 處理階段建立。
通常,需要一個 ScopeBuilder
rust
pub struct ScopeBuilder {
scopes: Scopes,
root_scope_id: ScopeId,
current_scope_id: ScopeId,
}
impl ScopeBuilder {
pub fn current_scope(&self) -> &Scope {
self.scopes[self.current_scope_id].get()
}
pub fn enter_scope(&mut self, flags: ScopeFlags) {
// Inherit strict mode for functions
// https://tc39.es/ecma262/#sec-strict-mode-code
let mut strict_mode = self.scopes[self.root_scope_id].get().strict_mode;
let parent_scope = self.current_scope();
if !strict_mode
&& parent_scope.flags.intersects(ScopeFlags::FUNCTION)
&& parent_scope.strict_mode
{
strict_mode = true;
}
let scope = Scope::new(flags, strict_mode);
let new_scope_id = self.scopes.new_node(scope);
self.current_scope_id.append(new_scope_id, &mut self.scopes);
self.current_scope_id = new_scope_id;
}
pub fn leave_scope(&mut self) {
self.current_scope_id = self.scopes[self.current_scope_id].parent().unwrap();
}
}
然後,我們在解析函式中相應地呼叫 enter_scope
和 leave_scope
,例如在 acorn 中
javascript
https://github.com/acornjs/acorn/blob/11735729c4ebe590e406f952059813f250a4cbd1/acorn/src/statement.js#L425-L437
資訊
這種方法的一個缺點是,對於箭頭函式,我們可能需要建立一個暫時的作用域,然後如果它不是箭頭函式而是序列表達式,則隨後將其丟棄。這在 cover grammar 中詳細說明。
訪問者模式
如果我們為了簡單起見決定在另一個處理階段建立作用域樹,那麼 AST 中的每個節點都需要以深度優先的前序方式訪問並建立作用域樹。
我們可以使用 訪問者模式 來將遍歷過程與對每個物件執行的操作分開。
在訪問時,我們可以相應地呼叫 enter_scope
和 leave_scope
來建立作用域樹。