Skip to content

抽象構文木(AST)

Oxc の AST はすべてのツールの基盤です。構造と扱い方を押さえると、パーサー・リンター・トランスフォーマなどへのコントリビュートがはかどります。

AST の設計

原則

Oxc の AST は次を重視して設計されています。

  1. 性能第一: 速度とメモリ効率
  2. 型安全: Rust の型で表現の誤りを防ぐ
  3. 仕様への追従: ECMAScript 仕様に沿う
  4. 明瞭な意味論: 他フォーマットに比べ曖昧さを減らす

AST の操作

関連コードの生成

AST 定義を変えたら生成ツールを走らせます。

bash
just ast

生成されるものの例:

  • Visitor: AST 走査
  • Builder: ノード構築ヘルパ
  • トレイト実装: 共通操作
  • TypeScript 型: Node.js バインディング向け

ノードの形

AST ノードはだいたい次のパターンです。

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>>,
}

主な部品:

  • span: ソース上の位置
  • #[ast(visit)]: visitor 用メソッドを生成
  • ライフタイム 'a: アリーナ上のメモリへの参照

メモリ管理

AST はアリーナでまとめて確保します。

rust
use oxc_allocator::Allocator;

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

利点:

  • 高速な確保: 都度 malloc しない
  • まとめて解放: アリーナをドロップすれば一括
  • キャッシュに優しい: だいたい線形配置
  • 参照カウント不要: ライフタイムで管理

AST の走査

Visitor

生成された visitor で 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

変換には可変 visitor を使います。

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);
    }
}

AST の構築

Builder

AstBuilder でノードを組み立てます。

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,
);

ヘルパー

よくある形はヘルパーで短く書けます。

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 })
        ))
    }
}

開発フロー

新しい AST ノードを足す

  1. struct を定義する:

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

    rust
    pub enum Statement<'a> {
        // ... existing variants
        MyNewStatement(Box<'a, MyNewNode<'a>>),
    }
  3. コード生成を走らせる:

    bash
    just ast
  4. パース処理を実装する:

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

AST 形式の比較

AST Explorer

他パーサーと比べるには ast-explorer.dev が便利です。

  1. UI: モダンでシンタックスハイライトあり
  2. バージョン: 新しいパーサーを試しやすい
  3. 複数エンジン: Oxc、Babel、TypeScript などを並べて比較
  4. 出力形式: JSON やコード生成へのエクスポート

性能面の考慮

メモリレイアウト

キャッシュ効率を意識したレイアウトです。

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 */ },
}

アリーナ割り当て

ノードはすべてアリーナ上に確保します。

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

enum サイズのテスト

enum のサイズが肥大化しないようテストで縛ります。

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);
}

応用

カスタム AST 属性

ツール向けに追加属性を付けられます。

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>,
}

セマンティック解析との連携

AST ノードにセマンティック情報をくっつけます。

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

これにより、走査中に束縛情報・スコープ・型情報などへアクセスできます。

デバッグのヒント

整形表示

デバッグフォーマッタで AST を眺めます。

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

Span

エラー報告用にソース位置を追います。

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