Next: , Up: プログラムソースの解析   [Contents][Index]


37.1 tree-sitterの言語グラマー

言語グラマーのロード

ある言語で記述されたテキストをパースするために、tree-sitterはその言語のグラマー(grammar: 文法)に依存します。Emacsにおける言語グラマーはシンボルによって表現されます。たとえばC言語のグラマーはシンボルcとして表現されます。このcというシンボルはtree-sitter関数のlanguage引数として渡すことができます。

tree-sitterの言語グラマーはダイナミックライブラリーとして配布されています。ある言語のグラマーをEmacsで使用するためには、そのダイナミックライブラリーがシステム上にインストール済みかを確認する必要があります。Emacsは以下の順序で複数の場所から言語グラマーを探します:

これらのディレクトリーそれぞれにおいて、Emacsは変数dynamic-library-suffixesが指定するファイル名拡張子をもつファイルを探すのです。

Emacsがライブラリーを見つけられなかったりロードに問題がある場合には、Emacsがtreesit-load-language-errorエラーをシグナルします。このシグナルのデータは以下のいずれかです:

(not-found error-msg …)

その言語のグラマーライブラリーをEmacsが見つけられなかったという意味。

(symbol-error error-msg)

すべての言語のグラマーライブラリーでエクスポートされているべき関数を、そのライブラリーではEmacsが見つけられなかったという意味。

(version-mismatch error-msg)

その言語のグラマーライブラリーとtree-sitterライブラリーのバージョンに互換性がないという意味。

上記すべてのケースにおいて、error-msgにより失敗に関する追加の詳細が提供されるかもしれません。

Function: treesit-language-available-p language &optional detail

この関数はlanguageにたいする言語グラマーが存在して、それがロード可能であれば非nilをリターンする。

detailが非nilの場合には、languageが利用可能なら(t . nil)、利用不可なら(nil . data)をリターンする。datatreesit-load-language-errorのシグナルデータ。

慣例によりlanguage用ダイナミックライブラリーのファイル名はlibtree-sitter-language.extです。ここでextはダイナミックライブラリー用のシステム固有な拡張子です。同じく慣例により、そのライブラリーが提供する関数の名前はtree_sitter_languageです。この慣例にしたがっていない言語グラマーライブラリーの場合には、

(language library-base-name function-name)

上記エントリーを変数treesit-load-name-override-listのリストに追加する必要があります。ここでlibrary-base-nameはダイナミックライブラリーのファイル名のベースネーム(basename: 先行するディレクトリー部分を除外したファイル名のことで、通常だとlibtree-sitter-language)、function-nameはそのライブラリーが提供する関数(通常だとtree_sitter_language)です。たとえば、

(cool-lang "libtree-sitter-coool" "tree_sitter_cooool")

これは慣例に屈するには自分があまりにも“cool”に過ぎると考える言語の例です。

Function: treesit-library-abi-version &optional min-compatible

この関数はtree-sitterライブラリーがサポートしている言語グラマーのABI (Application Binary Interface: アプリケーションバイナリーインターフェイス)のバージョンをリターンする。デフォルトではそのライブラリーがサポートする最新のABIバージョンをリターンするが、min-compatibleが非nilの場合にはそのライブラリーでまだサポートできる最古のABIバージョンをリターンする。言語グラマーライブラリーはtree-sitterライブラリーがサポートする最古と最新の間にあるABIバージョンにたいしてビルドしなければ、tree-sitterライブラリーがそれらをロードできなくなる。

Function: treesit-language-abi-version language

この関数はEmacsがロードしたlanguage用の言語グラマーライブラリーのABIバージョンをリターンする。languageが利用できなければnilをリターンする。

構文ツリーの具体例

構文ツリーはパーサーによって生成されます。構文ツリーにおけるノードはそれぞれがテキストのある部分を表し、お互いが親子関係というリレーションシップによって接続されています。たとえば以下のようなソーステキストがあるとします

1 + 2

これは以下のような構文ツリーになるかもしれません

                  +--------------+
                  | root "1 + 2" |
                  +--------------+
                         |
        +--------------------------------+
        |       expression "1 + 2"       |
        +--------------------------------+
           |             |            |
+------------+   +--------------+   +------------+
| number "1" |   | operator "+" |   | number "2" |
+------------+   +--------------+   +------------+

これを以下のようにS式で表すことも可能です:

(root (expression (number) (operator) (number)))

ノードタイプ

rootexpressionnumberoperatorのような名前はノードのタイプ(type: 型)を指定します。ただし構文ツリーのすべてのノードがタイプをもつ訳ではありません。タイプをもっていないノードは無名ノード(anonymous nodes)、タイプをもつノードは名前つきノード(named nodes)と呼ばれています。無名ノードは角カッコ‘]’のような区切り文字やreturnのようなキーワードを含む、固定化された綴りのトークン(token: 字句単位)です。

フィールド名

構文ツリーの分析を容易にするために、多くの言語グラマーは子ノードにフィールド名(field names)を割り当てています。たとえばfunction_definitionノードはdeclaratorbodyのフィールド名をもつかもしれません:

