Грамматика языка Tree-sitter
Загрузка грамматики языка
Tree-sitter использует грамматику языка для разбора текста на этом языке. В Emacs грамматика языка представлена символом. Например, грамматика языка C представлена символом c, а c можно передать функциям tree-sitter в качестве аргумента language.
Грамматики языков для Tree-sitter распространяются в виде динамических библиотек. Чтобы использовать грамматику языка в Emacs, необходимо убедиться, что динамическая библиотека установлена в системе. Emacs ищет грамматики языков в нескольких местах в следующем порядке:
- сначала в списке каталогов, заданном переменной
treesit-extra-load-path; - затем в подкаталоге tree-sitter каталога, заданного переменной
user-emacs-directory(см. Файл инициализации); - и, наконец, в системных каталогах по умолчанию для динамических библиотек.
В каждом из этих каталогов Emacs ищет файл с расширениями, заданными переменной dynamic-library-suffixes.
Если Emacs не может найти библиотеку или при её загрузке возникают проблемы, Emacs сигнализирует об ошибке treesit-load-language-error. Данные этого сигнала могут иметь один из следующих видов:
(not-found error-msg …)Это означает, что Emacs не удалось найти библиотеку грамматики языка.
(symbol-error error-msg)Это означает, что Emacs не удалось найти в библиотеке ожидаемую функцию, которую должна экспортировать каждая библиотека грамматики языка.
(version-mismatch error-msg)Это означает, что версия библиотеки грамматики языка несовместима с версией библиотеки tree-sitter.
Во всех этих случаях error-msg может содержать дополнительные сведения о сбое.
- Функция: treesit-language-available-p language &optional detail
-
Эта функция возвращает значение, отличное от
nil, если грамматика языка для language существует и может быть загружена.Если detail не равно
nil, функция возвращает(t . nil), если language доступен, и(nil . data), если он недоступен. data — это данные сигналаtreesit-load-language-error.
По соглашению имя файла динамической библиотеки для language имеет вид libtree-sitter-language.ext, где ext — системно-зависимое расширение для динамических библиотек. Также по соглашению функция, предоставляемая этой библиотекой, называется tree_sitter_language. Если библиотека грамматики языка не следует этому соглашению, добавьте запись
(language library-base-name function-name)
в список переменной treesit-load-name-override-list, где library-base-name — базовое имя файла динамической библиотеки (обычно libtree-sitter-language), а function-name — функция, предоставляемая библиотекой (обычно tree_sitter_language). Например,
(cool-lang "libtree-sitter-coool" "tree_sitter_cooool")
для языка, который считает себя слишком «крутым», чтобы следовать соглашениям.
- Функция: treesit-library-abi-version &optional min-compatible
Эта функция возвращает версию двоичного интерфейса приложения (ABI) грамматики языка, поддерживаемую библиотекой tree-sitter. По умолчанию она возвращает последнюю версию ABI, поддерживаемую библиотекой, но если min-compatible не равно
nil, она возвращает самую старую версию ABI, которую библиотека всё ещё может поддерживать. Библиотеки грамматик языков должны быть собраны для версий ABI в диапазоне от самой старой до самой новой версии, поддерживаемой библиотекой tree-sitter; иначе библиотека не сможет их загрузить.
- Функция: treesit-language-abi-version language
Эта функция возвращает версию ABI библиотеки грамматики языка, загруженной Emacs для language. Если language недоступен, функция возвращает
nil.
- Функция: treesit-language-display-name language
-
Эта функция преобразует language в подходящее отображаемое имя. Например, она преобразует
rubyв «Ruby», аcpp— в «C++».У большинства языков «обычные» названия, и их отображаемое имя — это просто имя символа с заглавной первой буквой. Для языков с «нестандартными» названиями treesit-language-display-name-alist сопоставляет символы языков с их отображаемыми именами.
Если пакет основного режима использует язык с «нестандартным» названием, при загрузке ему следует добавить соответствующее сопоставление в treesit-language-display-name-alist.
Конкретное синтаксическое дерево
Синтаксическое дерево — это результат работы парсера. В синтаксическом дереве каждый узел представляет фрагмент текста и связан с другими узлами отношением родитель—потомок. Например, если исходный текст имеет вид
1 + 2
его синтаксическое дерево может выглядеть так:
+--------------+
| root "1 + 2" |
+--------------+
|
+--------------------------------+
| expression "1 + 2" |
+--------------------------------+
| | |
+------------+ +--------------+ +------------+
| number "1" | | operator "+" | | number "2" |
+------------+ +--------------+ +------------+
Его также можно представить в виде s-выражения:
(root (expression (number) (operator) (number)))
Типы узлов
Имена, такие как root, expression, number и operator, указывают тип узлов. Однако не все узлы синтаксического дерева имеют тип. Узлы без типа называются анонимными узлами, а узлы с типом — именованными узлами. Анонимные узлы — это токены с фиксированным написанием, включая знаки пунктуации, например скобку «]», и ключевые слова, например return.
Имена полей
Чтобы упростить анализ синтаксического дерева, многие грамматики языков присваивают дочерним узлам имена полей. Например, узел function_definition может иметь declarator и body:
(function_definition declarator: (declaration) body: (compound_statement))
Исследование синтаксического дерева
Чтобы помочь разобраться в синтаксисе языка и отладить программы на Lisp, использующие синтаксическое дерево, Emacs предоставляет режим «исследования», который в реальном времени отображает синтаксическое дерево исходного текста в текущем буфере. В Emacs также есть режим «инспекции», который отображает сведения об узлах в точке в строке режима.
- Команда: treesit-explore
-
Эта команда открывает окно с синтаксическим деревом исходного текста в текущем буфере. Выделение текста в исходном буфере подсвечивает соответствующие узлы в отображении синтаксического дерева. Щелчок по узлам синтаксического дерева подсвечивает соответствующий текст в исходном буфере.
Чтобы переключиться на другой парсер, используйте
treesit-explorer-switch-parser.
- Команда: treesit-inspect-mode
-
Этот второстепенный режим отображает в строке режима узел, который начинается в точке. Например, строка режима может выглядеть так:
parent field: (node (child (...)))
где node, child и т. д. — узлы, начинающиеся в точке. parent — родительский узел для node. node отображается полужирным шрифтом. field-name — имена полей узла node, узла child и т. д.
Если в точке не начинается ни один узел, то есть точка находится внутри узла, строка режима отображает самый ранний узел, охватывающий точку, и его непосредственного родителя.
Этот второстепенный режим самостоятельно не создаёт парсеры. Он использует первый парсер в
(treesit-parser-list)(см. Использование парсера Tree-sitter).
Чтение определения грамматики
Авторы грамматик языков определяют грамматику языка программирования, которая задаёт, как парсер строит конкретное синтаксическое дерево из текста программы. Чтобы эффективно использовать синтаксическое дерево, необходимо обратиться к файлу грамматики.
Файл грамматики обычно называется grammar.js и находится в репозитории проекта грамматики языка. Ссылку на домашнюю страницу грамматики языка можно найти на домашней странице tree-sitter.
Определение грамматики записывается на JavaScript. Например, правило, соответствующее узлу function_definition, может выглядеть так:
function_definition: $ => seq(
$.declaration_specifiers,
field('declarator', $.declaration),
field('body', $.compound_statement)
)
Правила представлены функциями, принимающими один аргумент $, который представляет всю грамматику. Сама функция составляется из других функций: функция seq объединяет последовательность дочерних узлов; функция field присваивает дочернему узлу имя поля. Если записать приведённое выше определение в синтаксисе так называемой формы Бэкуса—Наура (BNF), оно будет выглядеть так:
function_definition := <declaration_specifiers> <declaration> <compound_statement>
а узел, возвращаемый парсером, будет выглядеть так:
(function_definition (declaration_specifier) declarator: (declaration) body: (compound_statement))
Ниже приведён список функций, которые можно встретить в определении грамматики. Каждая функция принимает другие правила в качестве аргументов и возвращает новое правило.
seq(rule1, rule2, …)сопоставляет каждое правило по очереди.
choice(rule1, rule2, …)сопоставляет одно из переданных ей правил.
repeat(rule)сопоставляет rule ноль или более раз. Это аналог оператора «*» в регулярных выражениях.
repeat1(rule)сопоставляет rule один или более раз. Это аналог оператора «+» в регулярных выражениях.
optional(rule)сопоставляет rule ноль или один раз. Это аналог оператора «?» в регулярных выражениях.
field(name, rule)присваивает имя поля name дочернему узлу, сопоставленному с помощью rule.
alias(rule, alias)-
заставляет узлы, сопоставленные с помощью rule, отображаться как alias в синтаксическом дереве, созданном парсером. Например,
alias(preprocessor_call_exp, call_expression)
заставляет любой узел, сопоставленный с
preprocessor_call_exp, отображаться какcall_expression.
Ниже приведены функции грамматики, которые менее важны при чтении грамматики языка.
token(rule)помечает rule так, чтобы оно создавало один листовой узел. То есть вместо родительского узла с отдельными дочерними узлами под ним всё объединяется в один листовой узел. См. Получение узлов.
token.immediate(rule)Обычно правила грамматики игнорируют предшествующие пробельные символы; эта функция изменяет rule так, чтобы оно сопоставлялось только при отсутствии предшествующих пробельных символов.
prec(n, rule)задаёт для rule приоритет уровня n.
prec.left([n,] rule)помечает rule как левоассоциативное, при необходимости задавая уровень n.
prec.right([n,] rule)помечает rule как правоассоциативное, при необходимости задавая уровень n.
prec.dynamic(n, rule)работает аналогично
prec, но приоритет применяется во время выполнения.
В документации проекта tree-sitter есть дополнительные сведения о написании грамматики. Особое внимание уделите разделу «The Grammar DSL».
Copyright © 1990-1996, 1998-2026 Free Software Foundation, Inc.
Licensed under the GNU GPL license.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Language-Grammar.html