Parsing Tree-sitter
Parsear código fuente en arboles de sintaxis concretos usando Tree-sitter. Basado en bindings de go-tree-sitter.
Tree-sitter produce arboles de sintaxis que:
- 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 Soportados
| 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 Rapido
Parsear Código
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()) -- número de declaraciones de nivel superior
Consultar Arbol de Sintaxis
local code = [[
func hello() {}
func world() {}
]]
local tree = treesitter.parse("go", code)
local root = tree:root_node()
-- Encontrar todos los nombres de función
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"
Parsing
Parse Simple
Parsear código fuente en un arbol de sintaxis. Crea un parser temporal internamente.
local tree, err = treesitter.parse("go", code)
| Parámetro | Tipo | Descripción |
|---|---|---|
language |
string | Nombre o alias de lenguaje |
code |
string | Código fuente |
Devuelve: Tree, error
Parser Reutilizable
Crear un parser para parsing repetido o actualizaciones incrementales.
local parser = treesitter.parser()
parser:set_language("go")
local tree1 = parser:parse("package main")
-- Parse incremental con arbol anterior
local tree2 = parser:parse("package main\nfunc foo() {}", tree1)
parser:close()
Devuelve: Parser
Metodos de 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 |
Arboles de Sintaxis
Obtener Nodo Raiz
local tree = treesitter.parse("go", "package main")
local root = tree:root_node()
print(root:kind()) -- "source_file"
print(root:text()) -- "package main"
Metodos 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 |
Edicion Incremental
Actualizar el arbol cuando el código fuente cambia:
local code = "func main() { x := 1 }"
local tree = treesitter.parse("go", code)
-- Marcar edicion: cambio "1" a "100" en 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-parsear con arbol editado (mas rapido que parse completo)
local parser = treesitter.parser()
parser:set_language("go")
local new_tree = parser:parse("func main() { x := 100 }", tree)
Nodos
Los nodos representan elementos en el arbol de sintaxis.
Tipos de Nodo
local node = root:child(0)
-- Información de tipo
print(node:kind()) -- "package_clause"
print(node:type()) -- igual que kind()
print(node:is_named()) -- true para nodos significativos
print(node:grammar_name()) -- nombre de regla gramatical
Navegacion
-- Hijos
local child = node:child(0) -- por indice (basado en 0)
local named = node:named_child(0) -- solo hijos nombrados
local count = node:child_count()
local named_count = node:named_child_count()
-- Hermanos
local next = node:next_sibling()
local prev = node:prev_sibling()
local next_named = node:next_named_sibling()
local prev_named = node:prev_named_sibling()
-- Padre
local parent = node:parent()
-- Por nombre de campo
local name_node = func_decl:child_by_field_name("name")
local field = node:field_name_for_child(0)
Información de Posicion
-- Offsets de byte
local start = node:start_byte()
local end_ = node:end_byte()
-- Posiciones fila/columna (basado en 0)
local start_pt = node:start_point() -- {row = 0, column = 0}
local end_pt = node:end_point() -- {row = 0, column = 12}
-- Texto fuente
local text = node:text()
Deteccion de Errores
if root:has_error() then
-- El arbol contiene errores de sintaxis
end
if node:is_error() then
-- Este nodo específico es un error
end
if node:is_missing() then
-- El parser inserto esto para recuperarse del error
end
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 Consulta
local query, err = treesitter.query("go", [[
(function_declaration
name: (identifier) @func_name
parameters: (parameter_list) @params
)
]])
| Parámetro | Tipo | Descripción |
|---|---|---|
language |
string | Nombre de lenguaje |
pattern |
string | Patrón de consulta en sintaxis S-expression |
Devuelve: Query, error
Ejecutar Consulta
-- Obtener todas las capturas (aplanadas)
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 de captura
-- capture.node es el objeto Node
end
-- Obtener coincidencias (agrupadas por patrón)
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
Control de Consulta
-- Limitar alcance de consulta
query:set_byte_range(0, 1000)
query:set_point_range({row = 0, column = 0}, {row = 10, column = 0})
-- Limitar coincidencias
query:set_match_limit(100)
if query:did_exceed_match_limit() then
-- Existen mas coincidencias
end
-- Timeout (string de duración o nanosegundos)
query:set_timeout("500ms")
query:set_timeout(1000000000) -- 1 segundo en nanosegundos
-- Deshabilitar patrones/capturas
query:disable_pattern(0)
query:disable_capture("func_name")
Inspeccion de 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")
Cursor de Arbol
Recorrido eficiente sin crear objetos nodo en cada paso.
Recorrido Básico
local cursor = tree:walk()
-- Comenzar en 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
-- movido al siguiente hermano
end
cursor:goto_parent() -- volver al padre
cursor:close()
Metodos de 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 |
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_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 |
copy() |
Cursor |
Crear copia del cursor |
close() |
- | Liberar recursos |
Metadatos de Lenguaje
local lang = treesitter.language("go")
print(lang:versión()) -- versión ABI
print(lang:node_kind_count()) -- número de tipos de nodo
print(lang:field_count()) -- número de campos
-- Busqueda de tipo de nodo
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)
-- Busqueda de campo
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 |
Consulte Manejo de Errores para trabajar con errores.
Referencia de Sintaxis de Consultas
Las consultas de Tree-sitter usan patrones S-expression:
; Coincidir un tipo de nodo
(identifier)
; Coincidir con nombres de campo
(function_declaration name: (identifier))
; Capturar con @nombre
(function_declaration name: (identifier) @func_name)
; Multiples patrones
[
(function_declaration)
(method_declaration)
] @declaration
; Comodines
(_) ; cualquier nodo
(identifier)+ ; uno o mas
(identifier)* ; cero o mas
(identifier)? ; opcional
; Predicados
((identifier) @var
(#match? @var "^_")) ; coincidencia regex
Consulte Sintaxis de Consultas Tree-sitter para documentacion completa.