콘텐츠로 이동

Tree-sitter 파싱

Tree-sitter를 사용하여 소스 코드를 구체적 구문 트리로 파싱합니다. go-tree-sitter 바인딩 기반입니다.

Tree-sitter는 다음과 같은 구문 트리를 생성합니다:

  • 소스 코드의 전체 구조를 표현
  • 코드 변경에 따라 점진적으로 업데이트
  • 구문 오류에 강건함 (부분 파싱)
  • S-표현식을 사용한 패턴 기반 쿼리 지원
local treesitter = require("treesitter")
treesitter 모듈은 선택 사항이며 `treesitter` 빌드 태그를 포함한 빌드에만 존재합니다. 공식 Wippy 바이너리에는 포함되어 있습니다. 소스에서 빌드하려면 `make build-wippy` 또는 `go build -tags treesitter`를 사용하세요. 태그가 없으면 `require("treesitter")`를 사용할 수 없습니다.
언어별칭루트 노드
Gogo, golangsource_file
JavaScriptjs, javascriptprogram
TypeScriptts, typescriptprogram
TSXtsxprogram
Pythonpython, pymodule
Lualuachunk
PHPphpprogram
C#csharp, cs, c#compilation_unit
HTMLhtml, html5document
Markdownmarkdown, mddocument
SQLsql-
local langs = treesitter.supported_languages()
-- {go = true, javascript = true, python = true, ...}
local code = [[
func hello() {
return "Hello!"
}
]]
local tree, err = treesitter.parse("go", code)
if err then
return nil, err
end
local root = tree:root_node()
print(root:kind()) -- "source_file"
print(root:child_count()) -- 최상위 선언 수
local code = [[
func hello() {}
func world() {}
]]
local tree = treesitter.parse("go", code)
local root = tree:root_node()
-- 모든 함수 이름 찾기
local query = treesitter.query("go", [[
(function_declaration name: (identifier) @func_name)
]])
local captures = query:captures(root, code)
for _, capture in ipairs(captures) do
print(capture.name, capture.text)
end
-- "func_name" "hello"
-- "func_name" "world"

소스 코드를 구문 트리로 파싱합니다. 내부적으로 임시 파서를 생성합니다.

local tree, err = treesitter.parse("go", code)
파라미터타입설명
languagestring언어 이름 또는 별칭
codestring소스 코드

반환: Tree, error

반복 파싱 또는 점진적 업데이트를 위한 파서를 생성합니다.

local parser = treesitter.parser()
parser:set_language("go")
local tree1 = parser:parse("package main")
-- 이전 트리를 사용한 점진적 파싱
local tree2 = parser:parse("package main\nfunc foo() {}", tree1)
parser:close()

반환: Parser

메서드설명
set_language(lang)파서 언어 설정, boolean, error 반환
get_language()현재 언어 이름 가져오기
parse(code, old_tree?)코드 파싱, 점진적 파싱을 위해 선택적으로 이전 트리 사용
set_timeout(duration)파싱 타임아웃 설정 ("1s" 같은 문자열 또는 나노초)
set_ranges(ranges)파싱할 바이트 범위 설정
reset()파서 상태 초기화
close()파서 리소스 해제
local tree = treesitter.parse("go", "package main")
local root = tree:root_node()
print(root:kind()) -- "source_file"
print(root:text()) -- "package main"
메서드설명
root_node()트리의 루트 노드 가져오기
root_node_with_offset(bytes, point)오프셋이 적용된 루트 가져오기
language()트리의 언어 객체 가져오기
copy()트리의 깊은 복사본 생성
walk()순회를 위한 커서 생성
edit(edit_table)점진적 편집 적용
changed_ranges(other_tree)변경된 범위 가져오기
included_ranges()파싱 중 포함된 범위 가져오기
dot_graph()DOT 그래프 표현 가져오기
close()트리 리소스 해제

소스 코드 변경 시 트리 업데이트:

local code = "func main() { x := 1 }"
local tree = treesitter.parse("go", code)
-- 편집 표시: 바이트 19에서 "1"을 "100"으로 변경
tree:edit({
start_byte = 19,
old_end_byte = 20,
new_end_byte = 22,
start_row = 0,
start_column = 19,
old_end_row = 0,
old_end_column = 20,
new_end_row = 0,
new_end_column = 22
})
-- 편집된 트리로 재파싱 (전체 파싱보다 빠름)
local parser = treesitter.parser()
parser:set_language("go")
local new_tree = parser:parse("func main() { x := 100 }", tree)

노드는 구문 트리의 요소를 나타냅니다.

local node = root:child(0)
-- 타입 정보
print(node:kind()) -- "package_clause"
print(node:type()) -- kind()와 동일
print(node:is_named()) -- 중요한 노드면 true
print(node:grammar_name()) -- 문법 규칙 이름
-- 자식
local child = node:child(0) -- 인덱스로 (0 기반)
local named = node:named_child(0) -- 명명된 자식만
local count = node:child_count()
local named_count = node:named_child_count()
-- 형제
local next = node:next_sibling()
local prev = node:prev_sibling()
local next_named = node:next_named_sibling()
local prev_named = node:prev_named_sibling()
-- 부모
local parent = node:parent()
-- 필드 이름으로
local name_node = func_decl:child_by_field_name("name")
local field = node:field_name_for_child(0)
-- 바이트 오프셋
local start = node:start_byte()
local end_ = node:end_byte()
-- 행/열 위치 (0 기반)
local start_pt = node:start_point() -- {row = 0, column = 0}
local end_pt = node:end_point() -- {row = 0, column = 12}
-- 소스 텍스트
local text = node:text()
if root:has_error() then
-- 트리에 구문 오류가 있음
end
if node:is_error() then
-- 이 특정 노드가 에러임
end
if node:is_missing() then
-- 파서가 오류 복구를 위해 이 노드를 삽입함
end
local sexp = node:to_sexp()
-- "(source_file (package_clause (package_identifier)))"

