Análisis con Tree-sitter
El módulo treesitter analiza código fuente y produce árboles de sintaxis concretos con Tree-sitter mediante los bindings de go-tree-sitter.
Esta página es una referencia de API con recetas parciales de análisis. Las cadenas de origen y los patrones de consulta son entradas de la aplicación, y los fragmentos a nivel de nodo presuponen un árbol vivo procedente de un análisis anterior comprobado. Los parsers, árboles, consultas y cursores son recursos bajo propiedad: cierra cada handle creado correctamente cuando finalice su última operación dependiente.
Los árboles de sintaxis resultantes:
- Representan la estructura completa del código fuente
- Se actualizan incrementalmente cuando el código cambia
- Son robustos ante errores de sintaxis (parsing parcial)
- Soportan consultas basadas en patrones usando S-expressions
Carga
local treesitter = require("treesitter")
Lenguajes compatibles
| Lenguaje | Aliases | Nodo 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, ...}
Inicio rápido
Analizar 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()
Consultar el árbol de sintaxis
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álisis
Análisis simple
Analiza código fuente y produce un árbol de sintaxis. Crea un parser temporal internamente.
local tree, err = treesitter.parse("go", code)
if err then
return nil, err
end
-- Use the tree, then call tree:close().
| Parámetro | Tipo | Descripción |
|---|---|---|
language |
string | Nombre o alias de lenguaje |
code |
string | Código fuente |
Devuelve: Tree, error
Parser reutilizable
Crea un parser para análisis repetidos o actualizaciones incrementales.
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()
Devuelve: Parser, error
Métodos del parser
| Método | Descripción |
|---|---|
set_language(lang) |
Establecer lenguaje del parser, devuelve boolean, error |
get_language() |
Obtener nombre de lenguaje actual |
parse(code, old_tree?) |
Parsear código, opcionalmente con arbol anterior para parsing incremental |
set_timeout(duration) |
Establecer timeout de parsing (string como "1s" o nanosegundos) |
set_ranges(ranges) |
Establecer rangos de bytes a parsear |
reset() |
Reiniciar estado del parser |
close() |
Liberar recursos del parser |
Los árboles creados por un parser reutilizable y el propio parser tienen propietarios independientes; cierra cada handle creado correctamente. Los nodos toman prestado el almacenamiento de su árbol y no deben usarse después de cerrarlo. Los cursores también toman prestado un árbol, así que ciérralos antes que el árbol. Las consultas poseen recursos nativos separados y también requieren close(); cierra una consulta cuando ya no necesites sus capturas o coincidencias. La limpieza explícita es determinista, mientras que el almacén de recursos del proceso solo es un respaldo para los handles que queden abiertos al terminar el proceso.
Árboles de sintaxis
Obtener el nodo raíz
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 de Tree
| Método | Descripción |
|---|---|
root_node() |
Obtener nodo raiz del arbol |
root_node_with_offset(bytes, point) |
Obtener raiz con offset aplicado |
language() |
Obtener objeto de lenguaje del arbol |
copy() |
Crear copia profunda del arbol |
walk() |
Crear cursor para recorrido |
edit(edit_table) |
Aplicar edicion incremental |
changed_ranges(other_tree) |
Obtener rangos que cambiaron |
included_ranges() |
Obtener rangos incluidos durante parsing |
dot_graph() |
Obtener representacion de grafo DOT |
close() |
Liberar recursos del arbol |
Edición incremental
Actualizar el arbol cuando el código fuente cambia:
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()
Nodos
Los nodos representan elementos del árbol de sintaxis. En los fragmentos aislados siguientes, root, node y func_decl son nodos seleccionados por la aplicación y prestados por un árbol que sigue abierto.
Tipos de nodo
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
Navegación
-- 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)
Información de posición
-- 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
Detección de errores
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
-- El nodo es un "extra" (por ejemplo, un comentario) no requerido por la gramatica
end
Otros metodos de nodo: descendant_count() y named_descendant_for_point_range(start_pt, end_pt).
S-Expression
local sexp = node:to_sexp()
-- "(source_file (package_clause (package_identifier)))"
Consultas
Coincidencia de patrones usando el lenguaje de consultas de Tree-sitter (S-expressions).
Crear una 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 | Descripción |
|---|---|---|
language |
string | Nombre de lenguaje |
pattern |
string | Patrón de consulta en sintaxis S-expression |
Devuelve: Query, error
Ejecutar una 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 de captura
-- capture.node es el 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()
Pasar userdata del tipo incorrecto en lugar de un Node de Tree-sitter devuelve nil, error; pasar un valor primitivo o una tabla genera un error de argumento Lua antes de esa comprobación. Aquí, root, source_code y query deben proceder de un árbol aún abierto y de una consulta creada correctamente. El fragmento usa el handle propietario tree para cerrar ambos recursos antes de devolver.
Control de consultas
-- 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")
Inspección de consultas
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")
Metodos adicionales de inspeccion: 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 Arbol
Recorrido eficiente sin crear objetos nodo en cada paso.
Recorrido Básico
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 del cursor
| Método | Devuelve | Descripción |
|---|---|---|
current_node() |
Node |
Nodo en posicion del cursor |
current_depth() |
integer |
Profundidad (0 = raiz) |
current_field_name() |
string? |
Nombre de campo si hay |
current_field_id() |
integer |
ID de campo (0 si no hay) |
current_descendant_index() |
integer |
Indice de descendiente del nodo actual |
goto_parent() |
boolean |
Mover al padre |
goto_first_child() |
boolean |
Mover al primer hijo |
goto_last_child() |
boolean |
Mover al ultimo hijo |
goto_next_sibling() |
boolean |
Mover al siguiente hermano |
goto_previous_sibling() |
boolean |
Mover al hermano anterior |
goto_descendant(index) |
- | Mover al descendiente por indice |
goto_first_child_for_byte(n) |
integer? |
Mover al hijo que contiene byte |
goto_first_child_for_point(pt) |
integer? |
Mover al hijo que contiene punto |
reset(node) |
- | Reiniciar cursor al nodo |
reset_to(cursor) |
- | Reiniciar cursor a la posicion de otro cursor |
copy() |
Cursor |
Crear copia del cursor |
close() |
- | Liberar recursos |
Metadatos del lenguaje
local lang, err = treesitter.language("go")
if err then
return nil, err
end
print(lang:version()) -- versión ABI
print(lang:node_kind_count()) -- número de tipos de nodo
print(lang:field_count()) -- número de campos
print(lang:parse_state_count()) -- número de estados de análisis
-- 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")
Errores
| Condición | Tipo | Reintentable |
|---|---|---|
| Lenguaje no soportado | errors.INVALID |
no |
| Lenguaje sin binding | errors.INVALID |
no |
| Patrón de consulta invalido | errors.INVALID |
no |
| Posiciones invalidas | errors.INVALID |
no |
| Parse fallido | errors.INTERNAL |
no |
| Sin contexto de ejecución | errors.INTERNAL |
no |
Cerrar un parser, árbol, consulta o cursor ya cerrado es seguro. Llamar a cualquier otro método de un handle cerrado genera un error de argumento Lua.
Consulta Manejo de errores para trabajar con errores.
Referencia de sintaxis de consultas
Las consultas de Tree-sitter usan patrones 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
Consulta Sintaxis de consultas de Tree-sitter para ver la documentación completa.