38.7 Разбор текста на нескольких языках

Иногда исходный код языка программирования может содержать фрагменты других языков; 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: список ячеек cons вида (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 и возвращает диапазоны захваченных узлов. Возвращаемое значение — список ячеек cons вида (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, эта функция возвращает язык первого анализатора из возвращаемого значения treesit-parser-list. Если в буфере нет анализатора, возвращается 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:

;; Создание анализаторов.
(setq html (treesit-parser-create 'html))
(setq css (treesit-parser-create 'css))
(setq js (treesit-parser-create 'javascript))

;; Установка диапазонов CSS.
(setq css-range
      (treesit-query-range
       'html
       '((style_element (raw_text) @capture))))
(treesit-parser-set-included-ranges css css-range)

;; Установка диапазонов JavaScript.
(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))))

;; Основные режимы с несколькими языками должны всегда устанавливать
;; `treesit-language-at-point-function' (см. описание).
(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-выражении или скомпилированном виде, либо функция.

Если query — это запрос tree-sitter, перед ним должны стоять две пары keyword/value, где ключевое слово :embed указывает встроенный язык, а ключевое слово :host — главный язык.

Если запросу задано ключевое слово :local со значением t, диапазон, установленный этим запросом, имеет выделенный локальный анализатор; в противном случае диапазон использует общий анализатор с другими диапазонами для того же языка.

По умолчанию анализатор рассматривает свои диапазоны как континуум, а не как отдельные независимые сегменты. Поэтому, если встроенные диапазоны семантически независимы, они должны обрабатываться локальными анализаторами, описанными ниже.

Локальный анализатор, установленный для диапазона, можно получить с помощью treesit-local-parsers-at и treesit-local-parsers-on.

treesit-update-ranges использует query для определения способа установки диапазонов для анализаторов встроенного языка. Он выполняет запрос query в анализаторе главного языка, вычисляет диапазоны, охватываемые захваченными узлами, и применяет эти диапазоны к анализаторам встроенного языка.

Если 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-local-parsers-at &optional pos language

Эта функция возвращает все локальные анализаторы в позиции pos в текущем буфере. pos по умолчанию равен точке.

Локальные анализаторы — это те, которые анализируют только ограниченную область, отмеченную оверлеем с ненулевым свойством treesit-parser. Если language не равен nil, возвращаются только анализаторы для этого языка.

Функция: treesit-local-parsers-on &optional beg end language

Эта функция аналогична treesit-local-parsers-at, но возвращает локальные анализаторы в диапазоне между beg и end, а не в точке.

По умолчанию beg и end равны всей доступной части буфера.