tree-sitter로 만져보는 AST

miinhho·2025년 10월 17일
1
post-thumbnail

(AST에 대한 내용을 자세히 다루는 글은 아니다. AST가 궁금하다면 위 링크를 참고하는 것을 추천한다)

tree-sitter 란?

tree-sitter 는 코드를 파싱해주는 증분 파서 제너레이터(incremental parser generator)이다. 증분 파싱을 지원하여 코드의 일부분만이 수정된 경우 해당 부분의 서브 트리만 갱신시키고 기존의 AST 트리는 재사용해 속도를 올린다.

tree-sitter 의 특징

  • 모든 프로그래밍 언어를 파싱할 수 있을 만큼 다양한 언어를 지원한다.
  • 텍스트 편집기에서 모든 키 입력을 파싱할 수 있을 만큼 빠르다.
  • 구문 오류가 있는 경우에도 유용한 결과를 제공할 만큼 강력하다.
  • 의존성이 없으므로 런타임 라이브러리를 모든 애플리케이션에 내장할 수 있다.
  • 474개의 언어를 파싱할 수 있다. (2025/10/17일 기준)

공식적인 언어 바인딩이 지원되는 언어는 다음과 같다.

  • C#
  • Go
  • Haskell
  • Java(JDK 22+)
  • JavaScript(Node.js)
  • JavaScript(WASM)
  • Kotlin
  • Python
  • Rust
  • Swift
  • Zig

서드 파티로 지원되는 언어는 다음과 같다.

  • C#(.NET)
  • C++
  • Crystal
  • D
  • Delphi
  • ELisp
  • Go
  • Gulie
  • Janet
  • Java(JDK 8+)
  • Java(JDK 11+)
  • Julia
  • Lua
  • Ocaml
  • Odin
  • Perl
  • Pharo
  • PHP
  • R
  • Ruby

어떻게 사용하나요?

사용법은 다양하다. CLI로 사용하거나, 전용 쿼리 언어를 사용하거나, 지원하는 바인딩의 API를 호출하거나... 일단 Python 바인딩을 예로 들자면 다음과 같다.

import tree_sitter_python as tspython
from tree_sitter import Language, Parser

# 파싱할 언어 설정
PY_LANGUAGE = Language(tspython.language())

# 해당 언어의 파서 생성
parser = Parser(PY_LANGUAGE)

# 파싱
tree = parser.parse(
    bytes(
        """
def foo():
    if bar:
        baz()
""",
        "utf8"
    )
)

해당 언어의 바인딩으로 사용하는 것이 편리하지만, 관련 자료가 많이 없다. 파싱된 tree는 어떤지, 그 tree의 어떤 정보를 사용할 수 있는지 등과 같은 정보를 얻기 힘들다.

CLI와 C언어 API, 전용 쿼리 언어에 대한 설명은 공식 문서에 자세히 나와있으니 CLI나 전용 쿼리 언어로 사용하는 것을 추천한다.

Python 바인딩에서 전용 쿼리 언어는 다음과 같이 사용할 수 있다.

query = Query(
    PY_LANGUAGE,
    """
(function_definition
  name: (identifier) @function.def
  body: (block) @function.block)

(call
  function: (identifier) @function.call
  arguments: (argument_list) @function.args)
""",
)
# query 에서 반환된 정보를 QueryCursor 로 탐색할 수 있다
query_cursor = QueryCursor(query)
captures = query_cursor.captures(tree.root_node)

# captures["function.def"][0]
# captures["function.block"][0]
# captures["function.call"][0]
# captures["function.args"][0]

tree-sitter playground

tree-sitter playground 에서 실제로 tree-sitter가 해당 코드를 어떻게 파싱하는지 볼 수 있다.

아래와 같은 JavaScript 코드를 tree-sitter playground 에서 파싱한 예시이다.

function add(a, b) {
	return a + b
}
program [0, 0] - [3, 0]
  function_declaration [0, 0] - [2, 1]
    name: identifier [0, 9] - [0, 12]
    parameters: formal_parameters [0, 12] - [0, 18]
      identifier [0, 13] - [0, 14]
      identifier [0, 16] - [0, 17]
    body: statement_block [0, 19] - [2, 1]
      return_statement [1, 1] - [1, 13]
        binary_expression [1, 8] - [1, 13]
          left: identifier [1, 8] - [1, 9]
          right: identifier [1, 12] - [1, 13]

마무리

현실적으로 개발자가 AST를 다룰 일이 많지 않지만, AST를 통해 아래와 같이 코드를 분석해 코드간 의존성을 분석하는 등 많은 작업을 할 수 있다.
(eslint, babel, prettier 등의 도구도 AST를 활용한다.)


사이드 프로젝트 중 나온 부산물이다. AST 분석 후 나온 그래프 구조를 json 형태로 보면 디버깅이 힘들어서 만들게 되었다.

profile
재미있는 걸 좋아합니다

0개의 댓글