본문으로 건너뛰기
김신건의 로그

[PLT] Abstract Syntax Tree (AST)

· 수정 · 📖 약 2분 · 904자/단어 #plt #compiler #ast #tree #visitor-pattern
AST, Abstract Syntax Tree, 추상 구문 트리, syntax tree, AST traversal, AST visitor pattern, concrete syntax tree, CST, parse tree

정의

Abstract Syntax Tree (AST, 추상 구문 트리) 는 프로그램의 문법 구조를 트리로 표현 한 자료구조입니다. 파서의 출력물이자, 이후 컴파일러/인터프리터의 거의 모든 단계 (semantic 분석, 최적화, 코드 생성) 의 입력.

핵심: 소스 코드의 본질적 구조 만 남기고, 문법 세부 (괄호, 세미콜론, 공백) 제거.

왜 트리인가

프로그래밍 언어의 구조는 자연스럽게 재귀적:

  • 함수는 statement 들의 목록
  • statement 는 expression 을 포함
  • expression 은 다른 expression 을 포함

트리 = 재귀 구조 표현의 자연스러운 자료구조.

AST 예시

소스 코드:

if (x > 0) {
  y = x * 2;
} else {
  y = 0;
}

AST:

flowchart TD
  Root["IfStatement"]
  Root --> Cond["BinaryOp: >"]
  Root --> Then["Block"]
  Root --> Else["Block"]

  Cond --> C1["Identifier: x"]
  Cond --> C2["Number: 0"]

  Then --> Assign1["Assignment"]
  Assign1 --> T1["Identifier: y"]
  Assign1 --> T2["BinaryOp: *"]
  T2 --> T3["Identifier: x"]
  T2 --> T4["Number: 2"]

  Else --> Assign2["Assignment"]
  Assign2 --> E1["Identifier: y"]
  Assign2 --> E2["Number: 0"]

각 노드는 의미 있는 구조:

  • IfStatement: cond, then, else 세 자식
  • BinaryOp: op, left, right
  • Assignment: target, value
  • Block: statements 리스트

AST vs Parse Tree (Concrete Syntax Tree)

두 개념 자주 혼동.

Parse Tree (CST)

문법 규칙을 그대로 반영. 모든 non-terminal 이 노드.

Expression
├── Term
│   ├── Factor
│   │   └── Number(2)
│   └── OP(+)
│   └── Term
│       ├── Factor
│       │   └── Number(3)
│       └── OP(*)
│       └── Factor
│           └── Number(4)

2 + 3 * 4 의 CST 는 Expression → Term → Factor 등 중간 노드 다수.

AST

본질 구조만:

BinaryOp(+)
├── Number(2)
└── BinaryOp(*)
    ├── Number(3)
    └── Number(4)

간결. 이후 처리에 유리.

언제 CST 유지?:

  • 소스 코드 포매터 (원본 재구성 필요)
  • 리팩터링 도구 (주석/공백 보존)
  • IDE (원본 위치 정확성)

AST 노드 정의 (TypeScript 예시)

// 표현식
type Expression =
  | NumberLiteral
  | StringLiteral
  | BooleanLiteral
  | Identifier
  | BinaryOp
  | UnaryOp
  | Call
  | MemberAccess
  | Assignment;

interface NumberLiteral {
  kind: 'NumberLiteral';
  value: number;
  location: SourceLocation;
}

interface Identifier {
  kind: 'Identifier';
  name: string;
  location: SourceLocation;
}

interface BinaryOp {
  kind: 'BinaryOp';
  op: '+' | '-' | '*' | '/' | '==' | '<' | '>' | '&&' | '||';
  left: Expression;
  right: Expression;
  location: SourceLocation;
}

interface UnaryOp {
  kind: 'UnaryOp';
  op: '-' | '!' | '++' | '--';
  operand: Expression;
  location: SourceLocation;
}

interface Call {
  kind: 'Call';
  callee: Expression;
  args: Expression[];
  location: SourceLocation;
}

// 문장
type Statement =
  | ExpressionStatement
  | VariableDeclaration
  | IfStatement
  | WhileStatement
  | ReturnStatement
  | FunctionDeclaration
  | Block;

