Skip to content

추상 구문 트리 (AST)

Oxc의 AST는 모든 Oxc 도구의 기반이다. 파서, 린터, 변환기 및 기타 구성 요소에 기여하기 위해서는 그 구조와 작업 방법을 이해하는 것이 필수적이다.

AST 아키텍처

설계 원칙

Oxc AST는 다음의 원칙에 따라 설계되었다:

  1. 성능 우선: 속도와 메모리 효율성을 최적화함
  2. 타입 안전성: 러스트의 타입 시스템을 활용하여 일반적인 오류를 방지
  3. 사양 준수: ECMAScript 사양을 엄격히 따름
  4. 명확한 의미: 다른 AST 형식에 존재하는 모호성을 제거

AST 사용법

AST 관련 코드 생성

AST 정의를 수정한 후에는 코드 생성 도구를 실행한다:

bash
just ast

이 명령은 다음과 같은 항목들을 생성한다:

  • 방문자 패턴(Visitor patterns): AST 탐색을 위한
  • 빌더 메서드(Builder methods): AST 노드를 구성하기 위한
  • 트레잇 구현(Trait implementations): 일반적인 연산을 위한
  • TypeScript 타입(TypeScript types): 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: arena에 할당된 메모리에 대한 참조

메모리 관리

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바이트
}

// 피해야 함: 박싱 없이 큰 열거형
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);