Парсинг с Tree-sitter
Парсинг с Tree-sitter
Заголовок раздела «Парсинг с Tree-sitter»Парсинг исходного кода в синтаксические деревья с помощью Tree-sitter. Модуль основан на биндингах go-tree-sitter.
Возможности Tree-sitter:
- Полное представление структуры исходного кода
- Инкрементальное обновление при изменениях
- Устойчивость к синтаксическим ошибкам (частичный парсинг)
- Поиск по шаблонам с помощью S-выражений
Подключение
Заголовок раздела «Подключение»local treesitter = require("treesitter")Поддерживаемые языки
Заголовок раздела «Поддерживаемые языки»| Язык | Псевдонимы | Корневой узел |
|---|---|---|
| Go | go, golang | source_file |
| JavaScript | js, javascript | program |
| TypeScript | ts, typescript | program |
| TSX | tsx | program |
| Python | python, py | module |
| Lua | lua | chunk |
| PHP | php | program |
| C# | csharp, cs, c# | compilation_unit |
| HTML | html, html5 | document |
| Markdown | markdown, md | document |
| SQL | sql | - |
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, errend
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)| Параметр | Тип | Описание |
|---|---|---|
language | string | Название языка или псевдоним |
code | string | Исходный код |
Возвращает: 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)
-- Отметить правку: "1" заменено на "100" в позиции 19tree: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 -- Узел добавлен парсером для восстановления после ошибкиendS-выражение
Заголовок раздела «S-выражение»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 )]])| Параметр | Тип | Описание |
|---|---|---|
language | string | Название языка |
pattern | string | Шаблон в виде S-выражения |
Возвращает: 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 — объект узла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()) endendУправление запросом
Заголовок раздела «Управление запросом»-- Ограничение области поиска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()) -- 1end
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()) -- версия ABIprint(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)* ; ноль или более(identifier)? ; опционально
; Предикаты((identifier) @var (#match? @var "^_")) ; совпадение по регуляркеПолная документация: Tree-sitter Query Syntax.