Análise semântica
É o estágio que verifica se o programa realmente faz sentido após caber na gramática: precisamos cobrir todas as regras de Early Error da especificação ECMAScript.
Contexto gramatical ([Yield], [Await], …)
Quando a gramática proíbe yield/await como binding, há que emitir erro, por exemplo:
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.Caso clássico:
async function* foo() {
var yield, await;
}Porque AsyncGeneratorBody : FunctionBody[+Yield, +Await].
Snippet do parser da Rome/Biome checando yield:
// 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(), ""),
),Escopo
Erros declarativos no bloco:
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.Precisamos de uma árvore de escopos com todos os let/var/funções, capaz de subir até pais quando resolvemos símbolos. indextree casa bem.
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 {
pub strict_mode: bool,
pub flags: ScopeFlags,
pub lexical: IndexMap<Atom, SymbolId, FxBuildHasher>,
pub var: IndexMap<Atom, SymbolId, FxBuildHasher>,
pub function: IndexMap<Atom, SymbolId, FxBuildHasher>,
}A árvore pode nascer já no parser (desempenho) ou num passe separado sobre a AST (ScopeBuilder, enter_scope/leave_scope).
https://github.com/acornjs/acorn/blob/11735729c4ebe590e406f952059813f250a4cbd1/acorn/src/statement.js#L425-L437INFO
Com funções arrow ambíguas ((a,b) => … versus expressão sequencial), talvez você empurre escopo temporário e descarte caso não veja =>. Detalhes em cover grammar.
Visitor
Se você processa AST em dois passes convém usar o visitor pattern para separar travessia de lógica — em cada entrada/saída de bloco atualize ScopeBuilder.