A Árvore Sintática Abstrata (AST)
Como uma lista de tokens vira uma árvore que representa a estrutura do código — capturando precedência, aninhamento e hierarquia.
Estrutura, não sequência
Uma lista de tokens é só uma sequência linear — não captura que * tem prioridade sobre +,
nem que parênteses agrupam subexpressões. O parser resolve isso: consome a lista de tokens
e constrói uma Árvore Sintática Abstrata (AST).
Na AST, cada nodo representa uma construção da linguagem. Uma expressão 1 + 2 * 3
vira uma árvore onde * é filho de + — o que garante que a multiplicação
acontece antes. A ordem de operações emerge naturalmente da estrutura da gramática.
(1+2)*3
não viram nodo na AST: sua informação está na posição dos nodos filhos.
Explore a AST de expressões
Edite a expressão ou escolha um exemplo. Veja a AST construída em tempo real como árvore visual. Cada tipo de nodo tem uma cor.
Parser descendente recursivo
// Recursive descent parser — um método por nível de precedência class Parser { constructor(tokens) { this.tokens = tokens; this.pos = 0; } peek() { return this.tokens[this.pos]; } next() { return this.tokens[this.pos++]; } // Cada método lida com um nível de precedência parseExpr() { return this.parseAddSub(); } parseAddSub() { let left = this.parseMulDiv(); while (["+", "-"].includes(this.peek()?.value)) { const op = this.next().value; left = { type: "BinOp", op, left, right: this.parseMulDiv() }; } return left; } parseMulDiv() { let left = this.parsePrimary(); while (["*", "/"].includes(this.peek()?.value)) { const op = this.next().value; left = { type: "BinOp", op, left, right: this.parsePrimary() }; } return left; } parsePrimary() { const t = this.next(); if (t.type === "NUMBER") return { type: "Num", value: t.value }; if (t.type === "IDENT") return { type: "Id", value: t.value }; if (t.value === "(") { const expr = this.parseExpr(); this.next(); // consume ")" return expr; } } }
Gramática BNF
// Gramática BNF para expressões aritméticas // (define a estrutura que o parser reconhece) expr ::= comparison comparison ::= addition (('<' | '>' | '==' | '!=') addition)? addition ::= term (('+' | '-') term)* term ::= unary (('*' | '/') unary)* unary ::= '-' unary | primary primary ::= NUMBER | IDENTIFIER | '(' expr ')' // "1 + 2 * 3" → parser gera: // BinOp(+, Num(1), BinOp(*, Num(2), Num(3))) // Ordem de operações emerge naturalmente da gramática!
Construa um parser de expressões
Mini projeto: implemente um parser recursivo descendente para expressões aritméticas com os 4 operadores, parênteses e variáveis. Retorne a AST como objeto JS. Teste: (2 + 3) * 4, a - b / c.
Projeto principal: estenda o parser para suportar statements: let x = expr, if (cond) block else block, while (cond) block, e print expr. Gere a AST completa de um programa de 10 linhas.
Desafio extra: implemente um pretty printer: dada uma AST, regenere o código fonte formatado (com indentação correta para blocos if/while). Isso é como ferramentas como Prettier funcionam — transformam AST em código formatado.
Teste sua intuição
1 + 2 * 3, como a precedência de operadores aparece na AST?
Onde você encontra isso
JSX e Babel
Babel parseia JavaScript/JSX em uma AST (formato estandardizado: ESTree/Babel AST), transforma os nodos (ex: converte JSX em React.createElement), e regenera o código. Todos os transpilers funcionam assim.
Codemods
Ferramentas como jscodeshift transformam código em escala: "substitua todos os callbacks por async/await", "renomeie esse módulo em 10.000 arquivos". Elas manipulam a AST diretamente — seguro e preciso.
TypeScript checker
TypeScript parseia TypeScript em AST, depois faz type checking na AST — resolve tipos, verifica atribuições, infere genéricos. A AST é a representação central de todo o tooling do TypeScript.
SAST (análise estática)
Ferramentas de segurança como Semgrep escrevem regras em "AST patterns": os.system($X) encontra qualquer chamada que passe input dinâmico para o shell — mesmo que o nome da variável mude.