
2026/02/26 3:28
**Show HN: Tree‑Sitter を Go に移植しました** Tree‑Sitter のパーサライブラリを C から Go に完全に移植し、使用できる状態になりました。 - **なぜ?** - Go プロジェクトとの統合が容易になります。 - C‑Go バインディングとその落とし穴を回避できます。 - **含まれる内容は?** - コア API 全般(パーサ作成、文法読み込み、構文木走査)。 - 元の C テストスイートに合わせた最小限のテストセット。 - **使い方** ```go import "github.com/yourrepo/tree-sitter-go" parser := treesitter.NewParser() parser.SetLanguage(treesitter.Go) root := parser.Parse(nil, sourceCode) ``` (詳細は README のサンプルをご覧ください。) - **パフォーマンス** - ベンチマークでは、ほとんどのワークロードで C バージョンに匹敵する速度を確認。 - Go の GC によりメモリ使用量がわずかに増加します。 - **ロードマップ** - 増分パース(incremental parsing)のサポート追加。 - 現在のサブセットを超える言語文法への拡張。 ぜひ試してみてください。問題があれば報告、あるいは貢献も歓迎します!
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
gotreesitter は、Go と WebAssembly でネイティブに動作する Tree‑Sitter ランタイムの純粋な Go 実装です。CGo や外部ツールチェーンを必要とせず、同じパーステーブル形式(アップストリーム Tree‑Sitter と同一)を使用しているため、205 のサポート言語(204 つの「完全」文法+1 つの部分的文法)が再コンパイルなしで動作します。ベンチマークでは、インクリメンタル編集時に CGo バインディングより約 90 倍 速く、単一バイト編集は ~1 µs(CGo は ~124 µs)です。また、フルパースでは約 1.5 倍 速い(≈1 330 µs 対 2 058 µs)。API には
NewParser、Edit、ParseIncremental があり、クエリ実行 (NewQuery、Exec、ストリーミングカーソル) と構文ハイライトやシンボルトラッキングなどの機能が提供されます。グラムマーブロブは圧縮保存され、初回使用時に遅延ロードされ、オプションで mmap サポートとキャッシュ制限があります。本ライブラリはスモークテスト、ゴールデン S‑式スナップショット、ハイライト検証、クエリエンジンチェック、パーサ正確性テスト、およびファズ (FuzzGoParseDoesNotPanic) で厳密にテストされています。今後の作業としては、クエリエンジンの整合性強化、外部スキャナ追加、明示的なパースエラー返却、自動パリティテスト(C 実装との比較)、ファズカバレッジ拡大などが挙げられます。プロジェクトは MIT ライセンスです。本文
gotreesitter
純粋Goで実装された tree‑sitter ランタイム ― CGo も C ツールチェーンも不要、WASM に対応済み
go get github.com/odvcencio/gotreesitter
tree‑sitter が使用するパーステーブル形式をそのまま実装しているため、既存の文法は再コンパイル無しで動作します。
CGo バインディングよりもすべてのワークロードで高速です ― エディタや言語サーバーで最頻操作となる incremental edits は C 実装に比べ 90 倍速です。
なぜ CGo を使わないのか?
既存の Go 用 tree‑sitter バインディングはすべて CGo を必要とします。すると次のような問題が発生します。
- クロスコンパイルが失敗する(例:
で Linux からビルド、Windows では MSYS2 が必須)GOOS=wasip1 GOARCH=arm64 - CI パイプラインはすべてのビルドイメージに C ツールチェーンをインストールしなければならない
は gcc が無い環境で失敗するgo install- ランタイム検出、フィジング、カバレッジツールは CGo 境界でうまく機能しない
gotreesitter は純粋 Go です。どのターゲット・プラットフォームでも
go get と build が可能です。
クイックスタート
import ( "fmt" "github.com/odvcencio/gotreesitter" "github.com/odvcencio/gotreesitter/grammars" ) func main() { src := []byte(`package main func main() {} `) lang := grammars.GoLanguage() parser := gotreesitter.NewParser(lang) tree := parser.Parse(src) fmt.Println(tree.RootNode()) // ソースを編集した後はインクリメンタルに再パース // tree.Edit(edit) // tree2 := parser.ParseIncremental(newSrc, tree) }
クエリ
tree‑sitter の S‑expression クエリ言語(predicate と cursor ベースのストリーミング)をサポートしています。現在の制約は Known Limitations を参照してください。
q, _ := gotreesitter.NewQuery(`(function_declaration name: (identifier) @fn)`, lang) cursor := q.Exec(tree.RootNode(), lang, src) for { match, ok := cursor.NextMatch() if !ok { break } for _, cap := range match.Captures { fmt.Println(cap.Node.Text(src)) } }
インクリメンタル編集
初回パース後、変更箇所だけを再パースします ― それ以外のサブツリーは自動的に再利用されます。
// 初期パース tree := parser.Parse(src) // ユーザーがバイトオフセット 42 に「x」を入力したとする src = append(src[:42], append([]byte("x"), src[42:]...)...) tree.Edit(gotreesitter.InputEdit{ StartByte: 42, OldEndByte: 42, NewEndByte: 43, StartPoint: gotreesitter.Point{Row: 3, Column: 10}, OldEndPoint: gotreesitter.Point{Row: 3, Column: 10}, NewEndPoint: gotreesitter.Point{Row: 3, Column: 11}, }) // インクリメンタル再パース – CGo バインディングより約1.38 µs vs 124 µs(90×高速) tree2 := parser.ParseIncremental(src, tree)
ヒント:
grammars.DetectLanguage("main.go") を使えばファイル名から適切な文法を自動選択できます ― エディタ統合に便利です。
シンタックスハイライト
hl, _ := gotreesitter.NewHighlighter(lang, highlightQuery) ranges := hl.Highlight(src) for _, r := range ranges { fmt.Printf("%s: %q\n", r.Capture, src[r.StartByte:r.EndByte]) }
注意:テキスト predicate(
#eq?, #match?, #any‑of?, #not‑eq?)はソース []byte を評価対象とします。nil を渡すと predicate チェックが無効化されます。
シンボルタグ付け
定義・参照を抽出します。
entry := grammars.DetectLanguage("main.go") lang := entry.Language() tagger, _ := gotreesitter.NewTagger(lang, entry.TagsQuery) tags := tagger.Tag(src) for _, tag := range tags { fmt.Printf("%s %s at %d:%d\n", tag.Kind, tag.Name, tag.NameRange.StartPoint.Row, tag.NameRange.StartPoint.Column) }
パース品質
各
LangEntry は Quality フィールドを公開し、パース結果の信頼性を示します。
| Quality | 意味 |
|---|---|
| full | Token source または DFA + 外部スキャナー – 完全な忠実度 |
| partial | DFA‑partial – 外部スキャナーが欠落。ツリーに隠れたギャップが存在する可能性あり |
| none | パースできない |
entries := grammars.AllLanguages() for _, e := range entries { fmt.Printf("%s: %s\n", e.Name, e.Quality) }
ベンチマーク
go-tree-sitter(標準 CGo バインディング)と比較し、500 関数定義を含む Go ソースファイルのパース結果を測定しました。
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
| CTreeSitterGoParseFull | 2 058 000 | 600 | 6 |
| CTreeSitterGoParseIncrementalSingleByteEdit | 124 100 | 648 | 7 |
| CTreeSitterGoParseIncrementalNoEdit | 121 100 | 600 | 6 |
| GoParseFull | 1 330 000 | 10 842 | 2 495 |
| GoParseIncrementalSingleByteEdit | 1 381 | 361 | 9 |
| GoParseIncrementalNoEdit | 8.63 | 0 | 0 |
まとめ
| ワークロード | gotreesitter | CGo バインディング | 比率 |
|---|---|---|---|
| Full parse | 1 330 µs | 2 058 µs | 約1.5×高速 |
| Incremental (single‑byte edit) | 1.38 µs | 124 µs | 約90×高速 |
| Incremental (no‑op reparse) | 8.6 ns | 121 µs | 約14 000×高速 |
インクリメンタルのホットパスはサブツリーを積極的に再利用します ― 1 バイト編集でマイクロ秒単位、CGo バインディングでは C ランタイムと呼び出しオーバーヘッドがかかります。no‑edit の高速経路は nil チェックのみで終了:割り当てゼロ、数ナノ秒。
対応言語
レジストリには 205 個の文法が付属しています。
go run ./cmd/parity_report を実行してリアルタイムの言語別ステータスを確認してください。
- 204 full – エラーなしでパース(Token source または DFA + 完全な外部スキャナー)
- 1 partial – norg(122 トークンの外部スキャナーが必要、まだ実装未完了)
- 0 unsupported
バックエンド別内訳:
| Backend | Count |
|---|---|
| dfa | 195 |
| dfa‑partial | 1 |
| token_source | 9 |
111 の言語は
zzz_scanner_attachments.go に Go で実装された外部スキャナーを持っています。
全言語リスト(205):
ada, agda, angular, apex, arduino, asm, astro, authzed, awk, bash, bass, beancount, bibtex, bicep, bitbake, blade, brightscript, c, c_sharp, caddy, cairo, capnp, chatito, circom, clojure, cmake, cobol, comment, commonlisp, cooklang, corn, cpon, cpp, crystal, css, csv, cuda, cue, cylc, d, dart, desktop, devicetree, dhall, diff, disassembly, djot, dockerfile, dot, doxygen, dtd, earthfile, ebnf, editorconfig, eds, eex, elisp, elixir, elm, elsa, embedded_template, enforce, erlang, facility, faust, fennel, fidl, firrtl, fish, foam, forth, fortran, fsharp, gdscript, git_config, git_rebase, gitattributes, gitcommit, gitignore, gleam, glsl, gn, go, godot_resource, gomod, graphql, groovy, hack, hare, haskell, haxe, hcl, heex, hlsl, html, http, hurl, hyprlang, ini, janet, java, javascript, jinja2, jq, jsdoc, json, json5, jsonnet, julia, just, kconfig, kdl, kotlin, ledger, less, linkerscript, liquid, llvm, lua, luau, make, markdown, markdown_inline, matlab, mermaid, meson, mojo, move, nginx, nickel, nim, ninja, nix, norg, nushell, objc, ocaml, odin, org, pascal, pem, perl, php, pkl, powershell, prisma, prolog, promql, properties, proto, pug, puppet, purescript, python, ql, r, racket, regex, rego, requirements, rescript, robot, ron, rst, ruby, rust, scala, scheme, scss, smithy, solidity, sparql, sql, squirrel, ssh_config, starlark, svelte, swift, tablegen, tcl, teal, templ, textproto, thrift, tlaplus, tmux, todotxt, toml, tsx, turtle, twig, typescript, typst, uxntal, v, verilog, vhdl, vimdoc, vue, wgsl, wolfram, xml, yaml, yuck, zig
クエリ API
| 機能 | 状態 |
|---|---|
コンパイル + 実行 (, , ) | 対応 |
Cursor ストリーミング (, , ) | 対応 |
構造量化子() | 対応 |
交差() | 対応 |
フィールドマッチ () | 対応 |
| 対応 |
| 対応 |
| 対応 |
| 対応 |
| 対応 |
| 対応 |
| 対応 |
ディレクティブ | 解析し受け入れ |
現在の制限
- クエリコンパイラギャップ – 2026年2月23日現在、本リポジトリ内で配布されているハイライト・タグクエリはすべてコンパイル可能(
156/156、HighlightQuery
69/69)TagsQuery - DFA‑partial 言語 – norg は外部スキャナーが未移植。DFA ラッカーのみでパースし、外部スキャナーに必要なトークンは無視されます。ツリー構造は有効ですがギャップが存在する可能性があります。
を確認して full/partial を判別してください。entry.Quality
言語追加手順
-
に文法を追加grammars/languages.manifest -
バインディング生成:
go run ./cmd/ts2go -manifest grammars/languages.manifest \ -outdir ./grammars -package grammars -compact=trueこれにより
、grammars/embedded_grammars_gen.go
、言語レジストリスタブが再生成されます。grammars/grammar_blobs/*.bin -
とcmd/parity_report/main.go
にスモークサンプルを追加grammars/parse_support_test.go -
確認:
go run ./cmd/parity_report go test ./grammars/...
アーキテクチャ
gotreesitter は tree‑sitter ランタイムを純粋 Go で再実装しています。
- Parser – テーブル駆動 LR(1)、曖昧文法用 GLR サポート
- Incremental reuse – cursor ベースのサブツリー再利用。変更されていない領域はパースを完全にスキップ
- Arena allocator – スラブベースノード割り当て、参照カウントで GC 負荷最小化
- DFA lexer –
で生成されたテーブルから作成。必要に応じて手書きブリッジも用意ts2go - External scanner VM – 言語固有のスキャン(Python のインデントなど)を実行するバイトコード解釈器
- Query engine – S‑expression パターンマッチング、predicate 評価、ストリーミング cursor
- Highlighter – クエリベースのシンタックスハイライト(インクリメンタル対応)
- Tagger – タグクエリを用いたシンボル定義・参照抽出
文法テーブルは upstream tree‑sitter の
parser.c から ts2go ツールで抽出し、圧縮バイナリブロブにシリアライズ。最初の言語使用時に遅延ロードします。パース実行中に C コードは走りません。
ビルド時に
-tags grammar_blobs_external を付与して GOTREESITTER_GRAMMAR_BLOB_DIR を設定すると、バイナリ内にブロブを埋め込まず外部ファイルとしてロードできます。Unix ではデフォルトで mmap が使用されます(GOTREESITTER_GRAMMAR_BLOB_MMAP=false で無効化)。
小さな埋め込みバイナリを作成したい場合は
-tags grammar_set_core を付与してコア言語セット(c, go, java, javascript, python, rust, typescript 等)だけを含めることができます。
ランタイムで登録される言語を制限するには:
GOTREESITTER_GRAMMAR_SET=go,json,python
長時間稼働するプロセスでは、グラマキャッシュメモリは調整可能です:
// 直近に使用した 8 個のデコード済み文法だけを保持 grammars.SetEmbeddedLanguageCacheLimit(8) // 一つの言語ブロブ(例: "rust.bin")をキャッシュから削除 grammars.UnloadEmbeddedLanguage("rust.bin") // キャッシュ内のすべてのデコード済み文法を破棄 grammars.PurgeEmbeddedLanguageCache()
また、起動時に
GOTREESITTER_GRAMMAR_CACHE_LIMIT を設定してキャッシュ上限を適用できます。0 にすると明示的に保持しないことを意味します(毎回デコードが再実行されます)。
アイドル退避は環境変数で有効化可能です:
GOTREESITTER_GRAMMAR_IDLE_TTL=5m GOTREESITTER_GRAMMAR_IDLE_SWEEP=30s
ロード時のコンパクション/インターンはデフォルトで有効。以下を通じて調整できます:
GOTREESITTER_GRAMMAR_COMPACT=true GOTREESITTER_GRAMMAR_STRING_INTERN_LIMIT=200000 GOTREESITTER_GRAMMAR_TRANSITION_INTERN_LIMIT=20000
テスト
テストスイートは以下を含みます。
- Smoke tests – 205 個の文法すべてがサンプルでクラッシュせず
ノードも生成しないことを確認ERROR - Correctness snapshots – コア言語 20 種類のゴールデン S‑expression テストでパーサと文法の回帰検出
- Highlight validation – コンパイル済みハイライトクエリが正しくハイライト範囲を生成するか End‑to‑End テスト
- Query tests – パターンマッチ、predicate、cursor、フィールドベースマッチングの検証
- Parser tests – インクリメンタル再パース、エラー回復、GLR アンビギュイティ解消
- Fuzzing –
でパーサの堅牢性を確認FuzzGoParseDoesNotPanic
実行例:
go test ./... -race -count=1
ロードマップ
現状:v0.4.0 ― 205 文法、安定したパーサ、インクリメンタル再パース、クエリエンジン、ハイライト、タグ付け。
次のステップ:
- クエリエンジンの完全性強化 – フィールド否定セマンティクス、メタデータディレクティブ挙動、アップストリーム tree‑sitter との細部一致
- 高価値 DFA‑partial 言語への手書き外部スキャナー追加
– エラーを返却(silent nil 木ではなく)Parse() (*Tree, error)- C tree‑sitter 出力とのパリティ自動テスト化
- フィジング拡張でより多くの言語とクエリエンジンをカバー
ライセンス
MIT