
2026/04/17 6:14
Tree-sitter のおかげで、より充実した R プログラミング体験をお楽しみください。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Tree-sitter 文法の "treesitter-r" リポジトリへの統合は、Jim Hester および Kevin Ushey が築いた基盤を、Davis Vaughan の仕事によってさらに進展させ、R コードの作成、保守、分析のあり方を変革しました。この成果は useR! 2024 で表彰され、主に Positron における IDE エクスペリエンスの改善(Ark を通じた自動補完や、GitHub の検索結果において関数の定義を特定するなどのナビゲーション機能の向上など)を通じて、開発者に高度な機能を解き放ちました。個別の編集を越えて、Tree-sitter は構造的解析ツールの広いエコシステムを可能にしています:Air と Jarl は高速なフォーマットとLintingを提供し、
{treesitter}はR環境内でコードの解析や構文ツリーの可視化を、{gander}はLLMとの相互作用を強化します。さらに、{igraph.r2cdocs}および{pkgdepends}といった専門パッケージはそれぞれCラッパーおよび依存関係を検出します。また、{ts}、{muttest}、difftastic、そしてuseR!で表彰された実績といったツールにより、Tree-sitterの有用性は設定ファイルの解析、突然変異テスト、構造的コード差分へ拡張され、単純なテキスト編集からR開発向けに洗練された構造的分析へと活気づいたパラダイムシフトを促進しました。本文
約 2 年前、Jim Hester氏と Kevin Ushey氏の研究成果を受け継ぎ、Davis Vaughan氏はRコミュニティに非常にインパクトのあるJavaScriptファイル(Tree-sitterパースャー生成器用のR言語用文法)を完成させました。その功績はuseR! 2024でのプレゼンテーションにおいて拍手に迎えられました!👏 では、 Davis Vaughan氏はこのJavaScriptファイルに含まれる「文法規則」が称賛されたのでしょうか?😅 いいえ、聴衆が興奮したのは、このファイルによって実現したR開発者体験(Developer Experience, DX)の向上でした。
Tree-sitterを基盤としたツール群は、Airによるコード整形やJarlによるlinting(静的解析)、Positron IDEにおけるオートコンプリートやカーソルホバー時のヘルプ提示、GitHub上のR関連リポジトリでの検索機能の改善など、多岐にわたる利便性をもたらしています。
本稿では、まずTree-sitterとは何かを解説し、さらにTree-sitterを基盤としたツールがR開発ワークフローにどのような恩恵をもたらすかを詳述します。
コード解析:Tree-sitterとは?
Tree-sitterはC言語で書かれたコードパースャー(解析器)生成器です。そのラッパー(バインディング)は、RustやR(!)など複数の言語に存在します。
少し時間を遡りましょう。「コードをパースする」とは具体的に何を意味するかというと、例えば文字列
"a <- mean(x, na.rm = TRUE)" が与えられた際、どの部分を関数名 (mean) と見なし、どの部分を引数名 (na.rm) または論理値 (TRUE) と識別できるのか、を正しく理解することです。これを行うには、コードを「パース木(Parse Tree)」へと変換する必要があります。Rコードを読む際に私たちは無意識にこれを頭の中で行っています😸。
もちろん、R自体はその独自の文法のおかげでRコードの解析が可能です。例えば、R固有のパイプ演算子
%>%を導入したコミットを見てもわかる通り、この機能を実装するためにはRの構文を拡張する必要があり、結果としてRの文法そのものが変更されました。
以下は、Rコードを解析するための
parse()関数と、解析結果を抽出するためのgetParseData()関数の使用例です。
parse( text = "a <- mean(x, na.rm = TRUE)", keep.source = TRUE ) |> getParseData() #> line1 col1 line2 col2 id parent token terminal text #> 23 1 1 1 26 23 0 expr FALSE #> 1 1 1 1 1 1 3 SYMBOL TRUE a #> 3 1 1 1 1 3 23 expr FALSE #> 2 1 3 1 4 2 23 LEFT_ASSIGN TRUE <- #> 21 1 6 1 26 21 23 expr FALSE #> 4 1 6 1 9 4 6 SYMBOL_FUNCTION_CALL TRUE mean #> 6 1 6 1 9 6 21 expr FALSE #> 5 1 10 1 10 5 21 '(' TRUE ( #> 7 1 11 1 11 7 9 SYMBOL TRUE x #> 9 1 11 1 11 9 21 expr FALSE #> 8 1 12 1 12 8 21 ',' TRUE , #> 13 1 14 1 18 13 21 SYMBOL_SUB TRUE na.rm #> 14 1 20 1 20 14 21 EQ_SUB TRUE = #> 15 1 22 1 25 15 16 NUM_CONST TRUE TRUE #> 16 1 22 1 25 16 21 expr FALSE #> 17 1 26 1 26 17 21 ')' TRUE )
あるいは、Gábor Csárdi氏による
{xmlparsedata}パッケージを用いて、同じデータをXML形式に変換することもできます。
parse( text = "a <- mean(x, na.rm = TRUE)", keep.source = TRUE ) |> xmlparsedata::xml_parse_data(pretty = TRUE) |> xml2::read_xml() |> as.character() |> cat() #> <?xml version="1.0" encoding="UTF-8" standalone="yes"?> #> <exprlist> #> <expr line1="1" col1="1" line2="1" col2="26" start="28" end="53"> #> <expr line1="1" col1="1" line2="1" col2="1" start="28" end="28"> #> <SYMBOL line1="1" col1="1" line2="1" col2="1" start="28" end="28">a</SYMBOL> #> </expr> #> <LEFT_ASSIGN line1="1" col1="3" line2="1" col2="4" start="30" end="31"><-</LEFT_ASSIGN> #> ... (省略)
どちらのケースでも、
LEFT_ASSIGNやSYMBOL_FUNCTION_CALLといった単語認識(トークン化)が行われます。コードを実行する前に解析を行うことは不可欠ですが、解析されたコードは他の用途にも活用可能です。例えば、壊れやすい正規表現に依存せずコードを分析する(特定の関数と呼び出しているか?)、コード構造を理解して移動する(関数の呼び出し元からその定義へ飛ぶ)、あるいはコードを書き換える(ある関数の出現箇所を別の関名に置き換えるなど)といった応用が可能になります。
Tree-sitterは、この同じコード解析作業を行いますしかし、その速度は格段に速いです。特に「増分パース(incremental parsing)」機能を備えている点が大きいですが、これはエディタ上で入力しながらシンタックスツリーを逐次更新する際に極めて重要です。Tree-sitterは文法さえ存在すればどの言語のコードでも解析可能であり、「ローゼッタストーン」といったプラグインのような役割を果たします。実際、多くの言語で利用されており、その周りに多数のツールが構築されています。
新しい言語をTree-sitterに「学習」させるには、その言語の構文定義を含むファイル(文法)を与える必要があります。ここで言及したDavis Vaughan氏らのJavaScriptファイルが活躍する場所です。Rの文法をTree-sitterが要求する形式へ翻訳した
treesitter-rリポジトリこそが、本稿で紹介する全てのツールの基盤となっています。これらはすべてRコードを入力とするツールです。
以下は、前述のコードを解析するための
{treesitter} Rパッケージを使った手順です。この{treesitter}パッケージは、Tree-sitterをR環境から直接利用できるようにします。Rコードを解析するには、{treesitter.r}パッケージからのlanguage()関数を必要とします。
library(treesitter) #> #> Attaching package: 'treesitter' #> The following object is masked from 'package:base': #> #> range language <- treesitter.r::language() parser <- parser(language) text <- "a <- mean(x, na.rm = TRUE)" parser_parse(parser, text) #> <tree_sitter_tree> #> #> ── Text ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #> a <- mean(x, na.rm = TRUE) #> #> ── S-Expression ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── #> (program [(0, 0), (0, 26)] #> (binary_operator [(0, 0), (0, 26)] #> lhs: (identifier [(0, 0), (0, 1)]) #> operator: "<-" [(0, 2), (0, 4)] #> rhs: (call [(0, 5), (0, 26)] #> function: (identifier [(0, 5), (0, 9)]) #> arguments: (arguments [(0, 9), (0, 26)] #> open: "(" [(0, 9), (0, 10)] #> argument: (argument [(0, 10), (0, 11)] #> value: (identifier [(0, 10), (0, 11)]) #> ) #> (comma [(0, 11), (0, 12)]) #> argument: (argument [(0, 13), (0, 25)] #> name: (identifier [(0, 13), (0, 18)]) #> "=" [(0, 19), (0, 20)] #> value: (true [(0, 21), (0, 25)]) #> ) #> close: ")" [(0, 25), (0, 26)] #> ) #> ) #> ) #> )
Tree-sitterは下図のツール群の中核をなすエンジンであり、これら全てのツールはTree-sitterとそれに対するR言語用の文法に依存しています。一部のツールはコマンドラインインターフェース(CLI)形式のものであり、その他はRパッケージです。
コードのインタラクティブ閲覧:Positron IDE, GitHub
聴衆がDavis Vaughan氏を称賛した真の理由は、Tree-sitter用のR文法がGitHubへデプロイされ、結果としてGitHub上でRコードを閲覧する体験感が(JavaScriptコードのように)ほぼ同等になったという事実を説明した点にあります。例えば、リポジトリ内で関数名を検索すれば、検索結果にその関数の定義も表示されます。Davis氏のスライド(PDF版も用意されています)をご覧いただくか、以下の変形例動画をご覧ください。Rの
vetiverリポジトリから「vetiver_model」と入力して検索バーを検索すると、関数定義が最初の結果として表示され、クリックすることでその定義ページに遷移できます。
また、Positron IDEで使用されるRカーネルであるArkがTree-sitterを活用している点も非常に有用です。Arkを通じてPositronではオートコンプリートやカーソルホバー時のヘルプ提示が可能となります。以下の動画は、Positron内でパイプラインの次のステップまで選択範囲を拡張する方法を示しています。
Tree-sitterによるコード検索・閲覧機能についてはDavis氏のスライドにも掲載されています。さらに、2024年のposit会議でLionel Henry氏とDavis Vaughan氏が発表した「Ark」に関するセッション(特にコードアシストに関するパート)も参照ください。 Emacsなどの開発環境でもTree-sitterへのサポートが実装されています。
コードの検索・閲覧
{treesitter} RパッケージとTree-sitterクエリ構文を活用することで、Rコードの解析と検索が可能です。Simon Couch氏による{gander}パッケージはLLM(大規模言語モデル)を駆使してRコードを書く際の体験感を向上させるために利用されるため、その依存関係として{treesitter}が必要です。また、{igraph}パッケージ向けの{roxygen2}拡張機能である{igraph.r2cdocs}も{treesitter} Rパッケージを利用しており、全てのigraph Rコードを解析することで、エクスポートされた関数において(直接的または間接的に)末尾が _impl で終わる関数を呼び出しているかどうかを検出し、R関数のマニュアルからC版のigraph関数のドキュメントへリンク付けできるようになっています。
さらに、依存関係をファイル内で検出する
{pkgdepends}パッケージもTree-sitter(C)を呼び出しています。以下はsaperlipopette Rパッケージのソースコードに対して実行した結果です。
pkgdepends::scan_deps( "../../../../../CHAMPIONS/saperlipopette", "../../../../../CHAMPIONS" ) #> #> Dependencies: #> + brio @ R/blame.R, R/check-editor.R, R/clean-dir.R, ... #> + cli @ inst/exo_bisect-Rprofile.en.R, ... #> + devtools @ saperlipopette.Rproj #> + fs @ R/blame.R, ... #> + gert @ inst/exo_check_editor-Rprofile.en.R, ... #> + knitr @ README.Rmd #> ... (省略)
ast-grepは、Tree-sitterを基盤としたコード検索および書き換えツールであり、Tree-sitterの構文よりも明確で使いやすいクエリ言語を提供します。名前の響きからgrepを連想しますが、ast-grepを使うと壊れやすい正規表現を記述する必要はありません😸。Etienne Bacher氏による{astgrepr}はRust版のast-grepラッパーであり、Etienne氏のコードリファクタリングツール{flir}で利用されています。また、Emil Hvitfeldt氏が執筆した有益なブログ記事には、Claudeへの指示文としてast-grepの使用法を文書化する方法が紹介されています。
コードの整形とLinting:Air, Jarl
コマンドラインインターフェース(CLI)の話に戻りましょう。
AirはDavis Vaughan氏とLionel Henry氏によって作成された、Rustで作られたTree-sitterを基盤としたCLIツールです。このツールはコードの整形(フォーマッティング)作業を非常に高速に行います。
JarlはEtienne Bacher氏によって作成され、Air(つまりTree-sitter)を基盤とするRust製のCLIツールです。Jarlはコードのlintingと修正を行い、極めて高速に動作します。さらに到達不可能なコード、未使用関数、重複した関数定義などの検出も可能です。
これらの例において、
{treesitter} RパッケージをラップするRパッケージを作成するよりも、Tree-sitterのバインディングをラップしたRust製CLIツールの作成の方が効率的である理由は以下の通りです:
- Rust製のCLIはコード編集速度が極めて速い;
- CLIツールは主流のIDE向けの拡張機能に統合されやすい(例:Positronなど);
- CI環境へのインストールにおいて、Rパッケージが必要とする「R環境そのもののセットアップ」を回避できるため、CLIの方が扱いやすい。
その他、少しばかり探検したが興味深いツールもいくつか紹介しておきましょう。
設定:TOML/JSONの解析用{ts}
{ts}Gábor Csárdi氏による
{ts}パッケージは、編集および操作用の2つのRパッケージの中核を担っています:
- TOML向け
{tstoml} - JSON向け
{tsjson}
これら2つのパッケージは、既存のR用解析器と比較して、コメント情報を保持したまま処理するという特徴があります。
コードのテスト:{muttest}
{muttest}突然変異テスト(Mutation Testing)は、例えばコード内の
+をランダムに - に置き換える(突然変異させる)等操作を行い、テストを実行することでその変異を検知できるかを確認する手法です。Jakub Sobolewski氏による{muttest}パッケージは、そのような突然変異テストを行うためのRパッケージであり、{treesitter} Rパッケージへの依存関係を持ちます。
コードの差分表示:difftastic
difftasticWilfred Hughes氏によって開発されたCLIツール「
difftastic」は、「構文を理解する構造ベースの差分表示ツール」です✨。つまり、difftasticは行単位の比較や「単語」単位の比較だけでなく、変更された行の前後(デフォルトでは3行)を見て実際のシンタックス構造を比較します。さらに素晴らしいことに、R言語自体もそのまま理解可能(Out of the box support)です。Rコードを使った差分表示の実例を含むブログ記事をご覧ください。
結論:今後さらに?
本稿では、RにおけるTree-sitter基盤のツール群について概観しました。なお、このエコシステムは非常に活発に開発が進められているため、ツールの登場や退場も繰り返されます。しかし、一般的なパースャー生成器にR言語の文法を取り入れることによる恩恵(優れた機能の提供)という点については間違いなく真実であり続けます。もしかすると、既存のツールへのコントリビューションや新たなツールの作成を通じて、皆様もこのエコシステムに貢献されるかもしれませんね?