GrooveSeek

Semantic search over a Markdown knowledge base, served over MCP.

View the Project on GitHub alphabet-h/grooveseek

13. grammar は 1 つだけ焼き込み、残りは読み込む

背景と問題

ADR-0012 がソースファイルを chunk にする方法を決めた。そのためには grammar が要る。そして grammar は小さくない — 生成された parse table は言語ごとに巨大な C のソースで、 全言語を持ち歩くツールはその全部を持ち歩く。

groove の value proposition は「置いて実行するバイナリ 1 つ」だった。他に入れるものは無く、 ランタイムもパッケージマネージャも要らない。20 個の grammar を同梱する code parser は、 重さだけでその約束を壊す。かといって 1 つも同梱しなければ、素のままでは何も parse できない。

本決定が答える問い: バイナリはどの grammar を持ち、別の言語が欲しい利用者はどう入手するのか。

決定を左右した事情

検討した選択肢

  1. 全 grammar を焼き込む。 単純だが、その単純さの代金がサイズ。この道を採ったエディタでは grammar が展開後サイズの大半を占めた。Markdown しか index しない利用者がそれを全額払う。
  2. cargo feature で、利用者が欲しい組み合わせをコンパイルする。 ソースからビルドする人に しか効かない。release パイプラインが作るのは platform ごとに 1 つの artifact で、 feature の組み合わせ行列は作れない。結局、公開バイナリはどれか 1 組を選ぶことになる — それは選択肢 1 か 5 が名前を変えたものでしかない。
  3. 1 つだけ焼き込み、残りは groove 自身が配る動的ライブラリにする (採用)。
  4. WebAssembly plugin。 サンドボックスされるのが魅力。だが組み込みランタイムだけで 数 MB かかり、plugin を一度も読まない人まで払う — 選択肢 1 を却下した理由そのもの。 toolchain の下限も上がり、macOS では JIT に署名済みアプリが要求すべき entitlement が要る。
  5. 何も焼き込まず、全言語を plugin にする。 選択肢 3 の feature を off にしただけの 同じコード経路なので、選べる状態は保たれる。ただし既定にすると、利用者が何かを 取りに行くまで 1 行も parse できない。
  6. 初回に言語バンドルを DL する。 便利だが、便利さのためにネットワークの性質を使い切る。 入手可能なバンドルは platform ごとに大きく、query を含んでいない。

決定

選択肢 3 を採る。Rust は既定で on の feature の後ろに焼き込む。他の言語はすべて、 groove 自身の release パイプラインが公開する別の動的ライブラリとし、利用者が groove の 読む置き場に配置する。

最初の 1 言語が Rust である理由は 2 つ。groove 自身がそれで書かれているので、groove を 自分のリポジトリに向けるだけで追加物なしに動く。そして、その grammar はバイナリ増分 1 MiB 強で測れた — 全員に渡せる大きさである。

tree-sitter 本家が公開しているのはソース書庫と WebAssembly ビルドであって native ライブラリ ではないので、ライブラリは groove が作り、groove が責任を持つ。バイナリと同じ release job が、 grammar に直接依存する crate から生成する — これが grammar と tags query を「一緒にビルドされた 版」に保つ。

結果と代償

参考

grammar の契約は crates/groove-grammar-abi にある。本決定が支える chunk 化の決定は ADR-0012

本決定は、その機構より先に出荷される。 v1.2.0 が運ぶのは焼き込まれた Rust grammar と 共有の契約まで。loader・置き場の設定・公開されるライブラリは v1.3.0 で届く。v1.2.0 を読んで plugin を置こうとした人は、まだ置き場を見つけられない。