
(AST에 대한 내용을 자세히 다루는 글은 아니다. AST가 궁금하다면 위 링크를 참고하는 것을 추천한다)
tree-sitter 는 코드를 파싱해주는 증분 파서 제너레이터(incremental parser generator)이다. 증분 파싱을 지원하여 코드의 일부분만이 수정된 경우 해당 부분의 서브 트리만 갱신시키고 기존의 AST 트리는 재사용해 속도를 올린다.
공식적인 언어 바인딩이 지원되는 언어는 다음과 같다.
서드 파티로 지원되는 언어는 다음과 같다.
사용법은 다양하다. 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가 해당 코드를 어떻게 파싱하는지 볼 수 있다.
아래와 같은 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 형태로 보면 디버깅이 힘들어서 만들게 되었다.