Análise sintática com Tree-sitter
O módulo treesitter analisa código-fonte em árvores de sintaxe concretas com o Tree-sitter por meio dos bindings go-tree-sitter.
Esta página é uma referência de API com receitas parciais de parsing. As strings de código-fonte e os padrões de consulta são entradas da aplicação, e os exemplos que operam em nós pressupõem uma árvore ativa obtida por um parse anterior verificado. Parsers, árvores, consultas e cursores são recursos com proprietário: feche cada handle criado com sucesso quando sua última operação dependente terminar.
As árvores de sintaxe resultantes:
- Representam toda a estrutura do código-fonte
- São atualizadas incrementalmente conforme o código muda
- São robustas a erros de sintaxe (parsing parcial)
- Aceitam consultas baseadas em padrões com S-expressions
Carregamento
local treesitter = require("treesitter")
Linguagens Suportadas
| Linguagem | Aliases | Nó Raiz |
|---|---|---|
| Go | go, golang |
source_file |
| JavaScript | js, javascript |
program |
| TypeScript | ts, typescript |
program |
| TSX | tsx |
program |
| Python | python, py |
module |
| Lua | lua |
chunk |
| PHP | php |
program |
| C# | csharp, cs, c# |
compilation_unit |
| HTML | html, html5 |
document |
| Markdown | markdown, md |
document |
| SQL | sql |
- |
local langs = treesitter.supported_languages()
-- {go = true, javascript = true, python = true, ...}
Início rápido
Análise de código
local code = [[
func hello() {
return "Hello!"
}
]]
local tree, err = treesitter.parse("go", code)
if err then
return nil, err
end
local root, root_err = tree:root_node()
if root_err then
tree:close()
return nil, root_err
end
print(root:kind()) -- "source_file"
print(root:child_count()) -- number of top-level declarations
tree:close()
Consulta na Árvore de Sintaxe
local code = [[
func hello() {}
func world() {}
]]
local tree, parse_err = treesitter.parse("go", code)
if parse_err then
return nil, parse_err
end
local root, root_err = tree:root_node()
if root_err then
tree:close()
return nil, root_err
end
-- Find all function names
local query, query_err = treesitter.query("go", [[
(function_declaration name: (identifier) @func_name)
]])
if query_err then
tree:close()
return nil, query_err
end
local captures, captures_err = query:captures(root, code)
if captures_err then
query:close()
tree:close()
return nil, captures_err
end
for _, capture in ipairs(captures) do
print(capture.name, capture.text)
end
-- "func_name" "hello"
-- "func_name" "world"
query:close()
tree:close()
Análise sintática
Análise simples
Analisa o código-fonte com um parser interno temporário.
local tree, err = treesitter.parse("go", code)
if err then
return nil, err
end
-- Use the tree, then call tree:close().
| Parâmetro | Tipo | Descrição |
|---|---|---|
language |
string | Nome ou alias da linguagem |
code |
string | Código fonte |
Retorna: Tree, error
Parser reutilizável
Cria um parser reutilizável para parsing repetido ou atualizações incrementais.
local parser, parser_err = treesitter.parser()
if parser_err then
return nil, parser_err
end
local _, language_err = parser:set_language("go")
if language_err then
parser:close()
return nil, language_err
end
local tree1, first_err = parser:parse("package main")
if first_err then
parser:close()
return nil, first_err
end
-- Parse another source with the reusable parser. For an incremental update,
-- edit the old tree first as shown in the complete recipe below.
local tree2, second_err = parser:parse("package main\nfunc foo() {}")
if second_err then
tree1:close()
parser:close()
return nil, second_err
end
tree2:close()
tree1:close()
parser:close()
Retorna: Parser, error
Métodos do parser
| Método | Descrição |
|---|---|
set_language(lang) |
Definir linguagem do parser, retorna boolean, error |
get_language() |
Obter nome da linguagem atual |
parse(code, old_tree?) |
Analisa o código, opcionalmente com uma árvore anterior 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 |
As árvores criadas por um parser reutilizável e o próprio parser têm proprietários independentes; feche cada handle criado com sucesso. Os nós tomam emprestado o armazenamento da árvore e não devem ser usados depois que ela for fechada. Cursores também tomam uma árvore emprestada, portanto feche-os antes da árvore. As consultas mantêm recursos nativos separados e também exigem close(); feche uma consulta quando suas capturas ou correspondências não forem mais necessárias. A limpeza explícita é determinística, enquanto o armazenamento de recursos do processo é apenas um fallback para handles deixados abertos no encerramento do processo.
Árvores de Sintaxe
Obter Nó Raiz
local tree, err = treesitter.parse("go", "package main")
if err then
return nil, err
end
local root, root_err = tree:root_node()
if root_err then
tree:close()
return nil, root_err
end
print(root:kind()) -- "source_file"
local source_text, text_err = root:text()
if text_err then
tree:close()
return nil, text_err
end
print(source_text) -- "package main"
tree:close()
Métodos da árvore
| Método | Descrição |
|---|---|
root_node() |
Obter o nó raiz da árvore |
root_node_with_offset(bytes, point) |
Obter raiz com offset aplicado |
language() |
Obter o objeto de linguagem da árvore |
copy() |
Criar uma cópia profunda da árvore |
walk() |
Criar cursor para travessia |
edit(edit_table) |
Aplicar uma edição incremental |
changed_ranges(other_tree) |
Obter ranges que mudaram |
included_ranges() |
Obter os ranges incluídos durante o parsing |
dot_graph() |
Obter representação em grafo DOT |
close() |
Liberar os recursos da árvore |
Edição Incremental
Aplica uma edição antes de refazer o parse do código-fonte alterado:
local code = "func main() { x := 1 }"
local tree, parse_err = treesitter.parse("go", code)
if parse_err then
return nil, parse_err
end
-- Mark edit: changed "1" to "100" at byte 19
local _, edit_err = 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
})
if edit_err then
tree:close()
return nil, edit_err
end
-- Re-parse with edited tree (faster than full parse)
local parser, parser_err = treesitter.parser()
if parser_err then
tree:close()
return nil, parser_err
end
local _, language_err = parser:set_language("go")
if language_err then
parser:close()
tree:close()
return nil, language_err
end
local new_tree, new_tree_err = parser:parse("func main() { x := 100 }", tree)
if new_tree_err then
parser:close()
tree:close()
return nil, new_tree_err
end
new_tree:close()
parser:close()
tree:close()
Nós
Nós representam elementos da árvore de sintaxe. Nos exemplos isolados abaixo, root, node e func_decl são nós selecionados pela aplicação e tomados emprestados de uma árvore ainda aberta.
Tipos de Nó
local node = root:child(0)
-- Type information
print(node:kind()) -- "package_clause"
print(node:type()) -- same as kind()
print(node:is_named()) -- true for significant nodes
print(node:grammar_name()) -- grammar rule name
Navegação
-- Children
local child = node:child(0) -- by index (0-based)
local named = node:named_child(0) -- named children only
local count = node:child_count()
local named_count = node:named_child_count()
-- Siblings
local next = node:next_sibling()
local prev = node:prev_sibling()
local next_named = node:next_named_sibling()
local prev_named = node:prev_named_sibling()
-- Parent
local parent = node:parent()
-- By field name
local name_node = func_decl:child_by_field_name("name")
local field = node:field_name_for_child(0)
Informações de Posição
-- Byte offsets
local start = node:start_byte()
local end_ = node:end_byte()
-- Row/column positions (0-based)
local start_pt = node:start_point() -- {row = 0, column = 0}
local end_pt = node:end_point() -- {row = 0, column = 12}
-- Source text
local source_text, err = node:text()
if err then
return nil, err
end
Detecção de Erro
if root:has_error() then
-- Tree contains syntax errors
end
if node:is_error() then
-- This specific node is an error
end
if node:is_missing() then
-- Parser inserted this to recover from error
end
if node:is_extra() then
-- No e um "extra" (ex.: um comentario) nao exigido pela gramatica
end
Outros metodos de no: descendant_count() e named_descendant_for_point_range(start_pt, end_pt).
S-Expression
local sexp = node:to_sexp()
-- "(source_file (package_clause (package_identifier)))"
Consultas
As consultas Tree-sitter encontram padrões da árvore de sintaxe escritos como S-expressions.
Criar consulta
local query, err = treesitter.query("go", [[
(function_declaration
name: (identifier) @func_name
parameters: (parameter_list) @params
)
]])
if err then
return nil, err
end
-- The owner calls query:close() after the final query operation.
| Parâmetro | Tipo | Descrição |
|---|---|---|
language |
string | Nome da linguagem |
pattern |
string | Padrão de query em sintaxe S-expression |
Retorna: Query, error
Executar consulta
-- Get all captures (flattened)
local captures, captures_err = query:captures(root, source_code)
if captures_err then
query:close()
tree:close()
return nil, captures_err
end
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
-- Get matches (grouped by pattern)
local matches, matches_err = query:matches(root, source_code)
if matches_err then
query:close()
tree:close()
return nil, matches_err
end
for _, match in ipairs(matches) do
print(match.id, match.pattern)
for _, capture in ipairs(match.captures) do
local captured_text, text_err = capture.node:text()
if text_err then
query:close()
tree:close()
return nil, text_err
end
print(capture.name, captured_text)
end
end
query:close()
tree:close()
Passar userdata do tipo incorreto no lugar de um Node do Tree-sitter retorna nil, error; passar um valor primitivo ou uma tabela gera um erro de argumento Lua antes dessa verificação. Aqui, root, source_code e query devem vir de uma árvore ainda aberta e de uma consulta criada com sucesso. O exemplo usa o handle proprietário tree para fechar ambos os recursos antes de retornar.
Controle da consulta
-- Limit query scope
query:set_byte_range(0, 1000)
query:set_point_range({row = 0, column = 0}, {row = 10, column = 0})
-- Limit matches
query:set_match_limit(100)
if query:did_exceed_match_limit() then
-- More matches exist
end
-- Timeout (string duration or nanoseconds)
query:set_timeout("500ms")
query:set_timeout(1000000000) -- 1 second in nanoseconds
-- Disable patterns/captures
query:disable_pattern(0)
query:disable_capture("func_name")
Inspeção da consulta
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")
Outros metodos de inspecao: string_count(), start_byte_for_pattern(i), end_byte_for_pattern(i), get_match_limit(), get_timeout(), is_pattern_rooted(i), is_pattern_non_local(i), is_pattern_guaranteed(i), capture_quantifier(pattern, capture), set_max_start_depth(n), get_property_predicates(i), get_property_settings(i), get_text_predicates(i).
Cursor de Arvore
Um cursor percorre uma árvore sem criar um objeto de nó a cada passo.
Travessia Básica
local cursor, err = tree:walk()
if err then
return nil, err
end
-- Start at root
print(cursor:current_node():kind()) -- "source_file"
print(cursor:current_depth()) -- 0
-- Navigate
if cursor:goto_first_child() then
print(cursor:current_node():kind())
print(cursor:current_depth()) -- 1
end
if cursor:goto_next_sibling() then
-- moved to next sibling
end
cursor:goto_parent() -- back to parent
cursor:close()
Métodos do Cursor
| Método | Retorna | Descrição |
|---|---|---|
current_node() |
Node |
Nó na posição do cursor |
current_depth() |
integer |
Profundidade (0 = raiz) |
current_field_name() |
string? |
Nome do campo se houver |
current_field_id() |
integer |
ID do campo (0 se nenhum) |
current_descendant_index() |
integer |
Índice de descendente do nó atual |
goto_parent() |
boolean |
Mover para pai |
goto_first_child() |
boolean |
Mover para primeiro filho |
goto_last_child() |
boolean |
Mover para o último filho |
goto_next_sibling() |
boolean |
Mover para o próximo irmão |
goto_previous_sibling() |
boolean |
Mover para o irmão anterior |
goto_descendant(index) |
- | Mover para descendente por índice |
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 o cursor para o nó |
reset_to(cursor) |
- | Resetar o cursor para a posição de outro cursor |
copy() |
Cursor |
Criar uma cópia do cursor |
close() |
- | Liberar recursos |
Metadados de Linguagem
local lang, err = treesitter.language("go")
if err then
return nil, err
end
print(lang:version()) -- ABI version
print(lang:node_kind_count()) -- number of node types
print(lang:field_count()) -- number of fields
print(lang:parse_state_count()) -- number of parse states
-- Node kind lookup
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)
-- Field lookup
local field_name = lang:field_name_for_id(1)
local field_id = lang:field_id_for_name("name")
Erros
| Condição | Tipo | Retentável |
|---|---|---|
| Linguagem não suportada | errors.INVALID |
não |
| Linguagem sem binding | errors.INVALID |
não |
| Padrão de query inválido | errors.INVALID |
não |
| Posições inválidas | errors.INVALID |
não |
| Parse falhou | errors.INTERNAL |
não |
| Contexto de execução ausente | errors.INTERNAL |
não |
Fechar um parser, uma árvore, uma consulta ou um cursor que já esteja fechado é seguro. Chamar qualquer outro método em um handle fechado gera um erro de argumento Lua.
Veja Tratamento de Erros para trabalhar com erros.
Referência da sintaxe de consultas
Consultas Tree-sitter usam padrões S-expression:
; Match a node type
(identifier)
; Match with field names
(function_declaration name: (identifier))
; Capture with @name
(function_declaration name: (identifier) @func_name)
; Multiple patterns
[
(function_declaration)
(method_declaration)
] @declaration
; Wildcards
(_) ; any node
(identifier)+ ; one or more
(identifier)* ; zero or more
(identifier)? ; optional
; Predicates
((identifier) @var
(#match? @var "^_")) ; regex match
Veja Tree-sitter Query Syntax para documentação completa.