odvcencio/gotreesitter: tempo de execução do tree-sitter Pure Go

PUBLICIDADE

odvcencio/gotreesitter: tempo de execução do tree-sitter Pure Go

Tempo de execução do tree-sitter Pure-Go — sem CGo, sem conjunto de ferramentas C, pronto para WASM.

go get github.com/odvcencio/gotreesitter

Implementa 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=arm64 do Linux, Windows sem MSYS2)
  • Os pipelines de CI precisam de um conjunto de ferramentas C em cada imagem de construção
  • go install falha para usuários finais sem gcc
  • 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?) exigem source []byte avaliar. Passagem nil desativa 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:

QualidadeSignificado
fullFonte de token ou DFA com scanner externo — fidelidade total
partialDFA parcial – scanner externo ausente, a árvore pode ter lacunas silenciosas
noneNã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êncians/opB/opalocações/operações
BenchmarkCTreeSitterGoParseFull2.058.0006006
BenchmarkCTreeSitterGoParseIncrementalSingleByteEdit124.1006487
BenchmarkCTreeSitterGoParseIncrementalNoEdit121.1006006
BenchmarkGoParseFull1.330.00010.8422.495
BenchmarkGoParseIncrementalSingleByteEdit1.3813619
BenchmarkGoParseIncrementalNoEdit8,6300

Resumo:

Carga de trabalhogotreesitterLigação CGoRazão
Análise completa1.330 μs2.058 μs~1,5x mais rápido
Incremental (edição de byte único)1,38 μs124 μs~90x mais rápido
Incremental (reparo autônomo)8,6ns121 μ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 parcialnorg (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


RecursoStatus
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! diretivasanalisado 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=true

Isso 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,python

Para 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=30s

A 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=20000

O 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
  • ConfusoFuzzGoParseDoesNotPanic para robustez do analisador
go test ./... -race -count=1

Atual: 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-partial idiomas
  • 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

Mais recentes

PUBLICIDADE

WP Twitter Auto Publish Powered By : XYZScripts.com