Tree-sitterというソフトウェアがある。CとRustメインで書かれているパーサジェネレータとそれをとりまくツールである。
これを利用して、コードをもとにシンタックスハイライトを効かせたHTMLを生成できたのでその紹介をする。
- 追記(2023-05-16T10:43+09:00)
- Tree-sitterのアーキテクチャ
- Rust製CLIツール
- シンタックスハイライト
- HTMLに出力する(その1)(追記)
- HTMLに出力する(その2)
- まとめ
- あわせて読みたい
追記(2023-05-16T10:43+09:00)
tree-sitter
単体でもHTMLを出力できたのでその旨追記した。
Tree-sitterのアーキテクチャ
Tree-sitterはコアとなるソフトウェアと、各言語ごとの文法ファイル、そしてそれを実行するCLIが別個のモジュールとして提供されており、文法ファイルをビルドするとそれが使えるようになる、というモジュラーな設計になっている。
例えば、Scalaの文法ファイルはtree-sitter/tree-sitter-scala
で提供される。
そして、Tree-sitter自体はライブラリの形をしているので、各言語バインディングを使ってそれをC API経由で呼び出すという形になっている。言語バインディングは豊富で、Emacsから呼び出せたりPerlから呼び出せたりする。もちろん、Rustからも呼び出せる。
flowchart TB tsc["Tree-sitter (CLI)"] ts["Tree-sitter (Library)"] tss["tree-sitter-scala (文法定義)"] tssb["Scalaパーサ"] text["シンタックスハイライトされた文字列"] tsc -- "call" --> ts tss -- "provide grammar info" --> ts ts -- "generate" --> tssb tssb -- "parse Scala" --> tsc tsc -- "highlighting" --> text
Rust製CLIツール
そんなTree-sitterの言語バインディングだが、CLIとして動作するように設計されたRust製のバインディングが存在する。
tree-sitterリポジトリにあることからも分かるように、ほぼ公式の立ち位置にあるソフトウェアだ。
このCLIツールの面白い機能として、単体でシンタックスハイライトを動作させるという機能がある。これを使うと、任意の(Tree-sitterが文法を認識できる)ソースコードを読み取って、これにシンタックスハイライトを行って標準出力に吐き出してくれる。
事前準備(CLIツール)
まずは前述のCLIツールをインストールする。
$ cargo install tree-sitter-cli
といってもcargo
を使うだけでよい。自分がインストールしたときはちょっとRustのバージョンが古かったのでrustup
を使って最新のRustをインストールした。
次に、CLIツールの設定を初期化する。これは最初に一度やればよい。
$ tree-sitter init-config
すると、Linuxの場合は~/.config/tree-sitter/config.json
が生成される。
{ "parser-directories": [ "/home/windymelt/github", "/home/windymelt/src", "/home/windymelt/source", ], "theme": { "attribute": { "italic": true, "color": 124 }, "punctuation.bracket": 239, "comment": { "color": 245, "italic": true }, "function": 26, "tag": 18, "type.builtin": { "color": 23, "bold": true }, "number": { "color": 94, "bold": true }, "type": 23, "embedded": null, "constant.builtin": { "bold": true, "color": 94 }, "constructor": 136, "keyword": 56, "constant": 94, "property": 124, "module": 136, "operator": { "bold": true, "color": 239 }, "function.builtin": { "bold": true, "color": 26 }, "punctuation.delimiter": 239, "string.special": 30, "string": 28, "variable.builtin": { "bold": true }, "variable.parameter": { "underline": true } } }
このうち、parser-directories
に注目してほしい。ここで指定したディレクトリに、各言語の文法が定義されたリポジトリが置かれている必要がある。ここでは追加で"/home/windymelt/src/github.com/tree-sitter"
を指定した。
また、テーマを指定したい場合はtheme
で設定することになるが、ここではいったんデフォルトのままとした。
事前準備(文法)
ここではScalaの文法を使うことにする。したがって、tree-sitter/tree-sitter-scala
をcloneしておく。
$ cd ~/src/github.com/tree-sitter
$ git clone git@github.com:tree-sitter/tree-sitter-scala.git
次に文法が正しく動くか検査する。
$ cd ~/src/github.com/tree-sitter/tree-sitter-scala $ tree-sitter test
問題なく終わるはず。
シンタックスハイライト
シンタックスハイライトを行う最低限の準備が完了した。この状態でtree-sitter highlight Foo.scala
を実行すると、標準出力にシンタックスハイライトされたScalaのコードが表示される。
好みでテーマを設定すれば、好きな色で表示することもできる。
HTMLに出力する(その1)(追記)
tree-sitter highlight --html
コマンドを使うとHTMLでシンタックスハイライトを実行し、結果を標準出力に流してくれる。
# -Hは--htmlのshorthand $ tree-sitter -H Foo.scala > foo.html
<!doctype HTML> <head> <title>Tree-sitter Highlighting</title> <style> body { font-family: monospace } .line-number { user-select: none; text-align: right; color: rgba(27,31,35,.3); padding: 0 10px; } .line { white-space: pre; } </style> </head> <body> <table> <tr><td class=line-number>1</td><td class=line><span style='color: #ea6962'>package</span> <span style='color: #d8a657'>com</span><span style='color: #928374'>.</span><span style='color: #d8a657'>github</span><span style='color: #928374'>.</span><span style='color: #d8a657'>windymelt</span><span style='color: #928374'>.</span><span style='color: #d8a657'>zmm</span> </td></tr> <tr><td class=line-number>2</td><td class=line> </td></tr> <tr><td class=line-number>3</td><td class=line>import cats<span style='color: #928374'>.</span><span style='color: #d8a657'>effect</span><span style='color: #928374'>.</span><span style='color: #d8a657'>ExitCode</span> </td></tr> <tr><td class=line-number>4</td><td class=line>import cats<span style='color: #928374'>.</span><span style='color: #d8a657'>effect</span><span style='color: #928374'>.</span><span style='color: #d8a657'>IO</span> </td></tr> ...
基本的にこれでも十分に便利そうだ。
HTMLに出力する(その2)
さて、行番号なしのコードがHTMLで欲しいときがある。Tree-sitterの機能を使わなければ、theZiz/aha
というツールを使うことで標準出力をそのままHTMLに変換できるため、Tree-sitterで好みのテーマでシンタックスハイライトしてもらい、HTMLとして出力するという組み合わせができる。
aha
自体はディストリビューションのリポジトリに標準で入っていることが多いようで、自分もzypper
でインストールできた:
$ sudo zypper install aha
aha
を単体で使うとXHTMLが出力され、html
要素やbody
タグもいっしょに付いてくる:
$ tree-sitter highlight Foo.scala | aha > foo.html
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!-- This file was created with the aha Ansi HTML Adapter. https://github.com/theZiz/aha --> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="application/xml+xhtml; charset=UTF-8"/> <title>stdin</title> </head> <body> <pre> <span style="color:#5f00d7;">package</span> <span style="color:#005f5f;">com</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">github</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">windymelt</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">zmm</span> import cats<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">effect</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">ExitCode</span> import cats<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">effect</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">IO</span> import cats<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">effect</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">IOApp</span> import com<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">monovore</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">decline</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">Opts</span> import com<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">monovore</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">decline</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">effect</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">CommandIOApp</span> ...
ちゃんとブラウザでも綺麗にレンダリングされている。
これでも十分有用だが、-n
オプションを使うとpre
要素まで全部剥がしてくれるため、これを使った出力を改めてpre
要素に詰め込むという手法を取るとプログラマチックに動かせて便利だ:
$ tree-sitter highlight Foo.scala | aha -n > foo.html
<span style="color:#5f00d7;">package</span> <span style="color:#005f5f;">com</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">github</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">windymelt</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">zmm</span> import cats<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">effect</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">ExitCode</span> import cats<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">effect</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">IO</span> import cats<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">effect</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">IOApp</span> import com<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">monovore</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">decline</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">Opts</span> import com<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">monovore</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">decline</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">effect</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">CommandIOApp</span> import org<span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">http4s</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">syntax</span><span style="color:#4a4a4a;">.</span><span style="color:#005f5f;">header</span> ...
例えばテンプレートエンジンから呼び出すと、テンプレートエンジンにシンタックスハイライト機能を搭載できる。
まとめ
- tree-sitterは、C/Rustで書かれたパーサジェネレータ/パースライブラリである。
- tree-sitterは実行時はプリビルドされた文法を使ってパースだけ行うため、非常に高速に動作する。
- tree-sitterに付属する同名のCLIツールによって、シンタックスハイライトを行うことが可能である。
- tree-sitterは依存性が少ない(パーサ生成のためにnodeが、パーサの実行のためにCコンパイラが必要だが、依存性地獄とはほど遠い印象だ)。
- tree-sitter(とahaとを組み合わせること)で、任意のソースコードをシンタックスハイライトしたHTMLが得られる。