AlmaLinux 9(WSL)で tree-sitter をセットアップして C++ の構造解析をしてみた
はじめに
コードの特定の場所に処理を追加したいとき、多くの人がまず思いつくのは「正規表現による検索」だと思います。しかし、他のアプローチとして「構造検索」という方法もあります。
たとえば、コメントや文字列リテラルを除外して、本当に挿入したい場所だけを見つけたいときなど、コードの構造を理解して検索するほうが正確です。
最近のエディタ(たとえば VSCode や Neovim)では、コメント部分が自動で薄い色になるなど、構造をもとにした解析が行われています。こうした機能の裏側では、tree-sitter のような構文解析ツールが使われている場合もあります。
今回は、そんな構造検索の強力な武器となる「tree-sitter」を、WSL 上の AlmaLinux 9 環境でセットアップし、実際に構造解析をしてみるところまでをまとめます。
対象環境
- OS: Windows 10 または 11
- 仮想化: WSL(Windows Subsystem for Linux)
- Linux ディストリビューション: AlmaLinux 9
- 主要パッケージ: Python 3.x、gcc、make、git
※ AlmaLinux 9 以外の RedHat 系ディストリビューションや、他の WSL 対応 Linux でも基本的には応用可能です。
tree-sitter のバージョン選定に注意
tree-sitter の Python バインディングは、バージョンによって使える API が異なります。
Language.build_library() 関数は v0.21.3 までしか使えず、最新版(例: 0.24.0)では削除されているため注意してください。
例えば、最新バージョンで下記のようなエラーが出ます。
AttributeError: type object 'tree_sitter.Language' has no attribute 'build_library'
そのため、本記事では 0.21.3 を利用します。
AlmaLinux 9 へのセットアップ手順
まず、WSL で AlmaLinux 9 環境を用意します。
その上で、dnf コマンドを使って開発ツールをインストールします。
sudo dnf update -y
sudo dnf install -y gcc gcc-c++ make git python3 python3-pip python3-devel
続いて、tree-sitter の Python バインディング(v0.21.3)をインストールします。
pip3 install tree-sitter==0.21.3
tree-sitter-cpp パーサのビルド
C++ の構造解析用に、tree-sitter-cpp をクローンします。
git clone https://github.com/tree-sitter/tree-sitter-cpp.git
cd tree-sitter-cpp
続いて、共有ライブラリをビルドします。(パスは自分の環境に合わせて調整してください)
gcc -shared -o my-languages.so -fPIC $(find . -name '*.c') -I/usr/include/python3.9
ls -lh my-languages.so
Python スクリプトで構造解析を試す
ビルドした共有ライブラリを使って、実際に C++ コードの構造解析を試してみます。
tree-sitter では、ソースコードを解析すると「AST(抽象構文木 / Abstract Syntax Tree)」と呼ばれるツリー構造が得られます。ASTは、コードの構造(関数・条件分岐など)を階層的に表現したものです。
下記のサンプルスクリプト(例: test.py)を用意します。
from tree_sitter import Language, Parser
# 共有ライブラリをロード
CPP_LANGUAGE = Language('./tree-sitter-cpp/my-languages.so', 'cpp')
# パーサーの初期化
parser = Parser()
parser.set_language(CPP_LANGUAGE)
# C++ コードの例
test_code = """
#include <iostream>
void example() {
if (true) {
std::cout << "Inside if" << std::endl;
}
}
"""
# ソースコードを解析してAST(抽象構文木)を取得
tree = parser.parse(test_code.encode())
root_node = tree.root_node
# AST(抽象構文木)は、コードの構造をツリー状に分解したものです
# ASTを探索して出力
def traverse_and_print(node, level=0):
indent = " " * level
print(f"{indent}{node.type}: {node.start_point} - {node.end_point}")
for child in node.children:
traverse_and_print(child, level + 1)
traverse_and_print(root_node)
実行例と出力結果
スクリプトを実行します。
python3 test.py
期待される出力例は下記のようになります。
translation_unit: (0, 0) - (9, 0)
preproc_include: (0, 0) - (1, 0)
function_definition: (2, 0) - (8, 1)
type: (2, 0) - (2, 4)
declarator: (2, 5) - (2, 13)
compound_statement: (2, 15) - (8, 1)
if_statement: (3, 4) - (7, 5)
condition: (3, 7) - (3, 11)
compound_statement: (3, 13) - (7, 5)
まとめ
以上の手順で、AlmaLinux 9 上の WSL 環境に tree-sitter をセットアップし、C++ の構造解析ができるところまでを紹介しました。
構造解析を使うことで、正規表現だけでは難しかった「本当に挿入したい場所」への処理追加なども実現しやすくなります。
興味のある方は使ってみてください。
関連記事
tree-sitter を使った構造解析を実際の現場でどう活用したかについては、こちらの記事でまとめています。
Discussion