추상 구문 트리 (AST)
Oxc의 AST는 모든 Oxc 도구의 기반이다. 파서, 린터, 변환기 및 기타 구성 요소에 기여하기 위해서는 그 구조와 작업 방법을 이해하는 것이 필수적이다.
AST 아키텍처
설계 원칙
Oxc AST는 다음의 원칙에 따라 설계되었다:
- 성능 우선: 속도와 메모리 효율성을 최적화함
- 타입 안전성: 러스트의 타입 시스템을 활용하여 일반적인 오류를 방지
- 사양 준수: ECMAScript 사양을 엄격히 따름
- 명확한 의미: 다른 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 노드 추가
구조체 정의:
rust#[ast(visit)] pub struct MyNewNode<'a> { pub span: Span, pub name: Atom<'a>, pub value: Expression<'a>, }열거형에 추가:
rustpub enum Statement<'a> { // ... 기존 변형들 MyNewStatement(Box<'a, MyNewNode<'a>>), }코드 생성 실행:
bashjust ast파싱 로직 구현:
rustimpl<'a> Parser<'a> { fn parse_my_new_node(&mut self) -> Result<MyNewNode<'a>> { // 파싱 구현 } }
다른 AST 형식과 비교
AST 탐색기 사용
다른 파서와 비교하려면 ast-explorer.dev를 사용한다:
- 더 나은 사용자 인터페이스: 최신 문법 강조 기능을 갖춘 현대적 인터페이스
- 최신 상태 유지: 최신 파서 버전
- 다양한 파서 지원: Oxc, Babel, TypeScript 등을 비교 가능
- 다양한 내보내기 형식: 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);