Skip to content

Árvore sintática abstrata (AST)

A AST do Oxc é a base de todas as ferramentas. Entender sua estrutura e como trabalhar com ela é essencial para contribuir com parser, linter, transformer e demais componentes.

Arquitetura da AST

Princípios de desenho

A AST do Oxc segue estes princípios:

  1. Desempenho em primeiro lugar: otimizada para velocidade e uso de memória
  2. Segurança de tipos: aproveita o sistema de tipos do Rust para evitar erros comuns
  3. Conformidade com a spec: alinhada à especificação ECMAScript
  4. Semântica clara: reduz ambiguidades presentes em outros formatos de AST

Trabalhando com a AST

Gerar código relacionado à AST

Quando alterar definições da AST, rode o gerador:

bash
just ast

Isso gera:

  • Padrões visitor: para percorrer a AST
  • Métodos builder: para construir nós
  • Implementações de traits: para operações comuns
  • Tipos TypeScript: para bindings Node.js

Estrutura de um nó da AST

Todo nó segue um padrão consistente:

rust
#[ast(visit)]
pub struct FunctionDeclaration<'a> {
    pub span: Span,
    pub id: Option<BindingIdentifier<'a>>,
    pub generator: bool,
    pub r#async: bool,
    pub params: FormalParameters<'a>,
    pub body: Option<FunctionBody<'a>>,
    pub type_parameters: Option<TSTypeParameterDeclaration<'a>>,
    pub return_type: Option<TSTypeAnnotation<'a>>,
}

Componentes-chave:

  • span: posição no código-fonte
  • #[ast(visit)]: gera métodos de visitor
  • Lifetime 'a: referências à memória alocada na arena

Gestão de memória

A AST usa arena para alocar com eficiência:

rust
use oxc_allocator::Allocator;

let allocator = Allocator::default();
let ast = parser.parse(&allocator, source_text, source_type)?;

Benefícios:

  • Alocação rápida: sem malloc por nó
  • Liberação rápida: descarta a arena inteira de uma vez
  • Amigável ao cache: layout linear na memória
  • Sem RC: gestão simples por lifetime

Percursos na AST

Padrão Visitor

Use o visitor gerado para percorrer a AST:

rust
use oxc_ast::visit::{Visit, walk_mut};

struct MyVisitor;

impl<'a> Visit<'a> for MyVisitor {
    fn visit_function_declaration(&mut self, func: &FunctionDeclaration<'a>) {
        println!("Found function: {:?}", func.id);
        walk_mut::walk_function_declaration(self, func);
    }
}

// Usage
let mut visitor = MyVisitor;
visitor.visit_program(&program);

Visitor mutável

Para transformações, use o visitor mutável:

rust
use oxc_ast::visit::{VisitMut, walk_mut};

struct MyTransformer;

impl<'a> VisitMut<'a> for MyTransformer {
    fn visit_binary_expression(&mut self, expr: &mut BinaryExpression<'a>) {
        // Transform the expression
        if expr.operator == BinaryOperator::Addition {
            // Modify the AST node
        }
        walk_mut::walk_binary_expression_mut(self, expr);
    }
}

Construção da AST

Padrão Builder

Use o builder da AST para criar nós:

rust
use oxc_ast::AstBuilder;

let ast = AstBuilder::new(&allocator);

// Create a binary expression: a + b
let left = ast.expression_identifier_reference(SPAN, "a");
let right = ast.expression_identifier_reference(SPAN, "b");
let expr = ast.expression_binary_expression(
    SPAN,
    left,
    BinaryOperator::Addition,
    right,
);

Funções auxiliares

Padrões comuns vêm como helpers:

rust
impl<'a> AstBuilder<'a> {
    pub fn expression_numeric_literal(&self, span: Span, value: f64) -> Expression<'a> {
        self.alloc(Expression::NumericLiteral(
            self.alloc(NumericLiteral { span, value, raw: None })
        ))
    }
}

Fluxo de desenvolvimento

Adicionar novos nós à AST

  1. Defina a struct:

    rust
    #[ast(visit)]
    pub struct MyNewNode<'a> {
        pub span: Span,
        pub name: Atom<'a>,
        pub value: Expression<'a>,
    }
  2. Inclua no enum:

    rust
    pub enum Statement<'a> {
        // ... existing variants
        MyNewStatement(Box<'a, MyNewNode<'a>>),
    }
  3. Rode a geração de código:

    bash
    just ast
  4. Implemente o parsing:

    rust
    impl<'a> Parser<'a> {
        fn parse_my_new_node(&mut self) -> Result<MyNewNode<'a>> {
            // Parsing implementation
        }
    }

Comparar formatos de AST

AST Explorer

Para comparar com outros parsers, use ast-explorer.dev:

  1. Interface melhor: UI moderna com destaque de sintaxe
  2. Atualizado: versões recentes dos parsers
  3. Vários parsers: Oxc, Babel, TypeScript etc.
  4. Exportação: JSON, geração de código

Considerações de desempenho

Layout na memória

A AST privilegia eficiência de cache:

rust
// Good: Compact representation
struct CompactNode<'a> {
    span: Span,           // 8 bytes
    flags: u8,            // 1 byte
    name: Atom<'a>,       // 8 bytes
}

// Avoid: Large enums without boxing
enum LargeEnum {
    Small,
    Large { /* 200 bytes of data */ },
}

Alocação na arena

Todos os nós vão para a arena:

rust
// Automatically handled by #[ast] macro
let node = self.ast.alloc(MyNode {
    span: SPAN,
    value: 42,
});

Testes de tamanho de enum

Impedimos enums grandes demais:

rust
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
#[test]
fn no_bloat_enum_sizes() {
    use std::mem::size_of;
    assert_eq!(size_of::<Statement>(), 16);
    assert_eq!(size_of::<Expression>(), 16);
    assert_eq!(size_of::<Declaration>(), 16);
}

Tópicos avançados

Atributos customizados na AST

Adicione atributos para ferramentas específicas:

rust
#[ast(visit)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct MyNode<'a> {
    #[cfg_attr(feature = "serialize", serde(skip))]
    pub internal_data: u32,
    pub public_field: Atom<'a>,
}

Integração com análise semântica

Ligue nós da AST a informação semântica:

rust
#[ast(visit)]
pub struct IdentifierReference<'a> {
    pub span: Span,
    pub name: Atom<'a>,
    #[ast(ignore)]
    pub reference_id: Cell<Option<ReferenceId>>,
}

Assim as ferramentas acessam bindings, escopo e tipos durante o traverse.

Dicas de depuração

Pretty print

Use o formatter de debug para inspecionar a AST:

rust
println!("{:#?}", ast_node);

Informação de span

Rastreie posições para relatórios de erro:

rust
let span = node.span();
println!("Error at {}:{}", span.start, span.end);