38.7 複数言語のパース

プログラミング言語のソースの一部に他の言語のソースが含まれているときがあります。一例としてはHTML + CSS + JavaScriptが挙げられます。このような場合には、別の言語によって記述されたテキストセグメントには別のパーサーを割り当てる必要があります。伝統的にこれはナローイングの使用によって達成されてきました。tree-sitterはナローイング(narrowingを参照)とともに機能しますが、推奨される方法はバッファーテキストのリージョン(範囲)にそれを操作するパーサーを指定する方法です。このセクションではパーサーにたいして範囲のセットや取得を行う関数について説明します。

一般的に同時に複数の言語が存在する場合には、“プライマリー(primary: 主要、メイン)”の言語、あるいは“ホスト(host: 宿主)”の言語が存在します。この言語にたいするパーサー、すなわちプライマリーパーサー(primary parser)がバッファー全体をパースします。これ以外の言語にたいするパーサーである“埋め込みパーサー(embedded parser)”、あるいは“ゲストパーサー(guest parser)”はバッファーの限定された範囲だけに機能します。埋め込みパーサーの動作範囲を決定するために使用されるのは、通常はプライマリーパーサーのパースツリー(解析木)です。

Emacsがフォントロックや他の機能にたいして適正にプライマリーパーサーを構成できるように、メジャーモードはtreesit-major-mode-setup呼び出しに先立ち、treesit-primary-parserにプライマリーパーサーをセットする必要があります。

Lispプログラムがバッファーでパーサーを使う前には、treesit-update-rangesの呼び出しによってパーサーそれぞれにたいする範囲が正しいか確認して、その位置にあるテキストにたいして任を負うパーサーを解決する必要があります。この2つの関数自身は作業を行いません。実際に作業を行うためにはメジャーモードがtreesit-language-at-point-functionおよびtreesit-language-at-point-functionをセットする必要があります。これらの関数および変数については、このセクションの終わり近くで詳細に説明しましょう。

簡単に言うと、複数言語向けメジャーモードはtreesit-major-mode-setupを呼び出す前に、treesit-primary-parsertreesit-range-settingstreesit-language-at-point-functionをセットする必要があるのです。

範囲の取得とセット

Function: treesit-parser-set-included-ranges parser ranges

この関数はrangesにたいして処理を行なうためにparserをセットアップする。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エラーをシグナルする。シグナルデータには特定のエラーメッセージ、セットを試みた範囲が含まれている。

この関数は範囲を無効にするためにも使うことができる。rangesnilの場合には、パーサーはバッファー全体をパースするようにセットされる。

例:

(treesit-parser-set-included-ranges
 parser '((1 . 9) (16 . 24) (24 . 25)))
Function: treesit-parser-included-ranges parser

この関数はparserにセットされている範囲をリターンする。リターン値はtreesit-parser-included-rangesranges引数と同じく(beg . end)という形式のコンスセルのリスト。parserが範囲を何ももっていなければリターン値はnil

(treesit-parser-included-ranges parser)
    ⇒ ((1 . 9) (16 . 24) (24 . 25))
Function: treesit-query-range source query &optional beg end

この関数はsourcequeryでマッチングしてキャプチャーされたノードをリターンする。リターン値は(beg . end)という形式のコンスセルのリスト。ここでbegendはそれぞれテキスト範囲の開始と終了をする。

利便性のためにsourceは言語シンボル、パーサー、あるいはノードでもよい。この関数はそれが言語シンボルならその言語を使用する最初のパーサーのルートノード、パーサーならそのパーサーのルートノード、ノードならそのノードでマッチを行なう。

引数queryはノードのキャプチャーに用いるクエリー(tree-sitterノードにたいするパターンマッチングを参照)。引数begendがどちらも非nilなら、それはこの関数がクエリーを行なう範囲を制限する。

他のクエリー関数と同じように、この関数はqueryが不正であればtreesit-query-errorエラーをraiseする。

Lispプログラムで複数言語をサポートするには

一般的なLispプログラムにおいて言語が複数ミックスされたプログラムをサポートするには、以下の2つの関数を呼び出すだけで十分です。

Function: treesit-update-ranges &optional beg end

この関数はバッファーのパーサーの範囲を更新する。この関数はパーサーの範囲がbegendの間に正しくセットされているかをtreesit-range-settingsに照らして確認する。省略された場合のデフォルトはbegがバッファー先頭、endがバッファー終端となる。

たとえばフォント表示(fontification)を行なう関数は、リージョン内のノードにクエリーを行う前にこの関数を使用する。

Function: treesit-language-at pos

この関数はバッファー位置posにあるテキストの言語をリターンする。その背後ではtreesit-language-at-point-functionを呼び出して、そのリターンされた値をリターンしている。treesit-language-at-point-functionnilの場合には、この関数はtreesit-parser-listのリターン値から最初のパーサーの言語をリターンする。バッファーにパーサーがなければnilをリターンする。

メジャーモードで複数の言語をサポートするには

ミックスされているかもしれない一連の言語では、ホスト言語(host language)と1つ以上の埋め込み言語(embedded languages)が存在することが珍しくありません。Lispプログラムはまずホスト言語のパーサーでドキュメント全体をパースすることで情報を得てから、それを用いて埋め込み言語の範囲をセット、その後に埋め込み言語をパースするのです。

HTMLCSS、それにJavaScriptを含むバッファーを例にとります。LispプログラムはまずHTMLパーサーでバッファー全体をパースして、それからパーサーにCSSとJavaScriptに相当するstyle_elementscript_elementのノードをクエリーするのです。その後に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)

