끄적끄적 코딩
article thumbnail
728x90

1. 서론

프론트엔드 개발을 하다보면 ESLint, Prettier, Babel 같은 도구들을 접하게 됩니다. 이들의 핵심에는 모두 추상 구문 트리가 존재합니다. 이 글에서는 프론트엔드 관점에서 AST란 무엇인지, 왜 알아야 하는지, 실무에서 어떻게 활용되는지를 정리해보겠습니다.

https://en.wikipedia.org/wiki/Abstract_syntax_tree


2. AST란 무엇인가?

AST는 Abstract Syntax Tree, 추상 구문 트리라는 뜻입니다. 소스 코드를 컴퓨터가 이해할 수 있도록 구조화한 자료구조이며, 프로그래밍 언어의 문법적 구조를 트리 형태로 표현합니다. 우리가 작성한 코드는 단순한 문자열이지만, 컴퓨터는 이 문자열을 해석하고 실행하기 위해 내부적으로 여러 단계를 거치며 그 중 하나가 바로 AST를 생성하는 과정입니다.

코드가 AST로 바뀌는 과정은 언어학에서 문장을 이해하는 과정과 비슷합니다. 우리가 어떤 문장을 이해할 때, 단어의 품사를 파악하고 문장의 구조를 분석하는 것처럼, 컴파일러도 코드를 분석해 구조화해야 이해할 수 있습니다. 이 분석의 결과물이 바로 AST입니다.

AST는 코드의 추상적인 계층 구조를 나무(Tree) 형태로 표현합니다. 이 구조에는 변수연산자함수조건문 등 코드의 의미를 구성하는 핵심 요소들이 각각의 노드로 존재하며, 트리의 부모-자식 관계로 표현됩니다. 중요한 점은, 이 구조는 코드의 '형식적 문법'이 아니라 '논리적 의미'를 기반으로 구성된다는 것입니다.

또한 AST는 사소한 문법 요소(괄호, 세미콜론 등)는 생략하고, 오직 코드의 의미 분석에 필요한 정보만을 포함합니다. 이 점에서 Concrete Syntax Tree(CST) 또는 Parse Tree와 차별화됩니다. 이러한 추상화 덕분에 코드 변환이나 분석을 수행할 때 더 효율적으로 작동할 수 있습니다.

예를 들어 다음과 같은 JavaScript 코드를 보겠습니다:

<code />
const sum = (a, b) => a + b;

이 코드를 AST로 변환하면 다음과 같은 트리 구조를 갖습니다:

  • VariableDeclaration
    • VariableDeclarator
      • Identifier (sum)
      • ArrowFunctionExpression
        • Parameters: Identifier (a), Identifier (b)
        • Body: BinaryExpression (+)
          • Identifier (a)
          • Identifier (b)

이처럼 AST는 함수 정의, 변수 선언, 연산자, 인자 등을 트리 구조로 나누어 표현합니다. 이 구조는 코드가 어떤 동작을 하는지 기계적으로 분석하거나 수정할 수 있도록 돕습니다.


3. AST가 만들어지는 과정

소스 코드에서 AST가 만들어지기까지는 여러 단계의 처리 과정을 거칩니다. 이를 일반적으로 프론트엔드 컴파일러 단계라고 부르며, 다음과 같은 주요 단계로 구성됩니다.

3.1. 렉시컬 분석 (Lexical Analysis)

이 단계에서는 코드 문자열을 토큰(token)이라는 작은 단위로 나눕니다.

예를 들어 const x = 1 + 2;라는 코드는 다음과 같은 토큰으로 분리됩니다:

  • 키워드: const
  • 식별자: x
  • 연산자: =
  • 숫자 리터럴: 1, 2
  • 산술 연산자: +
  • 구분자: ;

이러한 작업은 렉서(lexer) 또는 스캐너(scanner)라고 불리는 컴포넌트가 수행합니다. 이 단계에서는 공백, 주석, 줄바꿈 등의 문법적으로 의미 없는 요소를 제거하고, 각 단어가 어떤 역할을 하는지 분류합니다.

