추상 구문 트리(AST)
Oxc AST는 모든 Oxc 도구의 기반입니다. parser, linter, transformer 등에 기여하려면 구조와 다루는 방법을 이해하는 것이 필수입니다.
AST 아키텍처
설계 원칙
Oxc AST는 다음 원칙에 따라 설계되었습니다.
- 성능 우선: 속도와 메모리 효율에 최적화
- 타입 안전성: Rust 타입 시스템으로 일반적인 실수 방지
- 명세 준수: ECMAScript 명세를 밀접하게 따름
- 명확한 의미: 다른 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 노드 추가
구조체 정의:
rust#[ast(visit)] pub struct MyNewNode<'a> { pub span: Span, pub name: Atom<'a>, pub value: Expression<'a>, }enum에 추가:
rustpub enum Statement<'a> { // ... 기존 variant MyNewStatement(Box<'a, MyNewNode<'a>>), }코드 생성 실행:
bashjust ast파싱 로직 구현:
rustimpl<'a> Parser<'a> { fn parse_my_new_node(&mut self) -> Result<MyNewNode<'a>> { // 파싱 구현 } }
AST 형식 비교
AST Explorer 사용
다른 parser와 비교할 때는 ast-explorer.dev를 사용합니다.
- UI: 문법 하이라이트가 있는 현대적인 인터페이스
- 최신: 최신 parser 버전
- 복수 parser: Oxc, Babel, TypeScript 등 비교
- 형식 내보내기: 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);