Pular para o conteúdo

Parsing Tree-sitter

Parse código fonte em arvores de sintaxe concretas usando Tree-sitter. Baseado nos bindings go-tree-sitter.

Tree-sitter produz arvores de sintaxe que:

  • Representam a estrutura completa do código fonte
  • Atualizam incrementalmente conforme o código muda
  • Sao robustas a erros de sintaxe (parsing parcial)
  • Suportam queries baseadas em padroes usando S-expressions
local treesitter = require("treesitter")
O módulo treesitter é opcional — está presente apenas em builds que incluem a tag de build `treesitter`. Os binários oficiais do Wippy o incluem; para compilar a partir do código-fonte, use `make build-wippy` ou `go build -tags treesitter`. Sem a tag, `require("treesitter")` fica indisponível.
LinguagemAliasesNo Raiz
Gogo, golangsource_file
JavaScriptjs, javascriptprogram
TypeScriptts, typescriptprogram
TSXtsxprogram
Pythonpython, pymodule
Lualuachunk
PHPphpprogram
C#csharp, cs, c#compilation_unit
HTMLhtml, html5document
Markdownmarkdown, mddocument
SQLsql-
local langs = treesitter.supported_languages()
-- {go = true, javascript = true, python = true, ...}
local code = [[
func hello() {
return "Hello!"
}
]]
local tree, err = treesitter.parse("go", code)
if err then
return nil, err
end
local root = tree:root_node()
print(root:kind()) -- "source_file"
print(root:child_count()) -- numero de declaracoes top-level
local code = [[
func hello() {}
func world() {}
]]
local tree = treesitter.parse("go", code)
local root = tree:root_node()
-- Encontrar todos os nomes de função
local query = treesitter.query("go", [[
(function_declaration name: (identifier) @func_name)
]])
local captures = query:captures(root, code)
for _, capture in ipairs(captures) do
print(capture.name, capture.text)
end
-- "func_name" "hello"
-- "func_name" "world"

Parse de código fonte em uma arvore de sintaxe. Cria um parser temporario internamente.

local tree, err = treesitter.parse("go", code)
ParâmetroTipoDescrição
languagestringNome ou alias da linguagem
codestringCódigo fonte

Retorna: Tree, error

Criar um parser para parsing repetido ou atualizacoes incrementais.

local parser = treesitter.parser()
parser:set_language("go")
local tree1 = parser:parse("package main")
-- Parse incremental com arvore antiga
local tree2 = parser:parse("package main\nfunc foo() {}", tree1)
parser:close()

Retorna: Parser

MétodoDescrição
set_language(lang)Definir linguagem do parser, retorna boolean, error
get_language()Obter nome da linguagem atual
parse(code, old_tree?)Parse de código, opcionalmente com arvore antiga para parsing incremental
set_timeout(duration)Definir timeout de parse (string como "1s" ou nanossegundos)
set_ranges(ranges)Definir ranges de bytes para parse
reset()Resetar estado do parser
close()Liberar recursos do parser
local tree = treesitter.parse("go", "package main")
local root = tree:root_node()
print(root:kind()) -- "source_file"
print(root:text()) -- "package main"
MétodoDescrição
root_node()Obter no raiz da arvore
root_node_with_offset(bytes, point)Obter raiz com offset aplicado
language()Obter objeto de linguagem da arvore
copy()Criar copia profunda da arvore
walk()Criar cursor para travessia
edit(edit_table)Aplicar edicao incremental
changed_ranges(other_tree)Obter ranges que mudaram
included_ranges()Obter ranges incluidos durante parsing
dot_graph()Obter representação em grafo DOT
close()Liberar recursos da arvore

Atualizar a arvore quando o código fonte muda:

local code = "func main() { x := 1 }"
local tree = treesitter.parse("go", code)
-- Marcar edicao: mudou "1" para "100" no byte 19
tree:edit({
start_byte = 19,
old_end_byte = 20,
new_end_byte = 22,
start_row = 0,
start_column = 19,
old_end_row = 0,
old_end_column = 20,
new_end_row = 0,
new_end_column = 22
})
-- Re-parse com arvore editada (mais rapido que parse completo)
local parser = treesitter.parser()
parser:set_language("go")
local new_tree = parser:parse("func main() { x := 100 }", tree)

Nos representam elementos na arvore de sintaxe.

local node = root:child(0)
-- Informação de tipo
print(node:kind()) -- "package_clause"
print(node:type()) -- igual a kind()
print(node:is_named()) -- true para nos significativos
print(node:grammar_name()) -- nome da regra gramatical
-- Filhos
local child = node:child(0) -- por indice (base 0)
local named = node:named_child(0) -- apenas filhos nomeados
local count = node:child_count()
local named_count = node:named_child_count()
-- Irmaos
local next = node:next_sibling()
local prev = node:prev_sibling()
local next_named = node:next_named_sibling()
local prev_named = node:prev_named_sibling()
-- Pai
local parent = node:parent()
-- Por nome de campo
local name_node = func_decl:child_by_field_name("name")
local field = node:field_name_for_child(0)
-- Offsets em bytes
local start = node:start_byte()
local end_ = node:end_byte()
-- Posicoes linha/coluna (base 0)
local start_pt = node:start_point() -- {row = 0, column = 0}
local end_pt = node:end_point() -- {row = 0, column = 12}
-- Texto fonte
local text = node:text()
if root:has_error() then
-- Arvore contem erros de sintaxe
end
if node:is_error() then
-- Este no especifico e um erro
end
if node:is_missing() then
-- Parser inseriu este no para recuperar de erro
end
local sexp = node:to_sexp()
-- "(source_file (package_clause (package_identifier)))"

