🌲

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 を使った構造解析を実際の現場でどう活用したかについては、こちらの記事でまとめています。
https://zenn.dev/naosegu/articles/eb859db78471cd

Discussion