Skip to content

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:

javascript
async function* foo() {
  var yield, await;
}

Porque AsyncGeneratorBody : FunctionBody[+Yield, +Await].

Snippet do parser da Rome/Biome checando 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(), ""),
        ),

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.

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 {
    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).

javascript
https://github.com/acornjs/acorn/blob/11735729c4ebe590e406f952059813f250a4cbd1/acorn/src/statement.js#L425-L437

INFO

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.