interface IfStatement {
  kind: 'IfStatement';
  condition: Expression;
  thenBranch: Statement;
  elseBranch?: Statement;
  location: SourceLocation;
}

interface FunctionDeclaration {
  kind: 'FunctionDeclaration';
  name: string;
  params: Identifier[];
  body: Block;
  location: SourceLocation;
}

interface SourceLocation {
  file: string;
  line: number;
  column: number;
}

AST 순회 (Traversal)

AST 를 처리할 때 대부분 재귀 순회.

Depth-First

function visit(node):
    switch node.kind:
        case 'NumberLiteral':
            processNumber(node)
        case 'BinaryOp':
            visit(node.left)
            visit(node.right)
            processBinaryOp(node)
        case 'IfStatement':
            visit(node.condition)
            visit(node.thenBranch)
            if node.elseBranch:
                visit(node.elseBranch)
            processIf(node)
        ...

방문 시점

  • Pre-order: 자식 방문 전 노드 처리
  • Post-order: 자식 모두 방문 후 노드 처리
  • In-order: 이진 트리 특수 (프로그래밍 언어 AST 는 잘 안 씀)

Visitor Pattern

전형적 OOP 패턴:

interface Visitor<T> {
  visitNumber(node: NumberLiteral): T;
  visitBinaryOp(node: BinaryOp): T;
  visitIdentifier(node: Identifier): T;
  visitIf(node: IfStatement): T;
  // ...
}

class Evaluator implements Visitor<Value> {
  visitNumber(node) {
    return node.value;
  }
  visitBinaryOp(node) {
    const left = this.visit(node.left);
    const right = this.visit(node.right);
    return applyOp(node.op, left, right);
  }
  visitIdentifier(node) {
    return this.env.get(node.name);
  }
  // ...

  visit(node): Value {
    switch (node.kind) {
      case 'NumberLiteral': return this.visitNumber(node);
      case 'BinaryOp': return this.visitBinaryOp(node);
      case 'Identifier': return this.visitIdentifier(node);
      // ...
    }
  }
}

Discriminated union + switch 방식 (TypeScript, Rust 스타일) 이 최근 관용.

AST 변환 (Transformation)

새 AST 를 만들어 반환하는 함수:

function replaceConstants(node: Expression): Expression {
  if (node.kind === 'Identifier' && node.name === 'PI') {
    return { kind: 'NumberLiteral', value: 3.14159, location: node.location };
  }
  if (node.kind === 'BinaryOp') {
    return {
      ...node,
      left: replaceConstants(node.left),
      right: replaceConstants(node.right),
    };
  }
  return node;
}

Babel plugin, TypeScript transformer, ESLint auto-fix 가 이 패턴.

실전 사용 사례

1. 인터프리터

AST 순회하며 실행. Tree-walking interpreter.

function evaluate(node: Expression, env: Environment): Value {
  switch (node.kind) {
    case 'NumberLiteral': return node.value;
    case 'BinaryOp':
      const l = evaluate(node.left, env);
      const r = evaluate(node.right, env);
      return applyOp(node.op, l, r);
    case 'Identifier':
      return env.lookup(node.name);
  }
}

Crafting Interpreters 의 첫 인터프리터.

2. 컴파일러 (코드 생성)

AST → 목표 코드:

NumberLiteral 42       → PUSH 42
BinaryOp(+, l, r)      → visit(l); visit(r); ADD

3. 정적 분석 / 린터

ESLint 는 AST 를 순회하며 규칙 적용:

// no-unused-vars 규칙
Program(node) {
  const declared = collectDeclared(node);
  const used = collectUsed(node);
  for (const varName of declared) {
    if (!used.has(varName)) {
      report(`Unused variable: ${varName}`);
    }
  }
}

4. 트랜스파일러

Babel: ES2020+ AST → ES5 AST (다운그레이드).

TypeScript: TS AST → JS AST + 별도 타입 검사.

