Skip to content

抽象语法树 (AST)

Oxc 的抽象语法树(AST)是所有 Oxc 工具的基础。理解其结构以及如何与之交互,对于参与解析器、检查器、转换器及其他组件的开发至关重要。

AST 架构

设计原则

Oxc AST 的设计遵循以下原则:

  1. 性能优先:针对速度和内存效率进行优化
  2. 类型安全:利用 Rust 的类型系统防止常见错误
  3. 规范兼容:严格遵循 ECMAScript 规范
  4. 语义清晰:消除其他 AST 格式中存在的歧义

与 AST 交互

生成与 AST 相关的代码

修改 AST 定义后,请运行代码生成工具:

bash
just ast

该命令将生成:

  • 访问者模式:用于遍历 AST
  • 构建方法:用于构造 AST 节点
  • 特质实现:用于常见操作
  • TypeScript 类型:用于 Node.js 绑定

AST 节点结构

每个 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)]:生成访问者方法
  • 生命周期 'a:对内存池分配内存的引用

内存管理

AST 使用内存池以实现高效分配:

rust
use oxc_allocator::Allocator;

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

优势:

  • 快速分配:无需单独的 malloc 调用
  • 快速释放:一次性丢弃整个内存池
  • 缓存友好:线性内存布局
  • 无引用计数:简单的生命周期管理

AST 遍历

访问者模式

使用生成的访问者来遍历 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!("发现函数: {:?}", func.id);
        walk_mut::walk_function_declaration(self, func);
    }
}

// 使用方式
let mut visitor = MyVisitor;
visitor.visit_program(&program);

可变访问者

对于变换操作,使用可变访问者:

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>) {
        // 转换表达式
        if expr.operator == BinaryOperator::Addition {
            // 修改 AST 节点
        }
        walk_mut::walk_binary_expression_mut(self, expr);
    }
}

AST 构建

构建者模式

使用 AST 构建者来创建节点:

rust
use oxc_ast::AstBuilder;

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

// 创建二元表达式: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. 定义结构体

    rust
    #[ast(visit)]
    pub struct MyNewNode<'a> {
        pub span: Span,
        pub name: Atom<'a>,
        pub value: Expression<'a>,
    }
  2. 添加到枚举中

    rust
    pub enum Statement<'a> {
        // ... 既有变体
        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>> {
            // 解析实现
        }
    }

对比 AST 格式

使用 AST 浏览器

要与其他解析器对比,可使用 ast-explorer.dev

  1. 更优的用户界面:现代化的界面,支持语法高亮
  2. 最新版本:使用最新的解析器版本
  3. 多种解析器:可对比 Oxc、Babel、TypeScript 等
  4. 导出格式:支持 JSON、代码生成

性能考虑

内存布局

AST 的设计注重缓存效率:

rust
// 优良:紧凑的表示形式
struct CompactNode<'a> {
    span: Span,           // 8 字节
    flags: u8,            // 1 字节
    name: Atom<'a>,       // 8 字节
}

// 避免:未使用 boxing 的大枚举
enum LargeEnum {
    Small,
    Large { /* 200 字节的数据 */ },
}

内存池分配

所有 AST 节点均在内存池中分配:

rust
// 由 #[ast] 宏自动处理
let node = self.ast.alloc(MyNode {
    span: SPAN,
    value: 42,
});

枚举大小测试

我们强制限制枚举大小较小:

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

位置信息

追踪源码位置以用于错误报告:

rust
let span = node.span();
println!("错误位于 {}:{}", span.start, span.end);