Spec-Zone.ru › Elisp

Грамматика языка 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API