プログラミング言語のソースの一部に他の言語のソースが含まれているときがあります。一例としては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-parser
、treesit-range-settings
、treesit-language-at-point-function
をセットする必要があるのです。
この関数は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
エラーをシグナルする。シグナルデータには特定のエラーメッセージ、セットを試みた範囲が含まれている。
この関数は範囲を無効にするためにも使うことができる。rangesがnil
の場合には、パーサーはバッファー全体をパースするようにセットされる。
例:
(treesit-parser-set-included-ranges parser '((1 . 9) (16 . 24) (24 . 25)))
この関数はparserにセットされている範囲をリターンする。リターン値はtreesit-parser-included-ranges
のranges引数と同じく(beg . end)
という形式のコンスセルのリスト。parserが範囲を何ももっていなければリターン値はnil
。
(treesit-parser-included-ranges parser) ⇒ ((1 . 9) (16 . 24) (24 . 25))
この関数はsourceをqueryでマッチングしてキャプチャーされたノードをリターンする。リターン値は(beg . end)
という形式のコンスセルのリスト。ここでbegとendはそれぞれテキスト範囲の開始と終了をする。
利便性のためにsourceは言語シンボル、パーサー、あるいはノードでもよい。この関数はそれが言語シンボルならその言語を使用する最初のパーサーのルートノード、パーサーならそのパーサーのルートノード、ノードならそのノードでマッチを行なう。
引数queryはノードのキャプチャーに用いるクエリー(tree-sitterノードにたいするパターンマッチングを参照)。引数begとendがどちらも非nil
なら、それはこの関数がクエリーを行なう範囲を制限する。
他のクエリー関数と同じように、この関数はqueryが不正であればtreesit-query-error
エラーをraiseする。
一般的なLispプログラムにおいて言語が複数ミックスされたプログラムをサポートするには、以下の2つの関数を呼び出すだけで十分です。
この関数はバッファーのパーサーの範囲を更新する。この関数はパーサーの範囲がbegとendの間に正しくセットされているかをtreesit-range-settings
に照らして確認する。省略された場合のデフォルトはbegがバッファー先頭、endがバッファー終端となる。
たとえばフォント表示(fontification)を行なう関数は、リージョン内のノードにクエリーを行う前にこの関数を使用する。
この関数はバッファー位置posにあるテキストの言語をリターンする。その背後ではtreesit-language-at-point-function
を呼び出して、そのリターンされた値をリターンしている。treesit-language-at-point-function
がnil
の場合には、この関数はtreesit-parser-list
のリターン値から最初のパーサーの言語をリターンする。バッファーにパーサーがなければnil
をリターンする。
ミックスされているかもしれない一連の言語では、ホスト言語(host language)と1つ以上の埋め込み言語(embedded languages)が存在することが珍しくありません。Lispプログラムはまずホスト言語のパーサーでドキュメント全体をパースすることで情報を得てから、それを用いて埋め込み言語の範囲をセット、その後に埋め込み言語をパースするのです。
HTML、CSS、それにJavaScriptを含むバッファーを例にとります。LispプログラムはまずHTMLパーサーでバッファー全体をパースして、それからパーサーにCSSとJavaScriptに相当するstyle_element
とscript_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)))))
この関数は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が関数の場合にはkeywordとvalueのペアーは必要ない。関数の場合にはstart、endという2つの引数を受け取り、カレントバッファーでstartとendの間にあるリージョンでパーサー用の範囲をセットすること。その関数がstartとendの間のリージョンを包むような広いリージョンに範囲をセットしても問題はない。
これはバッファーでtreesit-update-ranges
がパーサーにたいする範囲を更新する際の助けとなる変数である。settingのリストであり、その正確なフォーマットは内部的な使用を意図している。この変数が保持できる値を生成するにはtreesit-range-rules
を使うこと。
この変数の値はバッファー位置posを単一の引数として受け取り、posにあるテキストの言語をリターンする関数であること。この変数はtreesit-language-at
により使用される。
この関数はカレントバッファーのposにセットされたすべてのローカルパーサーをリターンする。posのデフォルトはポイント位置。
ローカルパーサーは非nil
のtreesit-parser
プロパティをもつオーバーレイによってマークされた、限定された範囲だけをパースする。languageが非nil
なら、その言語用のパーサーだけがリターンされる。
この関数はtreesit-local-parsers-at
と同様だが、ポイント位置ではなくbegとendの間の範囲にセットされたローカルパーサーをリターンする点が異なる。
begとendのデフォルトは、そのバッファーのアクセス可能範囲全体。