Next: ユーザー定義の“Things”とナビゲーション, Previous: ノード情報へのアクセス, Up: プログラムソースの解析 [Contents][Index]
tree-sitterでは小さな宣言型言語を用いてLispプログラムによるパターンのマッチングができます。このパターンマッチングは2つのステップから構成されています。まずtree-sitterが構文ツリーのノードにたいするパターン(pattern)のマッチを行い、その後にパターンにマッチした特定のノードをキャプチャー(capture)してそのノードをリターンするのです。
まずはもっとも基本的なクエリーパターンを記述してパターン内のノードをキャプチャーする方法、それからパターンマッチング関数、そして最後により上級のパターン構文について説明していきます。
クエリー(query:
問い合わせ)は複数のパターン(patterns)によって構成されます。パターンはそれぞれ構文ノード内の特定ノードにマッチするS式です。パターンは(type (child…))
という形式をもっています。
たとえば子ノードを含んだノードbinary_expression
にマッチするのは以下のようなパターンでしょう
(binary_expression (number_literal))
上記のクエリーパターンを用いてノードをキャプチャー(capture)するためには、キャプチャーしたいノードパターンの後に@capture-name
を追加します。たとえば、
(binary_expression (number_literal) @number-in-exp)
これはノードbinary_expression
にあるノードnumber_literal
をキャプチャー名でキャプチャーします。
同じようにして、たとえばキャプチャー名biexp
でノードbinary_expression
をキャプチャーできます:
(binary_expression (number_literal) @number-in-exp) @biexp
これでクエリー関数(query functions)を説明する準備ができました。
この関数はqueryのパターンをnodeとマッチする。引数queryはS式、文字列、またはコンパイル済みクエリーオブジェクトのいずれか。ここではS式構文に焦点を当てる。文字列構文およびコンパイル済みクエリーについては、このセクションの最後で説明しよう。
引数nodeはパーサー、あるいは言語シンボルでもよい。パーサーの場合にはそのルートノードを、言語シンボルならまずカレントバッファーでその言語用のパーサーを探すか作成してそのルートノードを使用する。
この関数はキャプチャーしたすべてのノードを、(capture_name . node)
という要素をもつalistでリターンする。node-onlyが非nil
の場合には、かわりにnodeのリストをリターンする。デフォルトではnodeのテキスト全体を検索するが、begとendがいずれも非nil
であれば、それはこの関数がノードをマッチするべきバッファーテキストのリージョンを指定する。begとendの間のリージョンを跨いで重なるようなノードがマッチすればすべてキャプチャーされる(完全にそのリージョンに含まれている必要はない)。
この関数はqueryが不正な形式であればtreesit-query-error
エラーraiseする。シグナルデータには、その特定。エラーに関する説明が含まれている。クエリーの検証とデバッグにはtreesit-query-validate
を使うことができる。
たとえばnodeのテキストが1 + 2
で、以下のクエリーを考えてみましょう
(setq query '((binary_expression (number_literal) @number-in-exp) @biexp)
このクエリーをマッチングすると以下がリターンされます
(treesit-query-capture node query) ⇒ ((biexp . <node for "1 + 2">) (number-in-exp . <node for "1">) (number-in-exp . <node for "2">))
前に言及したように、queryに複数のパターンを含めることができます。たとえば以下のようにトップレベルのパターンを2つもつことができます:
(setq query '((binary_expression) @biexp (number_literal) @number @biexp)
この関数はlanguageとしてstringをパースして、そのルートノードにたいしてqueryをマッチ、その結果をリターンする。
ノードのタイプとキャプチャー名以外にも、tree-sitterのパターン構文により無名ノード、フィールド名、ワイルドカード、量化、グルーピング、選択、アンカー、述語を表現することができます。
無名ノードはクォートで括ることで逐語的に記述されます。キーワードreturn
にマッチ(してキャプチャー)するパターンは以下のようになるでしょう
"return" @keyword
パターンにおいて‘(_)’は任意の名前つきノード、‘_’は名前つきノードや無名ノードのすべてにマッチします。たとえばbinary_expression
ノードの名前つきの子をすべてキャプチャーするパターンは以下のようになるでしょう
(binary_expression (_) @in-biexp)
特定のフィールド名をもつ子ノードをキャプチャーすることは可能です。以下のパターンでは、フィールド名であることを示すコロンが後置されているdeclarator
とbody
がフィールド名です。
(function_definition declarator: (_) @func-declarator body: (_) @func-body)
特定のフィールドをもたないノード、たとえばbody
フィールドのないfunction_definition
をキャプチャーすることも可能です:
(function_definition !body) @func-no-body
tree-sitterは量化演算子(quantification operator)の‘:*’、‘:+’,‘:?’を認識します。これらの演算子の意味は正規表現の場合と同じです。‘:*’と‘:+’はそれぞれ前にあるパターンの0個以またはは1個以上の繰り返し、‘:?’は前のパターンの0個または1個の繰り返しにマッチします。
たとえば以下はlong
というキーワードの0個以上の繰り返しにマッチするパターンです。
(type_declaration "long" :*) @long-type
こちらはlong
というキーワードをもつか、あるいはもたないかというタイプ宣言用のパターンです:
(type_declaration "long" :?) @long-type
正規表現におけるグループと同じように、パターンをグループにまとめたり量化演算子を適用することができます。たとえばカンマで区切られた識別子を表現するには、以下のように記述できるでしょう
(identifier) ("," (identifier)) :*
繰り返しになりますが正規表現と同じように、パターンにおいて“これらのパターンのいずれかにマッチ”と表現することができます。この構文はパターンのベクターです。たとえばCのいくつかのキーワードをキャプチャーするパターンは、以下のように記述できるでしょう
[ "return" "break" "if" "else" ] @keyword
たとえば2つのオブジェクトを隣接させるといったように、アンカー演算子:anchor
を用いて並置させることができます。2つの“オブジェクト”には2つのノードや子ノードと最後の親などを指定できます。たとえば最初最後の子、あるいは2つの隣接した子をキャプチャーするには:
;; 子と最後で親をアンカー (compound_expression (_) @last-child :anchor)
;; 子と最初の親をアンカー (compound_expression :anchor (_) @first-child)
;; 隣接する2つの子をアンカー (compound_expression (_) @prev-child :anchor (_) @next-child)
並置させる際には無名ノードは無視されることに注意してください。
パターンに述語による制約を追加することができます。たとえば以下のパターンでは:
( (array :anchor (_) @first (_) @last :anchor) (:equal @first @last) )
tree-sitterは配列の最初と最後の要素が等しい場合だけマッチします。パターンに述語を付加するためには、それらをグループにまとめる必要があります。現在のところ:equal
、:match
、:pred
という3つの述語があります。
arg1とarg2が等しければマッチ。引数は文字列またはキャプチャー名のいずれか。キャプチャー名とは、バッファーにおいてキャプチャーされたノードが跨ぐテキストを表す。
バッファーにおいてcapture-nameのノードが跨ぐテキストと、文字列リテラルとして与えられた正規表現regexpがマッチすればマッチ。マッチは大文字小文字を区別する。
関数fnにnodes内のノードそれぞれを渡して非nil
がリターンされたらマッチ。この関数はノードの問い合わせが行われたバッファーをカレントバッファーにセットして実行される。
述語が参照できるのは、同じパターン内に出現するキャプチャー名だけであることに注意してください。実際問題として別のパターンからキャプチャー名を参照しても、意味はほとんどありません。
S式の他にも、Emacsでは使用するtree-sitterのネイティブクエリー構文を文字列として記述することができます。これはS式の構文とよく似ています。たとえば以下のクエリーは
(treesit-query-capture node '((addition_expression left: (_) @left "+" @plus-sign right: (_) @right) @addition ["return" "break"] @keyword))
以下と等価です
(treesit-query-capture node "(addition_expression left: (_) @left \"+\" @plus-sign right: (_) @right) @addition [\"return\" \"break\"] @keyword")
ほとんどのパターンは文字列内部のS式として直接記述できます。変更が必要になるのはごく少数のパターンだけです:
:anchor
は‘.’と記述。
:equal
、:match
、:pred
はそれぞれ#equal
、#match
、#pred
と記述。一般的には述語の‘:’を‘#’に変更する。
たとえば、
'(( (compound_expression :anchor (_) @first (_) :* @rest) (:match "love" @first) ))
文字列形式では以下のように記述します
"( (compound_expression . (_) @first (_)* @rest) (#match \"love\" @first) )"
繰り返し使うことを意図したクエリー、とりわけタイトなループ(訳注 :少ない命令を含み多数回実行されるループ)では、クエリーのコンパイルが重要になります。なぜならコンパイル済みのクエリーは、コンパイルされていないものと比較して非常に高速だからです。クエリーの使用が許されている場所ならどこでもコンパイル済みクエリーを使うことができます。
この関数はlanguageのqueryをコンパイル済みクエリーオブジェクトにコンパイルして、それをリターンする。
この関数はqueryが不正な形式であればtreesit-query-error
エラーraiseする。シグナルデータには、その特定。エラーに関する説明が含まれている。クエリーの検証とデバッグにはtreesit-query-validate
を使うことができる。
この関数はqueryの言語をリターンする。
この関数はS式のqueryを文字列フォーマットに変換する。
この関数はS式のpatternを文字列フォーマットに変換する。
パターンマッチングに関する詳細については、https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queriesにあるtree-sitterプロジェクトのドキュメントを参照してください。
Next: ユーザー定義の“Things”とナビゲーション, Previous: ノード情報へのアクセス, Up: プログラムソースの解析 [Contents][Index]