# "Tree-sitter Parsing" _Path: en/lua/text/treesitter_ > "Parse source code into concrete syntax trees using Tree-sitter. Based on go-tree-sitter bindings." ## Table of Contents - Tree-sitter Parsing ## Content # Tree-sitter Parsing The `treesitter` module parses source code into concrete syntax trees with [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) through the [go-tree-sitter](https://github.com/tree-sitter/go-tree-sitter) bindings. This page is an API reference with partial parsing recipes. Source strings and query patterns are application input, and node-level snippets assume a live tree from an earlier checked parse. Parsers, trees, queries, and cursors are owned resources: close every successfully created handle when its last dependent operation is complete. The resulting syntax trees: - Represent the full structure of source code - Update incrementally as code changes - Are robust to syntax errors (partial parsing) - Support pattern-based queries using S-expressions ## Loading ```lua local treesitter = require("treesitter") ``` The `treesitter` module is optional and is present only in builds that include the `treesitter` build tag. Official Wippy binaries include it. Source builds can use `make build-wippy` or `go build -tags treesitter`; without the tag, `require("treesitter")` is unavailable. ## Supported Languages | Language | Aliases | Root Node | |----------|---------|-----------| | 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` | - | ```lua local langs = treesitter.supported_languages() -- {go = true, javascript = true, python = true, ...} ``` ### Parse Code ```lua 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() ``` ### Query Syntax Tree ```lua 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() ``` ### Simple Parse Parse source code with a temporary internal parser. ```lua local tree, err = treesitter.parse("go", code) if err then return nil, err end -- Use the tree, then call tree:close(). ``` | Parameter | Type | Description | |-----------|------|-------------| | `language` | string | Language name or alias | | `code` | string | Source code | **Returns:** `Tree, error` ### Reusable Parser Create a reusable parser for repeated parsing or incremental updates. ```lua 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() ``` **Returns:** `Parser, error` ### Parser Methods | Method | Description | |--------|-------------| | `set_language(lang)` | Set parser language, returns `boolean, error` | | `get_language()` | Get current language name | | `parse(code, old_tree?)` | Parse code, optionally with old tree for incremental parsing | | `set_timeout(duration)` | Set parse timeout (string like `"1s"` or nanoseconds) | | `set_ranges(ranges)` | Set byte ranges to parse | | `reset()` | Reset parser state | | `close()` | Release parser resources | Trees created by a reusable parser and the parser itself are independently owned; close each successful handle. Nodes borrow their tree's storage and must not be used after that tree is closed. Cursors borrow a tree as well, so close them before the tree. Queries own separate native resources and also require `close()`; close a query after its captures or matches are no longer needed. Explicit cleanup is deterministic, while the process resource store is only a fallback for handles left open at process teardown. ### Get Root Node ```lua 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() ``` ### Tree Methods | Method | Description | |--------|-------------| | `root_node()` | Get root node of tree | | `root_node_with_offset(bytes, point)` | Get root with offset applied | | `language()` | Get tree's language object | | `copy()` | Create deep copy of tree | | `walk()` | Create cursor for traversal | | `edit(edit_table)` | Apply incremental edit | | `changed_ranges(other_tree)` | Get ranges that changed | | `included_ranges()` | Get ranges included during parsing | | `dot_graph()` | Get DOT graph representation | | `close()` | Release tree resources | ### Incremental Editing Apply an edit before reparsing changed source code: ```lua 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() ``` ## Nodes Nodes represent elements in the syntax tree. In the isolated snippets below, `root`, `node`, and `func_decl` are application-selected nodes borrowed from a still-open tree. ### Node Types ```lua 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 ``` ### Navigation ```lua -- 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) ``` ### Position Information ```lua -- 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 ``` ### Error Detection ```lua 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 -- Node is an "extra" (e.g. a comment) not required by the grammar end ``` Other node methods: `descendant_count()` and `named_descendant_for_point_range(start_pt, end_pt)`. ### S-Expression ```lua local sexp = node:to_sexp() -- "(source_file (package_clause (package_identifier)))" ``` ## Queries Tree-sitter queries match syntax-tree patterns written as S-expressions. ### Create Query ```lua 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. ``` | Parameter | Type | Description | |-----------|------|-------------| | `language` | string | Language name | | `pattern` | string | Query pattern in S-expression syntax | **Returns:** `Query, error` ### Execute Query ```lua -- 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) -- actual text print(capture.index) -- capture index -- capture.node is the Node object 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() ``` Passing userdata of the wrong type instead of a Tree-sitter `Node` returns `nil, error`; passing a primitive or table raises a Lua argument error before that check. `root`, `source_code`, and `query` here must come from a still-open tree and a successfully created query. The snippet uses the owning `tree` handle to close both resources before it returns. ### Query Control ```lua -- 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") ``` ### Query Inspection ```lua 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") ``` Further inspection methods: `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)`. ## Tree Cursor A tree cursor traverses a tree without creating a node object at every step. ### Basic Traversal ```lua 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() ``` ### Cursor Methods | Method | Returns | Description | |--------|---------|-------------| | `current_node()` | `Node` | Node at cursor position | | `current_depth()` | `integer` | Depth (0 = root) | | `current_field_name()` | `string?` | Field name if any | | `current_field_id()` | `integer` | Field ID (0 if none) | | `current_descendant_index()` | `integer` | Descendant index of current node | | `goto_parent()` | `boolean` | Move to parent | | `goto_first_child()` | `boolean` | Move to first child | | `goto_last_child()` | `boolean` | Move to last child | | `goto_next_sibling()` | `boolean` | Move to next sibling | | `goto_previous_sibling()` | `boolean` | Move to previous sibling | | `goto_descendant(index)` | - | Move to descendant by index | | `goto_first_child_for_byte(n)` | `integer?` | Move to child containing byte | | `goto_first_child_for_point(pt)` | `integer?` | Move to child containing point | | `reset(node)` | - | Reset cursor to node | | `reset_to(cursor)` | - | Reset cursor to another cursor's position | | `copy()` | `Cursor` | Create copy of cursor | | `close()` | - | Release resources | ## Language Metadata ```lua 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") ``` ## Errors | Condition | Kind | Retryable | |-----------|------|-----------| | Language not supported | `errors.INVALID` | no | | Language has no binding | `errors.INVALID` | no | | Invalid query pattern | `errors.INVALID` | no | | Invalid positions | `errors.INVALID` | no | | Parse failed | `errors.INTERNAL` | no | | No execution context | `errors.INTERNAL` | no | Closing an already closed parser, tree, query, or cursor is safe. Calling any other method on a closed handle raises a Lua argument error. See [Error Handling](lua/core/errors.md) for working with errors. ## Query Syntax Reference Tree-sitter queries use S-expression patterns: ``` ; 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 ``` See [Tree-sitter Query Syntax](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax) for complete documentation. ## Navigation Previous: "Template Engine" (lua/text/template) Next: "Security & Access Control" (lua/security/security)