Skip to content

パーサのアーキテクチャ

Oxc は独自の AST とパーサを持ち、Rust で書かれた JavaScript / TypeScript(JSX・TSX を含む)パーサの中で、現時点でもっとも高速で適合性が高い部類にあります。

パーサは JavaScript ツールングではしばしばボトルネックになるため、わずかな改善でも下流ツールに連鎖的な効果があります。自前のパーサによって、よく研究された性能技法を試し実装する機会も得ています。

AST 設計の考え方

多くの既存ツールは AST 仕様として estree に依存しますが、estree には曖昧なノードが多いという欠点があります。開発時の混乱を招きやすいです。

Oxc の AST は estree とは異なり、曖昧なノードを削除し、区別された型を導入します。例として、汎用の estree Identifier の代わりに、BindingIdentifierIdentifierReferenceIdentifierName のような具体的な型を用意します。

この区別により、ECMAScript 仕様との対応が明確になり、開発体験が大きく向上します。

AST ノード型

rust
// 汎用 Identifier の代わり
pub struct BindingIdentifier<'a> {
    pub span: Span,
    pub name: Atom<'a>,
}

pub struct IdentifierReference<'a> {
    pub span: Span,
    pub name: Atom<'a>,
    pub reference_id: Cell<Option<ReferenceId>>,
}

pub struct IdentifierName<'a> {
    pub span: Span,
    pub name: Atom<'a>,
}

意味論的な明確さ

この方針がもたらす明確さ:

  • BindingIdentifier: 変数宣言(let x = 1
  • IdentifierReference: 変数の使用(console.log(x)
  • IdentifierName: プロパティ名(obj.property

パフォーマンス設計

なぜこれほど速いか

  • メモリアリーナ: AST をメモリアリーナ上に確保し、高速な確保・解放を実現
  • 文字列最適化: 短い文字列は CompactString でインライン化
  • ヒープ利用の最小化: 上記以外のヒープ確保は行わない
  • 責務の分離: スコープ束縛、シンボル解決、一部構文エラーは意味解析に委ねる

メモリ管理の詳細

アリーナ確保

rust
use oxc_allocator::Allocator;

// すべての AST ノードをこのアリーナに確保
let allocator = Allocator::default();
let ast_node = allocator.alloc(Expression::NumericLiteral(
    allocator.alloc(NumericLiteral { value: 42.0, span: SPAN })
));

利点:

  • O(1) 確保: ポインタを進めるだけ
  • O(1) 解放: アリーナごと一括ドロップ
  • キャッシュに優しい: 線形なメモリ配置
  • 断片化なし: 連続したメモリ利用

CompactString による文字列の扱い

rust
// 24 バイト以下の文字列はインライン(ヒープなし)
let short_name = CompactString::from("variableName");  // スタック
let long_name = CompactString::from("a_very_long_variable_name_that_exceeds_limit");  // ヒープ

大部分の JavaScript 識別子や文字列リテラルでの割り当てを減らせます。

パーサの構造

二段階設計

Oxc パーサは二段階です。

  1. パース段階: 最小限の意味解析で AST を構築
  2. 意味段階: スコープ解析、シンボル解決、高度な構文エラー検査
rust
// 段階 1: AST へパース
let parser_result = Parser::new(&allocator, &source_text, source_type).parse();

// 段階 2: 意味解析
let semantic_result = SemanticBuilder::new()
    // パーサが行わない追加の構文チェックを有効化
    .with_check_syntax_error(true)
    .build(&parser_result.program);

パーサの構成要素

Lexer(レキサ)

  • トークン生成: ソースを構造化トークンへ
  • SIMD 最適化: 空白スキップなどに SIMD
  • 文脈依存: 正規表現と除算演算子の曖昧性解消

再帰下降パーサ

  • 手書き: 最大性能のための独自実装
  • エラー回復: 意味のあるメッセージ付きの高度なエラー処理
  • 文法準拠: ECMAScript 仕様に忠実に追随

AST ビルダ

  • 型安全: Rust の型システムで正しさを担保
  • メモリ効率: アリーナへの直接確保
  • ビルダパターン: ノード構築を便利に

適合戦略

テストスイートのカバレッジ

  • Test262: ECMAScript 適合テストで 100% 合格
  • Babel: Babel パーサテストとの互換約 99.62%
  • TypeScript: TypeScript コンパイラテストとの互換約 99.86%

エラー処理の方針

rust
// ソース位置付きの分かりやすいメッセージ
pub struct OxcDiagnostic {
    pub message: String,
    pub span: Span,
    pub severity: Severity,
    pub help: Option<String>,
}

パーサが提供するもの:

  • 正確なエラー位置: ソース上の正確な位置
  • 回復戦略: エラー後もパースを継続
  • 役立つ提案: 実行可能なエラーメッセージ

高度な機能

TypeScript 対応

  • 型の剥ぎ取り: TypeScript 固有構文の除去
  • デコレータのパース: 実験的デコレータに対応
  • 名前空間: モジュールと名前空間をフルサポート
  • JSX 統合: TypeScript + JSX(TSX)

研究テーマ

  • SIMD テキスト処理: ベクトル化された文字列操作
  • キャッシュ最適化: メモリアクセスパターンの最小化
  • 分岐予測: ホットなパース経路の最適化
  • ゼロコピーパース: 不要な文字列コピーの排除