Tree-sitter의 쿼리 언어(S-표현식)를 사용한 패턴 매칭.

local query, err = treesitter.query("go", [[
(function_declaration
name: (identifier) @func_name
parameters: (parameter_list) @params
)
]])
파라미터타입설명
languagestring언어 이름
patternstringS-표현식 구문의 쿼리 패턴

반환: Query, error

-- 모든 캡처 가져오기 (평탄화됨)
local captures = query:captures(root, source_code)
for _, capture in ipairs(captures) do
print(capture.name) -- "@func_name"
print(capture.text) -- 실제 텍스트
print(capture.index) -- 캡처 인덱스
-- capture.node는 Node 객체
end
-- 매치 가져오기 (패턴별 그룹화)
local matches = query:matches(root, source_code)
for _, match in ipairs(matches) do
print(match.id, match.pattern)
for _, capture in ipairs(match.captures) do
print(capture.name, capture.node:text())
end
end
-- 쿼리 범위 제한
query:set_byte_range(0, 1000)
query:set_point_range({row = 0, column = 0}, {row = 10, column = 0})
-- 매치 제한
query:set_match_limit(100)
if query:did_exceed_match_limit() then
-- 더 많은 매치가 존재함
end
-- 타임아웃 (문자열 기간 또는 나노초)
query:set_timeout("500ms")
query:set_timeout(1000000000) -- 나노초 단위 1초
-- 패턴/캡처 비활성화
query:disable_pattern(0)
query:disable_capture("func_name")
local pattern_count = query:pattern_count()
local capture_count = query:capture_count()
local name = query:capture_name_for_id(0)
local id = query:capture_index_for_name("func_name")

각 단계에서 노드 객체를 생성하지 않는 효율적인 순회.

local cursor = tree:walk()
-- 루트에서 시작
print(cursor:current_node():kind()) -- "source_file"
print(cursor:current_depth()) -- 0
-- 탐색
if cursor:goto_first_child() then
print(cursor:current_node():kind())
print(cursor:current_depth()) -- 1
end
if cursor:goto_next_sibling() then
-- 다음 형제로 이동함
end
cursor:goto_parent() -- 부모로 돌아가기
cursor:close()
메서드반환설명
current_node()Node커서 위치의 노드
current_depth()integer깊이 (0 = 루트)
current_field_name()string?필드 이름 (있는 경우)
current_field_id()integer필드 ID (없으면 0)
current_descendant_index()integer현재 노드의 후손 인덱스
goto_parent()boolean부모로 이동
goto_first_child()boolean첫 번째 자식으로 이동
goto_last_child()boolean마지막 자식으로 이동
goto_next_sibling()boolean다음 형제로 이동
goto_previous_sibling()boolean이전 형제로 이동
goto_descendant(index)-인덱스로 후손으로 이동
goto_first_child_for_byte(n)integer?바이트를 포함하는 자식으로 이동
goto_first_child_for_point(pt)integer?포인트를 포함하는 자식으로 이동
reset(node)-커서를 노드로 초기화
reset_to(cursor)-커서를 다른 커서의 위치로 초기화
copy()Cursor커서 복사본 생성
close()-리소스 해제
local lang = treesitter.language("go")
print(lang:version()) -- ABI 버전
print(lang:node_kind_count()) -- 노드 타입 수
print(lang:field_count()) -- 필드 수
print(lang:parse_state_count()) -- 파싱 상태 수
-- 노드 종류 조회
local kind = lang:node_kind_for_id(1)
local id = lang:id_for_node_kind("identifier", true)
local is_named = lang:node_kind_is_named(1)
-- 필드 조회
local field_name = lang:field_name_for_id(1)
local field_id = lang:field_id_for_name("name")
조건종류재시도 가능
언어가 지원되지 않음errors.INVALID아니오
언어에 바인딩 없음errors.INVALID아니오
잘못된 쿼리 패턴errors.INVALID아니오
잘못된 위치errors.INVALID아니오
파싱 실패errors.INTERNAL아니오

에러 처리는 에러 처리를 참조하세요.

Tree-sitter 쿼리는 S-표현식 패턴을 사용합니다:

; 노드 타입 매치
(identifier)
; 필드 이름으로 매치
(function_declaration name: (identifier))
; @name으로 캡처
(function_declaration name: (identifier) @func_name)
; 여러 패턴
[
(function_declaration)
(method_declaration)
] @declaration
; 와일드카드
(_) ; 모든 노드
(identifier)+ ; 하나 이상
(identifier)* ; 0개 이상
(identifier)? ; 선택적
; 조건
((identifier) @var
(#match? @var "^_")) ; 정규식 매치

전체 문서는 Tree-sitter 쿼리 구문을 참조하세요.