Skip to content

Архитектура парсера

У Oxc свой AST и свой парсер — на сегодня это самый быстрый и наиболее соответствующий спецификации парсер JavaScript и TypeScript (включая JSX и TSX) на Rust.

Парсер часто узкое место по производительности в цепочке JS-инструментов, поэтому даже мелкие улучшения каскадно отражаются на всём, что ниже. Развивая свой парсер, мы можем пробовать и внедрять хорошо изучённые приёмы ускорения.

Философия проектирования AST

Многие существующие инструменты опираются на estree как на спецификацию AST, но у неё немало двусмысленных узлов. Эта двусмысленность мешает разработке с estree.

AST Oxc отличается от estree: двусмысленные узлы убраны, введены отдельные типы. Например, вместо универсального Identifier из estree в AST Oxc есть BindingIdentifier, IdentifierReference и IdentifierName.

Такое разделение сильно улучшает опыт разработки и ближе следует спецификации ECMAScript.

Типы узлов AST

rust
// Instead of generic 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)

Архитектура производительности

Почему так быстро

  • Arena-память: AST выделяется в arena-аллокаторе для быстрых аллокаций и освобождения
  • Оптимизация строк: короткие строки инлайнятся через CompactString
  • Минимум кучи: кроме этих двух механизмов дополнительных кучевых аллокаций нет
  • Разделение обязанностей: привязка к областям, разрешение символов и часть синтаксических ошибок — в семантическом анализаторе

Детали управления памятью

Выделение в arena

rust
use oxc_allocator::Allocator;

// All AST nodes are allocated in this arena
let allocator = Allocator::default();
let ast_node = allocator.alloc(Expression::NumericLiteral(
    allocator.alloc(NumericLiteral { value: 42.0, span: SPAN })
));

Плюсы:

  • O(1) выделение: простой сдвиг указателя
  • O(1) освобождение: одним сбросом всей arena
  • Дружелюбно к кэшу: линейный доступ к памяти
  • Без фрагментации: непрерывное использование памяти

Интернирование строк через CompactString

rust
// Strings ≤ 24 bytes are stored inline (no heap allocation)
let short_name = CompactString::from("variableName");  // Stack allocated
let long_name = CompactString::from("a_very_long_variable_name_that_exceeds_limit");  // Heap allocated

Так сокращается число аллокаций для большинства идентификаторов и строковых литералов в JS.

Архитектура парсера

Двухфазная схема

Парсер Oxc двухфазный:

  1. Фаза разбора: строим AST с минимальной семантикой
  2. Семантическая фаза: области, символы и расширенные проверки ошибок
rust
// Phase 1: Parse to AST
let parser_result = Parser::new(&allocator, &source_text, source_type).parse();

// Phase 2: Semantic analysis
let semantic_result = SemanticBuilder::new()
    // Enable additional syntax checks not performed by the parser
    .with_check_syntax_error(true)
    .build(&parser_result.program);

Компоненты парсера

Лексер

  • Токены: исходный текст → структурированные токены
  • SIMD: пропуск пробелов векторными инструкциями
  • Контекст: различение regex и оператора деления

Рекурсивный спуск

  • Рукописный парсер: максимальная производительность
  • Восстановление после ошибок: понятные сообщения
  • Соответствие грамматике: строго по ECMAScript

Построитель AST

  • Типобезопасность: выгоды системы типов Rust
  • Память: прямое размещение в arena
  • Builder: удобное конструирование узлов

Стратегия соответствия спецификации

Охват тестами

  • Test262: 100% прохождение тестов соответствия ECMAScript
  • Babel: 99.62% совместимости с тестами парсера Babel
  • TypeScript: 99.86% совместимости с тестами компилятора TypeScript

Философия обработки ошибок

rust
// Meaningful error messages with source location
pub struct OxcDiagnostic {
    pub message: String,
    pub span: Span,
    pub severity: Severity,
    pub help: Option<String>,
}

Парсер даёт:

  • Точные позиции: абсолютные смещения в исходнике
  • Восстановление: продолжение разбора после ошибок
  • Подсказки: сообщения, по которым можно действовать

Расширенные возможности

Поддержка TypeScript

  • Стриппинг типов: удаление синтаксиса TypeScript
  • Декораторы: экспериментальный синтаксис
  • Пространства имён: модули и namespace
  • JSX: поддержка TSX

Направления исследований

  • SIMD для текста: векторная обработка строк
  • Кэш-поведение: меньше промахов при обходе памяти
  • Предсказание ветвлений: горячие пути разбора
  • Zero-copy: без лишнего копирования строк