treesit-update-rangesによってEmacsがこのプロセスを自動化します。treesit-update-rangesがプロセスを自動化する方法を解決するためには、複数言語のメジャーモードがtreesit-range-settingsをセットする必要があります。treesit-range-settingsに割り当てられる値を生成するためには、メジャーモードがヘルパー関数treesit-range-rulesを使う必要があります。この操作を直接コード化したのが以下のセッティング例になります。

(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)))))
Function: treesit-range-rules &rest query-specs

この関数はtreesit-range-settingsをセットするために用いる。クエリーのコンパイルやその他の後処理に注意を払い、treesit-range-settingsにセットできるような値を出力する。

この関数は引数として一連のquery-specを受け取る。ここでquery-specとは0個以上のkeyword/valueペアーが前置されたqueryのこと。queryはそれぞれ文字列、S式、コンパイル済みフォーム、あるいは関数のいずれかによるtree-sitterクエリーである。

queryがtree-sitterクエリーなら:keyword/valueのペアーを2つを前置すること(:keyword:embedは埋め込み言語、:hostはホスト言語)。

値がtであるような:localキーワードを与えられたクエリーでは、クエリーによってセットされた範囲に専用のローカルパーサーが適用され、それ以外の場合には範囲に同じ言語にたいする他の範囲のパーサーが共有される。

デフォルトではパーサーは範囲を個別の独立したセグメントではなく、連続的なものとして認識する。したがって埋め込まれた範囲が意味論的に独立したセグメントの場合には、以降で説明するローカルパーサーによって処理する必要がある。

範囲にセットされたローカルパーサーはtreesit-local-parsers-atおよびtreesit-local-parsers-onで取得できる。

treesit-update-rangesは埋め込み言語用のパーサーにたいして範囲をセットする方法の解決にqueryを使用する。ホスト言語パーサーにqueryを問い合わせてキャプチャーされたノードが跨ぐ範囲を計算、それらの範囲を埋め込み言語パーサーに適用するのである。

queryが関数の場合にはkeywordvalueのペアーは必要ない。関数の場合にはstartendという2つの引数を受け取り、カレントバッファーでstartendの間にあるリージョンでパーサー用の範囲をセットすること。その関数がstartendの間のリージョンを包むような広いリージョンに範囲をセットしても問題はない。

Variable: treesit-range-settings

これはバッファーでtreesit-update-rangesがパーサーにたいする範囲を更新する際の助けとなる変数である。settingのリストであり、その正確なフォーマットは内部的な使用を意図している。この変数が保持できる値を生成するにはtreesit-range-rulesを使うこと。

Variable: treesit-language-at-point-function

この変数の値はバッファー位置posを単一の引数として受け取り、posにあるテキストの言語をリターンする関数であること。この変数はtreesit-language-atにより使用される。

Function: treesit-local-parsers-at &optional pos language

この関数はカレントバッファーのposにセットされたすべてのローカルパーサーをリターンする。posのデフォルトはポイント位置。

ローカルパーサーは非niltreesit-parserプロパティをもつオーバーレイによってマークされた、限定された範囲だけをパースする。languageが非nilなら、その言語用のパーサーだけがリターンされる。

Function: treesit-local-parsers-on &optional beg end language

この関数はtreesit-local-parsers-atと同様だが、ポイント位置ではなくbegendの間の範囲にセットされたローカルパーサーをリターンする点が異なる。

begendのデフォルトは、そのバッファーのアクセス可能範囲全体。


This page has generated for branch:work/emacs-30_69b16e5c63840479270d32f58daea923fe725b90, commit:5e3f74b56ff47b5bcef2526c70f53f749bbd45f6 to check Japanese translation.