Skip to content

추상 구문 트리(AST)

Oxc AST는 모든 Oxc 도구의 기반입니다. parser, linter, transformer 등에 기여하려면 구조와 다루는 방법을 이해하는 것이 필수입니다.

AST 아키텍처

설계 원칙

Oxc AST는 다음 원칙에 따라 설계되었습니다.

  1. 성능 우선: 속도와 메모리 효율에 최적화
  2. 타입 안전성: Rust 타입 시스템으로 일반적인 실수 방지
  3. 명세 준수: ECMAScript 명세를 밀접하게 따름
  4. 명확한 의미: 다른 AST 형식에 남아 있는 모호성 제거

AST 다루기

AST 관련 코드 생성

AST 정의를 수정한 뒤에는 코드 생성 도구를 실행합니다.

bash
just ast

다음이 생성됩니다.

  • Visitor 패턴: 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)]: Visitor 메서드 생성
  • 수명 'a: arena 할당 메모리 참조

메모리 관리

AST는 효율적인 할당을 위해 메모리 arena를 사용합니다.

rust
use oxc_allocator::Allocator;

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

이점:

  • 빠른 할당: 항목마다 malloc를 호출하지 않음
  • 빠른 해제: arena 전체를 한 번에 드롭
  • 캐시 친화적: 선형 메모리 레이아웃
  • 참조 카운트 없음: 단순한 수명 관리

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

// 사용
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>) {
        // 식 변환
        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. enum에 추가:

    rust
    pub enum Statement<'a> {
        // ... 기존 variant
        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 Explorer 사용

다른 parser와 비교할 때는 ast-explorer.dev를 사용합니다.

  1. UI: 문법 하이라이트가 있는 현대적인 인터페이스
  2. 최신: 최신 parser 버전
  3. 복수 parser: Oxc, Babel, TypeScript 등 비교
  4. 형식 내보내기: JSON, 코드 생성 등

성능 고려 사항

메모리 레이아웃

AST는 캐시 효율을 위해 설계되었습니다.

rust
// 권장: 컴팩트한 표현
struct CompactNode<'a> {
    span: Span,           // 8 bytes
    flags: u8,            // 1 byte
    name: Atom<'a>,       // 8 bytes
}

// 피하기: 박싱 없이 큰 enum
enum LargeEnum {
    Small,
    Large { /* 200 bytes of data */ },
}

Arena 할당

모든 AST 노드는 arena에서 할당됩니다.

rust
// #[ast] 매크로가 자동 처리
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>>,
}

이를 통해 순회 중 바인딩 정보, 스코프 맥락, 타입 정보에 접근할 수 있습니다.

디버깅 팁

예쁜 출력(Pretty Printing)

디버그 포매터로 AST를 살펴봅니다.

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

Span 정보

오류 보고용으로 소스 위치를 추적합니다.

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