(function_definition
 declarator: (declaration)
 body: (compound_statement))

構文ツリーの調査

言語の構文の理解、および構文ツリー割り当て使用するLispプログラムのデバッグ支援のために、Emacsはカレントバッファーのソースの構文ツリーをリアルタイムで表示する“explore”モードを提供しています。更にEmacsにはポイント位置にあるノードの情報をモードラインに表示する“inspect”モードも付属しています。

Command: treesit-explore-mode

このモードはカレントバッファーのソースの構文ツリーを表示するウィンドウをポップアップする。ソースバッファーでテキストを選択することによって、表示されている構文ツリーの対応する部分がハイライトされる。構文ツリーでノードをクリックすれば、ソースバッファーの対応するテキストがハイライトされる。

Command: treesit-inspect-mode

このマイナーモードはポイント位置で始まるノードをモードラインに表示する。たとえばモードラインに以下のように表示されるかもしれない

parent field: (node (child (…)))

ここでnodechild、...等はポイント位置で始まるノード、parentnodeの親である。nodeはbold書体で表示される。field-namenodechild、...等のフィールド名である。

ポイント位置で始まるノードがない(ポイントがノードの中間にある)場合には、ポイントを跨ぐ(span)もっとも前のノード、およびそのノードの直近の親ノードがモードラインに表示される。

このマイナーモード自身はパーサーを作成せず、(treesit-parser-list)の最初のパーサーを使用する(tree-sitterパーサーの使用を参照)。

グラマー定義を読む

言語グラマーの製作者はプログラミング言語のグラマー(grammar: 文法)を定義します。パーサーがどのようにしてプログラムテキストから具体的な構文ツリーを構築するかを決めるのがグラマーです。構文ツリーを効果的に使用するためには、グラマーファイル(grammar file)を調べる必要があります。

グラマーファイルは通常だと言語グラマーのプロジェクトレポジトリにあるgrammar.jsです。言語グラマーのホームページへのリンクはtree-sitter’s homepageで見つけることができるでしょう。

グラマー定義はJavaScriptによって記述されます。たとえばfunction_definitionノードにマッチするようなルールは以下のようなものかもしれません

function_definition: $ => seq(
  $.declaration_specifiers,
  field('declarator', $.declaration),
  field('body', $.compound_statement)
)

ルールは単一の引数$を受け取る関数によって表現されます。この関数がグラマー全体を表すのです。この関数自体は他の関数によって構築されています。一連の子ノードをまとめるのがseq関数、子ノードにフィールド名の注釈をつけるのがfield関数です。上記の定義を俗にBNF (Backus-Naur Form: バッカス・ナウア記法)と呼ばれる構文で表せば以下のようになるでしょう

function_definition :=
  <declaration_specifiers> <declaration> <compound_statement>

そしてパーサーがリターンするノードは以下のようになります

(function_definition
  (declaration_specifier)
  declarator: (declaration)
  body: (compound_statement))

以下はグラマー定義で目にするかもしれない関数のリストです。これらの関数はいずれも引数として他のルールを受け取り新たなルールをリターンします。

seq(rule1, rule2, …)

すべてのルールに逐一マッチする。

choice(rule1, rule2, …)

引数のルールいずれかにマッチする。

repeat(rule)

rule0回以上マッチする。正規表現の演算子‘*’に似ている。

repeat1(rule)

rule1回以上マッチする。正規表現の演算子‘+’に似ている。

optional(rule)

rule0回または1回マッチする。正規表現の演算子‘?’に似ている。

field(name, rule)

ruleにマッチする子ノードにフィールド名nameを割り当てる。

alias(rule, alias)

ruleにマッチしたノードをパーサーが生成する構文ツリーでaliasとして表示する。たとえば、

alias(preprocessor_call_exp, call_expression)

これによりpreprocessor_call_expがマッチしたノードがcall_expressionと表示される。

以下は言語グラマーを読むにあたってそれほど重要ではないグラマー関数です。

token(rule)

単一の葉ノード(leaf node)としてruleをマークする。つまり個別の子ノードをもつ親ノードではなく、その単一の葉ノードにすべてが収斂されるようなノードを生成する。ノードの取得を参照のこと。

token.immediate(rule)

通常のグラマールールは先行する空白を無視するが、これは空白が前置されていないruleだけにマッチするよう変更する。

prec(n, rule)

ruleにたいしてレベルnの優先度を与える。

prec.left([n,] rule)

ruleにたいしてオプションとしてレベルnを付与して左結合(left-associative)とマークする。

prec.right([n,] rule)

ruleにたいしてオプションとしてレベルnを付与して右結合(right-associative)とマークする。

prec.dynamic(n, rule)

precと似ているが優先度は実行時に適用される。

tree-sitterプロジェクトにはmore about writing a grammarというドキュメントがあります。特に“The Grammar DSL”というセクションを読んでください。


Next: tree-sitterパーサーの使用, Up: プログラムソースの解析   [Contents][Index]