3.2. 파싱 (Parsing)

토큰화된 데이터를 기반으로 문법 구조를 분석하고, 이를 트리 형태의 AST로 변환합니다. 이 과정은 파서가 담당합니다.

파서는 언어의 문법 규칙(Context-Free Grammar 등)에 따라 토큰을 계층적 구조로 조합하며, 프로그램의 논리적인 구문 구조를 나타내는 트리를 생성합니다.

이 과정에서 괄호나 세미콜론 같은 문법적 장식은 생략되고, 코드의 핵심 의미만 포함된 AST가 생성됩니다.

<code />
const x = 1 + 2;
  1. 렉서가 다음과 같은 토큰을 생성합니다: const, x, =, 1, +, 2, ;
  2. 파서가 이를 기반으로 다음과 같은 AST를 생성합니다:
<code />
VariableDeclaration └── VariableDeclarator ├── Identifier (x) └── BinaryExpression (+) ├── Literal (1) └── Literal (2)

3.3. 의미 분석 (Semantic Analysis)

AST가 만들어진 후에는 의미 분석 단계가 추가로 수행될 수 있습니다. 이 단계에서는 변수 선언 여부, 타입 일관성, 참조 유효성 등을 검사합니다. 예를 들어, 사용된 변수가 실제로 선언되었는지, 타입 간의 연산이 유효한지 등을 검사합니다.

또한 이 과정에서 심볼 테이블(symbol table)을 생성하고, 노드에 추가적인 메타데이터를 부여할 수 있습니다. 이러한 분석은 린터, 정적 분석기에서 중요한 역할을 합니다.


4. 프론트엔드에서 자주 쓰이는 AST 도구들

4.1. AST 도구

프론트엔드에서는 다음과 같은 도구들이 AST를 기반으로 작동합니다:

  • Babel: 코드 트랜스파일링. 최신 문법을 예전 문법으로 바꾸거나 JSX를 일반 JavaScript로 바꾸는 데 사용됩니다.
  • ESLint: 코드 품질과 스타일을 점검합니다. 커스텀 룰을 만들 때 AST 노드를 기준으로 검사합니다.
  • Prettier: 코드 스타일을 자동으로 정리해주는 도구로, AST를 기준으로 포맷팅합니다.
  • ts-morph: TypeScript에서 AST를 다루기 쉽게 도와주는 라이브러리입니다.
  • SWC: 빠른 빌드를 위해 Rust로 작성된 JavaScript/TypeScript 트랜스파일러입니다.
  • Recast: 코드 변환 시 원래의 스타일을 최대한 보존해주는 AST 기반 도구입니다.

4.2. AST의 활용 사례

  • 변수명이나 함수명을 일괄 변경해야 할 때
  • 불필요한 import 구문을 자동으로 제거할 때
  • 예전 컴포넌트 API를 새로운 방식으로 마이그레이션할 때
  • 주석이나 함수 구조를 기반으로 문서를 자동 생성할 때
  • 코드 전반에 특정 패턴을 탐지하고 수정할 때


5. 마무리

추상 구문 트리는 소스 코드를 구조적으로 해석할 수 있도록 도와주는 개념입니다. 코드를 단순한 문자열이 아닌 의미 있는 트리로 표현함으로써, 린팅, 포맷팅, 트랜스파일링, 정적 분석, 자동 리팩토링 등 다양한 개발 도구의 기반이 됩니다. 프론트엔드 개발에서 자주 사용하는 Babel, ESLint, Prettier 등은 모두 AST를 중심으로 작동하고 있으며, 그 흐름을 이해하면 도구를 잘 활용할 수 있습니다.

실제 개발에서는 반복적인 코드 수정 작업을 자동화하거나, 팀에 맞는 커스텀 린터 규칙을 만들거나, 코드 마이그레이션을 효율적으로 수행하는 데 AST 기반 도구들이 유용하게 쓰일 수 있습니다. 복잡한 문제를 조금 더 체계적으로 다루고 싶을 때, AST는 좋은 접근 방법이 되어 줄 수 있습니다.

검색 태그