Разбор текста на нескольких языках
Иногда исходный код программы может содержать фрагменты на других языках; HTML + CSS + JavaScript — один из примеров. В этом случае сегментам текста, написанным на разных языках, нужно назначить разные парсеры. Традиционно этого добиваются с помощью сужения. Хотя tree-sitter работает с сужением (см. сужение), рекомендуется вместо этого указывать области текста в буфере (то есть диапазоны), в которых будет работать парсер. В этом разделе описаны функции задания и получения диапазонов для парсера.
Как правило, при наличии нескольких языков один из них является «основным», или «хост-языком». Парсер этого языка — основной парсер — разбирает весь буфер. Парсеры для других языков являются «встроенными» или «гостевыми» парсерами и работают лишь с частью буфера. Дерево разбора основного парсера обычно используется для определения диапазонов, в которых работают встроенные парсеры.
Основные режимы должны назначать treesit-primary-parser основному парсеру перед вызовом treesit-major-mode-setup, чтобы Emacs мог правильно настроить основной парсер для font-lock и других возможностей.
Программы на Lisp должны вызывать treesit-update-ranges, чтобы убедиться в корректности диапазонов для каждого парсера перед использованием парсеров в буфере, и вызывать treesit-language-at, чтобы определить, какой язык отвечает за текст в заданной позиции. Эти две функции не работают сами по себе; для их работы основной режим должен задавать treesit-range-settings и, при необходимости, treesit-language-at-point-function, которые и выполняют фактическую работу. Эти функции и переменные подробнее описаны в конце раздела.
Иными словами, основные режимы для нескольких языков должны задавать treesit-primary-parser, treesit-range-settings и, при необходимости, treesit-language-at-point-function перед вызовом treesit-major-mode-setup.
Получение и задание диапазонов
- Функция: treesit-parser-set-included-ranges parser ranges
-
Эта функция настраивает parser для работы с диапазонами ranges. parser будет читать текст только из указанных диапазонов. Каждый диапазон в ranges — это пара вида
(beg . end).Диапазоны в ranges должны быть упорядочены и не должны перекрываться. Иными словами, в псевдокоде:
(cl-loop for idx from 1 to (1- (length ranges)) for prev = (nth (1- idx) ranges) for next = (nth idx ranges) should (<= (car prev) (cdr prev) (car next) (cdr next)))Если ranges нарушает это ограничение или произошла другая ошибка, функция сигнализирует об ошибке
treesit-range-invalid. Данные сигнала содержат конкретное сообщение об ошибке и диапазоны, которые мы пытались задать.Эту функцию также можно использовать для отключения диапазонов. Если ranges равно
nil, парсеру задаётся разбор всего буфера.Пример:
(treesit-parser-set-included-ranges parser '((1 . 9) (16 . 24) (24 . 25)))
- Функция: treesit-parser-included-ranges parser
-
Эта функция возвращает диапазоны, заданные для parser. Возвращаемое значение совпадает с аргументом ranges функции
treesit-parser-included-ranges: это список конс-ячеек вида(beg . end). Если для parser не заданы диапазоны, возвращаемое значение —nil.(treesit-parser-included-ranges parser) ⇒ ((1 . 9) (16 . 24) (24 . 25))
- Функция: treesit-query-range source query &optional beg end
-
Эта функция сопоставляет source с query и возвращает диапазоны захваченных узлов. Возвращаемое значение — список конс-ячеек вида
(beg . end), где beg и end задают начало и конец области текста.Для удобства source может быть символом языка, парсером или узлом. Если это символ языка, функция выполняет сопоставление с корневым узлом первого парсера, использующего этот язык; если это парсер — с его корневым узлом; если это узел — с этим узлом.
Аргумент query — запрос, используемый для захвата узлов (см. Сопоставление с образцом узлов Tree-sitter). Имена захватов не имеют значения. Аргументы beg и end, если оба не равны
nil, ограничивают область, в которой эта функция выполняет запрос.Как и другие функции запросов, эта функция вызывает ошибку
treesit-query-error, если запрос query имеет неправильный формат.
Поддержка нескольких языков в программах на Lisp
Для поддержки исходного кода программ, сочетающего несколько языков, программам на Lisp общего назначения обычно достаточно вызывать следующие две функции.
- Функция: treesit-update-ranges &optional beg end
-
Эта функция обновляет диапазоны парсеров в буфере. Она проверяет, что диапазоны парсеров правильно заданы между beg и end в соответствии с
treesit-range-settings. Если аргументы не указаны, beg по умолчанию равен началу буфера, а end — его концу.Например, функции раскрашивания текста вызывают эту функцию перед выполнением запроса узлов в области.
- Функция: treesit-language-at pos
Эта функция возвращает язык текста в позиции буфера pos. Внутри она вызывает
treesit-language-at-point-functionи возвращает его результат. Еслиtreesit-language-at-point-functionравноnil, эта функция возвращает язык парсера с наибольшим уровнем вложенности (см. parser-embed-level) среди парсеров, возвращённых функциейtreesit-parsers-at. Если в этой позиции буфера нет парсера, функция возвращаетnil.
Поддержка нескольких языков в основных режимах
Обычно в наборе языков, которые можно сочетать, есть хост-язык и один или несколько встроенных языков. Программа на Lisp обычно сначала разбирает весь документ парсером хост-языка, извлекает некоторую информацию, задаёт диапазоны для встроенных языков на основе этой информации, а затем разбирает встроенные языки.
Рассмотрим в качестве примера буфер с HTML, CSS и JavaScript. Программа на Lisp сначала разберёт весь буфер парсером HTML, а затем выполнит запрос к парсеру для поиска узлов style_element и script_element, соответствующих тексту CSS и JavaScript. Затем она задаст диапазоны парсеров CSS и JavaScript так, чтобы они охватывали соответствующие узлы.
Рассмотрим простой документ HTML:
<html>
<script>1 + 2</script>
<style>body { color: "blue"; }</style>
</html>
программа на Lisp сначала выполнит разбор парсером HTML, а затем задаст диапазоны для парсеров CSS и JavaScript:
;; Create parsers. (setq html (treesit-parser-create 'html)) (setq css (treesit-parser-create 'css)) (setq js (treesit-parser-create 'javascript))
;; Set CSS ranges.
(setq css-range
(treesit-query-range
'html
'((style_element (raw_text) @capture))))
(treesit-parser-set-included-ranges css css-range)
;; Set JavaScript ranges.
(setq js-range
(treesit-query-range
'html
'((script_element (raw_text) @capture))))
(treesit-parser-set-included-ranges js js-range)
Emacs автоматизирует этот процесс в treesit-update-ranges. Основной режим для нескольких языков должен задавать treesit-range-settings, чтобы treesit-update-ranges знал, как автоматически выполнять этот процесс. Основным режимам следует использовать вспомогательную функцию treesit-range-rules для создания значения, которое можно присвоить treesit-range-settings. Настройки в следующем примере напрямую соответствуют операциям, показанным выше.
(setq treesit-range-settings
(treesit-range-rules
:embed 'javascript
:host 'html
'((script_element (raw_text) @capture))
:embed 'css
:host 'html
'((style_element (raw_text) @capture))))
;; Major modes with multiple languages can optionally set
;; `treesit-language-at-point-function' (which see).
(setq treesit-language-at-point-function
(lambda (pos)
(let* ((node (treesit-node-at pos 'html))
(parent (treesit-node-parent node)))
(cond
((and node parent
(equal (treesit-node-type node) "raw_text")
(equal (treesit-node-type parent) "script_element"))
'javascript)
((and node parent
(equal (treesit-node-type node) "raw_text")
(equal (treesit-node-type parent) "style_element"))
'css)
(t 'html)))))
- Функция: treesit-range-rules &rest query-specs
-
Эта функция используется для задания
treesit-range-settings. Она компилирует запросы и выполняет другую постобработку, а затем возвращает значение, которое может иметьtreesit-range-settings.Она принимает последовательность query-spec, где каждый query-spec — это query, которому предшествуют ноль или более пар keyword/value. Каждый query — это запрос tree-sitter в виде строки, s-выражения, скомпилированного запроса или функции.
treesit-update-rangesиспользует query, чтобы определить, как задать диапазоны для парсеров встроенного языка. Функция выполняет запрос query к парсеру хост-языка, вычисляет диапазоны, охватывающие захваченные узлы, и применяет эти диапазоны к парсерам встроенного языка.Если query — запрос tree-sitter, перед ним должны идти две пары keyword/value: ключевое слово
:embedзадаёт встроенный язык, а ключевое слово:host— хост-язык. Захваченные узлы определяют диапазон встроенных парсеров. Имена захватов не имеют значения, но имена, начинающиеся с символа подчёркивания, игнорируются, а имя захвата@languageзарезервировано.Если встроенный язык динамический,
:embedможет задавать функцию вместо статического языка. Функция принимает узел, захваченный с помощью имени захвата@language. Необходимо следить за тем, чтобы захват@languageи захват диапазона находились в одной группе сопоставления. Функция должна возвращать символ языка или nil, если подходящий язык не найден.Если для запроса задано ключевое слово
:offsetсо значением — парой чисел, каждый захваченный диапазон смещается на эти числа. Например, смещение(1 . -1)изменит захваченный диапазон(2 . 8)на(3 . 7). Это можно использовать, чтобы исключить из диапазона встроенного парсера такие элементы, как окружающие разделители.По умолчанию парсер воспринимает свои диапазоны как непрерывную область, а не как отдельные независимые сегменты. Поэтому, если встроенные диапазоны представляют семантически независимые сегменты, их следует обрабатывать локальными парсерами, описанными ниже.
Если для запроса задано ключевое слово
:localсо значениемt, для диапазона, заданного этим запросом, создаётся отдельный локальный парсер; в противном случае диапазон использует парсер совместно с другими диапазонами того же языка.Локальные парсеры, назначенные диапазону, можно получить с помощью
treesit-local-parsers-atиtreesit-local-parsers-on.Emacs совершенно по-разному работает с локальными и нелокальными встроенными парсерами:
- Локальными парсерами управляет Emacs. Emacs создаёт локальные парсеры при необходимости и удаляет их, когда они больше не нужны. Нелокальные парсеры должны создаваться основными режимами, а Emacs использует их, только если они существуют.
- Поскольку Emacs может свободно создавать локальные парсеры, они подходят для рекурсивно вложенных встроенных парсеров, уровень вложенности которых может быть произвольно большим. Нелокальные парсеры, создаваемые основными режимами, напротив, могут обрабатывать только фиксированное число уровней вложенности.
У парсеров верхнего уровня уровень вложенности равен 0 или не задан; у встроенных парсеров первого уровня он равен 1 и так далее. Для локальных парсеров на каждом уровне вложенности создаётся новый парсер; для нелокальных парсеров, поскольку Emacs не может свободно создавать их, он пытается найти подходящий парсер на нужном уровне вложенности. Если такого парсера нет, Emacs просто прекращает попытки.
Если для запроса задано ключевое слово
:range-fnсо значением-функцией, Emacs использует эту функцию для вычисления диапазонов встроенного парсера. Функция принимает в качестве аргументов захваченный узел и смещение, заданное ключевым словом:offset, и возвращает список диапазонов, каждый из которых представляет собой cons начальной и конечной позиций. Для локальных парсеров каждый query создаёт один локальный парсер, и диапазоны, заданные с помощью:range-fn, применяются к этому парсеру; для нелокальных парсеров все диапазоны на этом уровне вложенности (включая диапазоны, созданные с помощью:range-fn) объединяются и применяются к одному парсеру.Если query — функция, ей не нужны пары keyword и value. Это должна быть функция, принимающая 2 аргумента — start и end — и задающая диапазоны парсеров в текущем буфере в области между start и end. Эта функция может задавать диапазоны и в более широкой области, охватывающей область между start и end.
- Переменная: treesit-range-settings
-
Эта переменная помогает
treesit-update-rangesобновлять диапазоны парсеров в буфере. Это список элементов setting, точный формат которых считается внутренней деталью реализации. Для создания значения, допустимого для этой переменной, следует использоватьtreesit-range-rules.
- Переменная: treesit-language-at-point-function
Значением этой переменной должна быть функция, принимающая один аргумент — pos, позицию в буфере, — и возвращающая язык текста буфера в позиции pos. Эта переменная используется
treesit-language-at.
- Функция: treesit-parsers-at &optional pos language with-host only
-
Эта функция возвращает все парсеры в позиции pos текущего буфера. По умолчанию pos равна точке. Возвращаемые парсеры отсортированы по убыванию уровня вложенности.
Если language не равно
nil, возвращаются только парсеры для этого языка.Если with-host не равно
nil, возвращается список(parser . host-parser), в котором host-parser — это родительский парсер, создавший parser.Если only не равно
nil, возвращаются все парсеры, включая основной парсер.Аргумент only может быть списком символов, указывающих, какие парсеры следует включить в возвращаемое значение.
Если only содержит символ
local, включаются локальные парсеры. Локальные парсеры анализируют только ограниченную область, отмеченную оверлеем с ненулевым свойствомniltreesit-parser-local-p.Если only содержит символ
global, включаются нелокальные парсеры, за исключением основного парсера.Если only содержит символ ‘primary’, включается основной парсер.
- Функция: treesit-local-parsers-at &optional pos language with-host
-
Эта функция возвращает все локальные парсеры в позиции pos текущего буфера. По умолчанию pos равна точке.
Локальные парсеры анализируют только ограниченную область, отмеченную оверлеем с ненулевым свойством
niltreesit-parser-local-p. Если language не равноnil, возвращаются только парсеры для этого языка.
- Функция: treesit-local-parsers-on &optional beg end language with-host
-
Эта функция аналогична
treesit-local-parsers-at, но возвращает локальные парсеры в диапазоне между beg и end, а не в точке.По умолчанию beg и end обозначают всю доступную часть буфера.
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/Multiple-Languages.html