5. IDE 지원

  • 자동완성: 커서 위치 AST 노드 확인
  • Go to definition: identifier → declaration
  • Rename: 같은 심볼의 모든 참조 재작성
  • 포매팅: AST → 표준 형식으로 재출력 (Prettier)

6. Code Mods (자동 리팩터링)

jscodeshift, ts-morph: 대규모 자동 리팩터링. AST 변환.

AST Explorer

https://astexplorer.net/ 에서 여러 언어 파서의 AST 를 실시간 확인 가능. 학습 필수 도구.

: JavaScript const x = 42; 의 AST:

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "kind": "const",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": { "type": "Identifier", "name": "x" },
          "init": { "type": "Literal", "value": 42 }
        }
      ]
    }
  ]
}

언어별 AST 표기법

JavaScript

  • ESTree 명세: 사실상 표준. Acorn, Babel, ESLint 채택.
  • SWC AST, Oxc AST: Rust 기반 최근 도구, 대부분 ESTree 호환.

TypeScript

TypeScript 자체 컴파일러 AST. ESTree 와 다름 (typescript-eslint 는 변환).

Python

ast 모듈. ast.parse(source) → AST.

import ast
tree = ast.parse("x = 42")
print(ast.dump(tree))

Rust

rustc_ast, syn (proc macro 용).

Go

go/ast 표준 패키지.

함정

WARNING

Source location 저장. AST 노드에 원본 위치 (파일/줄/열) 없으면 에러 메시지 최악.

CAUTION

주석/공백 처리. Prettier / 포매터 하려면 AST 에 관련 정보 유지 필요. 대부분 컴파일러는 버림.

WARNING

AST 재사용 시 immutable 지향. 변환은 새 AST 생성. 원본 수정은 버그 온상.

IMPORTANT

Discriminated union + switch 가 OOP visitor 보다 현대적. TypeScript exhaustive check.

CAUTION

AST 순회 순서가 의미 결정. Pre-order 인지 post-order 인지 명확히 (예: 순회 중 변수 정의 발견 순서).

관련 위키

이 글의 용어 (8개)
[JavaScript] Bundling (번들링 개요)javascript
정의 JavaScript 번들링 (Bundling) 은 여러 소스 파일 (JS, CSS, 이미지, JSON 등) 을 브라우저나 런타임이 효율적으로 실행할 수 있는 최소한의 결과물…
[PLT] Interpreter vs Compilerplt
정의 프로그래밍 언어의 실행 모델 두 접근: - Compiler (컴파일러): 소스 → 목표 코드 (기계어, 바이트코드) 로 변환. 실행은 나중. - Interpreter (인터…
[PLT] IR, 최적화, 코드 생성 (Compiler Backend)plt
정의 컴파일러의 백엔드 는 semantic 분석까지 마친 AST 를 실제 실행 가능한 목표 코드로 변환합니다. 세 주요 단계: 1. IR (Intermediate Represen…
[PLT] Lexical Analysis (어휘 분석)plt
정의 어휘 분석 (Lexical Analysis) 은 소스 코드 문자 시퀀스 를 의미 있는 최소 단위인 토큰 (Token) 으로 분해하는 과정입니다. 담당 컴포넌트를 Lexer …
[PLT] Parsing & Grammarsplt
정의 Parsing (구문 분석) 은 lexer 의 토큰 스트림 을 언어의 문법 규칙 에 따라 구조화된 트리 (AST, Abstract Syntax Tree) 로 조립하는 과정입…
[PLT] Semantic Analysis (의미 분석)plt
정의 Semantic Analysis (의미 분석) 는 파서가 생성한 AST 를 검사하여 문법적으로 올바르지만 의미상 오류인 경우 를 발견하고, 각 이름 (identifier) …
[TypeScript] Decoratorstypescript
정의 Decorator 는 class, method, accessor, property, parameter 에 부착되는 함수로 그 대상을 검사/수정할 수 있습니다. TypeScr…
프로그래밍 언어론 (Programming Language Theory)plt
정의 프로그래밍 언어론 (Programming Language Theory, PLT) 은 프로그래밍 언어를 어떻게 설계하고, 정의하고, 처리하는가 를 다루는 컴퓨터 과학 분야입니…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기