抽象構文木(AST)
Oxc の AST はすべてのツールの基盤です。構造と扱い方を押さえると、パーサー・リンター・トランスフォーマなどへのコントリビュートがはかどります。
AST の設計
原則
Oxc の AST は次を重視して設計されています。
- 性能第一: 速度とメモリ効率
- 型安全: Rust の型で表現の誤りを防ぐ
- 仕様への追従: ECMAScript 仕様に沿う
- 明瞭な意味論: 他フォーマットに比べ曖昧さを減らす
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 ノードを足す
struct を定義する:
rust#[ast(visit)] pub struct MyNewNode<'a> { pub span: Span, pub name: Atom<'a>, pub value: Expression<'a>, }enum に variant を足す:
rustpub enum Statement<'a> { // ... existing variants MyNewStatement(Box<'a, MyNewNode<'a>>), }コード生成を走らせる:
bashjust astパース処理を実装する:
rustimpl<'a> Parser<'a> { fn parse_my_new_node(&mut self) -> Result<MyNewNode<'a>> { // Parsing implementation } }
AST 形式の比較
AST Explorer
他パーサーと比べるには ast-explorer.dev が便利です。
- UI: モダンでシンタックスハイライトあり
- バージョン: 新しいパーサーを試しやすい
- 複数エンジン: Oxc、Babel、TypeScript などを並べて比較
- 出力形式: 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);