Pattern matching usando a linguagem de query do Tree-sitter (S-expressions).

local query, err = treesitter.query("go", [[
(function_declaration
name: (identifier) @func_name
parameters: (parameter_list) @params
)
]])
ParâmetroTipoDescrição
languagestringNome da linguagem
patternstringPadrão de query em sintaxe S-expression

Retorna: Query, error

-- Obter todas as capturas (achatadas)
local captures = query:captures(root, source_code)
for _, capture in ipairs(captures) do
print(capture.name) -- "@func_name"
print(capture.text) -- texto real
print(capture.index) -- indice da captura
-- capture.node e o objeto Node
end
-- Obter matches (agrupados por padrão)
local matches = query:matches(root, source_code)
for _, match in ipairs(matches) do
print(match.id, match.pattern)
for _, capture in ipairs(match.captures) do
print(capture.name, capture.node:text())
end
end
-- Limitar escopo da query
query:set_byte_range(0, 1000)
query:set_point_range({row = 0, column = 0}, {row = 10, column = 0})
-- Limitar matches
query:set_match_limit(100)
if query:did_exceed_match_limit() then
-- Existem mais matches
end
-- Timeout (string de duração ou nanossegundos)
query:set_timeout("500ms")
query:set_timeout(1000000000) -- 1 segundo em nanossegundos
-- Desabilitar padroes/capturas
query:disable_pattern(0)
query:disable_capture("func_name")
local pattern_count = query:pattern_count()
local capture_count = query:capture_count()
local name = query:capture_name_for_id(0)
local id = query:capture_index_for_name("func_name")

Travessia eficiente sem criar objetos de no a cada passo.

local cursor = tree:walk()
-- Comecar na raiz
print(cursor:current_node():kind()) -- "source_file"
print(cursor:current_depth()) -- 0
-- Navegar
if cursor:goto_first_child() then
print(cursor:current_node():kind())
print(cursor:current_depth()) -- 1
end
if cursor:goto_next_sibling() then
-- moveu para proximo irmao
end
cursor:goto_parent() -- voltar ao pai
cursor:close()
MétodoRetornaDescrição
current_node()NodeNo na posicao do cursor
current_depth()integerProfundidade (0 = raiz)
current_field_name()string?Nome do campo se houver
current_field_id()integerID do campo (0 se nenhum)
current_descendant_index()integerIndice de descendente do no atual
goto_parent()booleanMover para pai
goto_first_child()booleanMover para primeiro filho
goto_last_child()booleanMover para ultimo filho
goto_next_sibling()booleanMover para proximo irmao
goto_previous_sibling()booleanMover para irmao anterior
goto_descendant(index)-Mover para descendente por indice
goto_first_child_for_byte(n)integer?Mover para filho contendo byte
goto_first_child_for_point(pt)integer?Mover para filho contendo ponto
reset(node)-Resetar cursor para no
reset_to(cursor)-Resetar cursor para posicao de outro cursor
copy()CursorCriar copia do cursor
close()-Liberar recursos
local lang = treesitter.language("go")
print(lang:version()) -- versão ABI
print(lang:node_kind_count()) -- numero de tipos de no
print(lang:field_count()) -- numero de campos
print(lang:parse_state_count()) -- numero de estados de parse
-- Lookup de tipo de no
local kind = lang:node_kind_for_id(1)
local id = lang:id_for_node_kind("identifier", true)
local is_named = lang:node_kind_is_named(1)
-- Lookup de campo
local field_name = lang:field_name_for_id(1)
local field_id = lang:field_id_for_name("name")
CondiçãoTipoRetentável
Linguagem não suportadaerrors.INVALIDnão
Linguagem sem bindingerrors.INVALIDnão
Padrão de query inválidoerrors.INVALIDnão
Posicoes invalidaserrors.INVALIDnão
Parse falhouerrors.INTERNALnão

Veja Error Handling para trabalhar com erros.

Queries Tree-sitter usam padroes S-expression:

; Match um tipo de no
(identifier)
; Match com nomes de campo
(function_declaration name: (identifier))
; Captura com @nome
(function_declaration name: (identifier) @func_name)
; Multiplos padroes
[
(function_declaration)
(method_declaration)
] @declaration
; Wildcards
(_) ; qualquer no
(identifier)+ ; um ou mais
(identifier)* ; zero ou mais
(identifier)? ; opcional
; Predicados
((identifier) @var
(#match? @var "^_")) ; regex match

Veja Tree-sitter Query Syntax para documentação completa.