Tempo de execução do tree-sitter Pure-Go — sem CGo, sem conjunto de ferramentas C, pronto para WASM.
go get github.com/odvcencio/gotreesitterImplementa o mesmo formato de tabela de análise usado pelo tree-sitter, para que as gramáticas existentes funcionem sem recompilação. Supera a ligação CGo em todas as cargas de trabalho — edições incrementais (a operação dominante em editores e servidores de linguagem) são 90x mais rápido do que a implementação C.
Cada ligação de tree-sitter Go existente requer CGo. Isso significa:
- Quebras de compilação cruzada (
GOOS=wasip1,GOARCH=arm64do Linux, Windows sem MSYS2) - Os pipelines de CI precisam de um conjunto de ferramentas C em cada imagem de construção
go installfalha para usuários finais semgcc- Ferramentas de detector de corrida, difusão e cobertura funcionam mal na fronteira do CGo
gotreesitter é puro Go. go get e construir — em qualquer alvo, em qualquer plataforma.
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())
// After editing source, reparse incrementally:
// tree.Edit(edit)
// tree2 := parser.ParseIncremental(newSrc, tree)
}A linguagem de consulta de expressão S do Tree-sitter é suportada, incluindo predicados e streaming baseado em cursor. Consulte Limitações conhecidas para obter as advertências atuais.
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))
}
}Após a análise inicial, analise novamente apenas a região alterada — as subárvores inalteradas são reutilizadas automaticamente.
// Initial parse
tree := parser.Parse(src)
// User types "x" at byte offset 42
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},
})
// Incremental reparse — ~1.38 μs vs 124 μs for the CGo binding (90x faster)
tree2 := parser.ParseIncremental(src, tree)Dica: Usar
grammars.DetectLanguage("main.go")para escolher a gramática correta por nome de arquivo — útil para integração com o editor.
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])
}Observação: Predicados de texto (
#eq?,#match?,#any-of?,#not-eq?) exigemsource []byteavaliar. Passagemnildesativa verificações de predicados.
Extraia definições e referências do código-fonte:
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)
}Cada LangEntry expõe um Quality campo indicando quão confiável é a saída da análise:
| Qualidade | Significado |
|---|---|
full | Fonte de token ou DFA com scanner externo — fidelidade total |
partial | DFA parcial – scanner externo ausente, a árvore pode ter lacunas silenciosas |
none | Não é possível analisar |
entries := grammars.AllLanguages()
for _, e := range entries {
fmt.Printf("%s: %s\n", e.Name, e.Quality)
}Medido contra go-tree-sitter (a ligação CGo padrão), analisando um arquivo de origem Go com 500 definições de função.
goos: linux / goarch: amd64 / cpu: Intel(R) Core(TM) Ultra 9 285
# pure-Go parser benchmarks (root module)
go test -run '^$' -bench 'BenchmarkGoParse' -benchmem -count=3
# C baseline benchmarks (cgo_harness module)
cd cgo_harness
go test . -run '^$' -tags treesitter_c_bench -bench 'BenchmarkCTreeSitterGoParse' -benchmem -count=3
| Referência | ns/op | B/op | alocações/operações |
|---|---|---|---|
BenchmarkCTreeSitterGoParseFull | 2.058.000 | 600 | 6 |
BenchmarkCTreeSitterGoParseIncrementalSingleByteEdit | 124.100 | 648 | 7 |
BenchmarkCTreeSitterGoParseIncrementalNoEdit | 121.100 | 600 | 6 |
BenchmarkGoParseFull | 1.330.000 | 10.842 | 2.495 |
BenchmarkGoParseIncrementalSingleByteEdit | 1.381 | 361 | 9 |
BenchmarkGoParseIncrementalNoEdit | 8,63 | 0 | 0 |
Resumo:
| Carga de trabalho | gotreesitter | Ligação CGo | Razão |
|---|---|---|---|
| Análise completa | 1.330 μs | 2.058 μs | ~1,5x mais rápido |
| Incremental (edição de byte único) | 1,38 μs | 124 μs | ~90x mais rápido |
| Incremental (reparo autônomo) | 8,6ns | 121 μs | ~14.000x mais rápido |
O hot path incremental reutiliza subárvores agressivamente – uma edição de byte único é reanalisada em microssegundos enquanto a ligação CGo paga o tempo de execução C completo e a sobrecarga de chamada. O caminho rápido sem edição termina em uma única verificação nula: zero alocações, nanossegundos de um dígito.
205 gramáticas estão no registro. Correr go run ./cmd/parity_report para status ativo por idioma.
Resumo atual:
- 204 completo — análise sem erros (fonte de token ou DFA com scanner externo completo)
- 1 parcial –
norg(requer scanner externo com 122 tokens, ainda não implementado) - 0 sem suporte
Detalhamento do back-end:
- 195 DFA — Lexer DFA com scanner externo Go escrito à mão quando necessário
- 1 dfa-parcial — DFA gerado sem scanner externo (
norg) - 9 token_source – ponte lexer pure-Go escrita à mão (authzed, c, go, html, java, json, lua, toml, yaml)
111 idiomas possuem scanners externos Go escritos à mão anexados via zzz_scanner_attachments.go.
Lista completa de idiomas (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
| Recurso | Status |
|---|---|
Compilar + executar (NewQuery, Execute, ExecuteNode) | suportado |
Fluxo de cursor (Exec, NextMatch, NextCapture) | suportado |
Quantificadores estruturais (?, *, +) | suportado |
Alternação ([...]) | suportado |
Correspondência de campo (name: (identifier)) | suportado |
#eq? / #not-eq? | suportado |
#match? / #not-match? | suportado |
#any-of? / #not-any-of? | suportado |
#lua-match? | suportado |
#has-ancestor? / #not-has-ancestor? | suportado |
#not-has-parent? | suportado |
#is? / #is-not? | suportado |
#set! / #offset! diretivas | analisado e aceito |
A partir de 23 de fevereiro de 2026, todas as consultas de destaque e tags enviadas serão compiladas neste repositório (156/156 não vazio HighlightQuery entradas, 69/69 não vazio TagsQuery entradas).
Nenhuma lacuna conhecida na sintaxe de consulta atualmente bloqueia consultas de destaque ou tags enviadas.
1 idioma (norg) requer um scanner externo que não tenha sido portado para Go. Ele analisa usando apenas o lexer do DFA, mas os tokens que exigem o scanner externo são ignorados silenciosamente. A estrutura em árvore é válida, mas pode conter lacunas. Verificar entry.Quality distinguir full de partial.
1. Adicione a gramática a grammars/languages.manifest.
2. Gerar ligações:
go run ./cmd/ts2go -manifest grammars/languages.manifest -outdir ./grammars -package grammars -compact=trueIsso regenera grammars/embedded_grammars_gen.go, grammars/grammar_blobs/*.bine stubs de registro de idioma.
3. Adicione amostras de fumaça cmd/parity_report/main.go e grammars/parse_support_test.go.
4. Verificar:
go run ./cmd/parity_report
go test ./grammars/...gotreesitter reimplementa o tempo de execução do tree-sitter em Go puro:
- Analisador — LR(1) baseado em tabela com suporte GLR para gramáticas ambíguas
- Reutilização incremental — reutilização de subárvores baseada em cursor; regiões inalteradas ignoram totalmente a nova análise
- Alocador de arena — alocação de nós baseada em placas com contagem de referência, minimizando a pressão do GC
- Lexer DFA – gerado a partir de tabelas gramaticais via
ts2gocom pontes escritas à mão quando necessário - VM de scanner externo – interpretador de bytecode para digitalização específica de idioma (recuo Python, etc.)
- Mecanismo de consulta — Correspondência de padrão de expressão S com avaliação de predicado e cursores de streaming
- Marcador — destaque de sintaxe baseado em consulta com suporte incremental
- Etiquetas — definição de símbolo/extração de referência usando consultas de tags
As tabelas gramaticais são extraídas do tree-sitter upstream parser.c arquivos pelo ts2go ferramenta, serializada em blobs binários compactados e carregada lentamente no uso do primeiro idioma. Nenhum código C é executado no momento da análise.
Para evitar incorporar blobs no binário, construa com -tags grammar_blobs_external e definir GOTREESITTER_GRAMMAR_BLOB_DIR para um diretório contendo *.bin bolhas gramaticais. O modo blob externo usa mmap no Unix por padrão (GOTREESITTER_GRAMMAR_BLOB_MMAP=false para desativar).
Para enviar um binário incorporado menor com um conjunto de idiomas selecionado, construa com -tags grammar_set_core (conjunto básico inclui linguagens comuns como c, go, java, javascript, python, rust, typescriptetc.).
Para restringir idiomas registrados em tempo de execução (incorporado ou externo), defina:
GOTREESITTER_GRAMMAR_SET=go,json,pythonPara processos de longa duração, a memória cache gramatical é ajustável:
// Keep only the 8 most recently used decoded grammars in cache.
grammars.SetEmbeddedLanguageCacheLimit(8)
// Drop one language blob from cache (e.g. "rust.bin").
grammars.UnloadEmbeddedLanguage("rust.bin")
// Drop all decoded grammars from cache.
grammars.PurgeEmbeddedLanguageCache()Você também pode definir GOTREESITTER_GRAMMAR_CACHE_LIMIT no início do processo para aplicar um limite de cache sem alterações de código. Defina-o para 0 somente quando você explicitamente não deseja retenção (cada acesso gramatical será decodificado novamente).
A remoção ociosa pode ser habilitada com env vars:
GOTREESITTER_GRAMMAR_IDLE_TTL=5m
GOTREESITTER_GRAMMAR_IDLE_SWEEP=30sA compactação/internação do carregador é habilitada por padrão e ajustável via:
GOTREESITTER_GRAMMAR_COMPACT=true
GOTREESITTER_GRAMMAR_STRING_INTERN_LIMIT=200000
GOTREESITTER_GRAMMAR_TRANSITION_INTERN_LIMIT=20000O conjunto de testes inclui:
- Testes de fumaça — todas as 205 gramáticas analisam uma amostra sem travar ou produzir nós ERROR
- Instantâneos de correção – testes de expressão S dourados para 20 linguagens principais capturam regressões de analisador e gramática
- Validação de destaque – teste ponta a ponta que compilou consultas de destaque produzindo intervalos de destaque
- Testes de consulta — correspondência de padrões, predicados, cursores, correspondência baseada em campo
- Testes de analisador — nova análise incremental, recuperação de erros, resolução de ambiguidade GLR
- Confuso –
FuzzGoParseDoesNotPanicpara robustez do analisador
go test ./... -race -count=1Atual: v0.4.0 — 205 gramáticas, analisador estável, nova análise incremental, mecanismo de consulta, destaque, marcação.
Próximo:
- Reforço de paridade do mecanismo de consulta – semântica de negação de campo, comportamento de diretiva de metadados e paridade adicional de casos extremos com execução de consulta upstream de tree-sitter
- Mais scanners externos escritos à mão para resultados de alto valor
dfa-partialidiomas Parse() (*Tree, error)– retorna erros em vez de árvores nulas silenciosas- Teste de paridade automatizado em relação à saída do tree-sitter C
- Expansão difusa para cobrir mais idiomas e o mecanismo de consulta
COM
Fonte: theverge

