Tree-sitter 解析
Tree-sitter 解析
Section titled “Tree-sitter 解析”使用 Tree-sitter 将源代码解析为具体语法树。基于 go-tree-sitter 绑定。
Tree-sitter 生成的语法树具有以下特点:
- 表示源代码的完整结构
- 随着代码更改增量更新
- 对语法错误具有鲁棒性(部分解析)
- 支持使用 S-expression 进行模式查询
local treesitter = require("treesitter")| 语言 | 别名 | 根节点 |
|---|---|---|
| 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, ...}local code = [[func hello() { return "Hello!"}]]
local tree, err = treesitter.parse("go", code)if err then return nil, errend
local root = tree:root_node()print(root:kind()) -- "source_file"print(root:child_count()) -- 顶级声明的数量local code = [[func hello() {}func world() {}]]
local tree = treesitter.parse("go", code)local root = tree:root_node()
-- 查找所有函数名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"将源代码解析为语法树。内部创建临时解析器。
local tree, err = treesitter.parse("go", code)| 参数 | 类型 | 描述 |
|---|---|---|
language | string | 语言名称或别名 |
code | string | 源代码 |
返回值: Tree, error
可重用解析器
Section titled “可重用解析器”创建解析器用于重复解析或增量更新。
local parser = treesitter.parser()parser:set_language("go")
local tree1 = parser:parse("package main")
-- 使用旧树进行增量解析local tree2 = parser:parse("package main\nfunc foo() {}", tree1)
parser:close()返回值: Parser
| 方法 | 描述 |
|---|---|
set_language(lang) | 设置解析器语言,返回 boolean, error |
get_language() | 获取当前语言名称 |
parse(code, old_tree?) | 解析代码,可选使用旧树进行增量解析 |
set_timeout(duration) | 设置解析超时(如 "1s" 或纳秒) |
set_ranges(ranges) | 设置要解析的字节范围 |
reset() | 重置解析器状态 |
close() | 释放解析器资源 |
local tree = treesitter.parse("go", "package main")local root = tree:root_node()
print(root:kind()) -- "source_file"print(root:text()) -- "package main"Tree 方法
Section titled “Tree 方法”| 方法 | 描述 |
|---|---|
root_node() | 获取树的根节点 |
root_node_with_offset(bytes, point) | 获取应用偏移量的根节点 |
language() | 获取树的语言对象 |
copy() | 创建树的深拷贝 |
walk() | 创建用于遍历的游标 |
edit(edit_table) | 应用增量编辑 |
changed_ranges(other_tree) | 获取更改的范围 |
included_ranges() | 获取解析期间包含的范围 |
dot_graph() | 获取 DOT 图表示 |
close() | 释放树资源 |
当源代码更改时更新树:
local code = "func main() { x := 1 }"local tree = treesitter.parse("go", code)
-- 标记编辑:在字节 19 处将 "1" 更改为 "100"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})
-- 使用编辑后的树重新解析(比完整解析更快)local parser = treesitter.parser()parser:set_language("go")local new_tree = parser:parse("func main() { x := 100 }", tree)节点表示语法树中的元素。
local node = root:child(0)
-- 类型信息print(node:kind()) -- "package_clause"print(node:type()) -- 与 kind() 相同print(node:is_named()) -- 对于重要节点为 trueprint(node:grammar_name()) -- 语法规则名称-- 子节点local child = node:child(0) -- 按索引(从 0 开始)local named = node:named_child(0) -- 仅命名子节点local count = node:child_count()local named_count = node:named_child_count()
-- 兄弟节点local next = node:next_sibling()local prev = node:prev_sibling()local next_named = node:next_named_sibling()local prev_named = node:prev_named_sibling()
-- 父节点local parent = node:parent()
-- 按字段名local name_node = func_decl:child_by_field_name("name")local field = node:field_name_for_child(0)-- 字节偏移local start = node:start_byte()local end_ = node:end_byte()
-- 行/列位置(从 0 开始)local start_pt = node:start_point() -- {row = 0, column = 0}local end_pt = node:end_point() -- {row = 0, column = 12}
-- 源文本local text = node:text()if root:has_error() then -- 树包含语法错误end
if node:is_error() then -- 此特定节点是错误end
if node:is_missing() then -- 解析器插入此节点以从错误中恢复endS-Expression
Section titled “S-Expression”local sexp = node:to_sexp()-- "(source_file (package_clause (package_identifier)))"使用 Tree-sitter 的查询语言(S-expression)进行模式匹配。
local query, err = treesitter.query("go", [[ (function_declaration name: (identifier) @func_name parameters: (parameter_list) @params )]])| 参数 | 类型 | 描述 |
|---|---|---|
language | string | 语言名称 |
pattern | string | S-expression 语法的查询模式 |
返回值: Query, error
-- 获取所有捕获(扁平化)local captures = query:captures(root, source_code)for _, capture in ipairs(captures) do print(capture.name) -- "@func_name" print(capture.text) -- 实际文本 print(capture.index) -- 捕获索引 -- capture.node 是 Node 对象end
-- 获取匹配(按模式分组)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()) endend-- 限制查询范围query:set_byte_range(0, 1000)query:set_point_range({row = 0, column = 0}, {row = 10, column = 0})
-- 限制匹配数query:set_match_limit(100)if query:did_exceed_match_limit() then -- 存在更多匹配end
-- 超时(字符串持续时间或纳秒)query:set_timeout("500ms")query:set_timeout(1000000000) -- 1 秒(纳秒)
-- 禁用模式/捕获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")无需在每一步创建节点对象的高效遍历。
local cursor = tree:walk()
-- 从根开始print(cursor:current_node():kind()) -- "source_file"print(cursor:current_depth()) -- 0
-- 导航if cursor:goto_first_child() then print(cursor:current_node():kind()) print(cursor:current_depth()) -- 1end
if cursor:goto_next_sibling() then -- 移动到下一个兄弟节点end
cursor:goto_parent() -- 返回父节点
cursor:close()| 方法 | 返回值 | 描述 |
|---|---|---|
current_node() | Node | 游标位置的节点 |
current_depth() | integer | 深度(0 = 根) |
current_field_name() | string? | 字段名(如果有) |
current_field_id() | integer | 字段 ID(无则为 0) |
current_descendant_index() | integer | 当前节点的后代索引 |
goto_parent() | boolean | 移动到父节点 |
goto_first_child() | boolean | 移动到第一个子节点 |
goto_last_child() | boolean | 移动到最后一个子节点 |
goto_next_sibling() | boolean | 移动到下一个兄弟节点 |
goto_previous_sibling() | boolean | 移动到上一个兄弟节点 |
goto_descendant(index) | - | 按索引移动到后代节点 |
goto_first_child_for_byte(n) | integer? | 移动到包含该字节的子节点 |
goto_first_child_for_point(pt) | integer? | 移动到包含该点的子节点 |
reset(node) | - | 将游标重置到节点 |
reset_to(cursor) | - | 将游标重置到另一个游标的位置 |
copy() | Cursor | 创建游标副本 |
close() | - | 释放资源 |
local lang = treesitter.language("go")
print(lang:version()) -- ABI 版本print(lang:node_kind_count()) -- 节点类型数量print(lang:field_count()) -- 字段数量print(lang:parse_state_count()) -- 解析状态数量
-- 节点类型查找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)
-- 字段查找local field_name = lang:field_name_for_id(1)local field_id = lang:field_id_for_name("name")| 条件 | 类型 | 可重试 |
|---|---|---|
| 不支持的语言 | errors.INVALID | 否 |
| 语言没有绑定 | errors.INVALID | 否 |
| 无效的查询模式 | errors.INVALID | 否 |
| 无效的位置 | errors.INVALID | 否 |
| 解析失败 | errors.INTERNAL | 否 |
参见 错误处理 了解如何处理错误。
查询语法参考
Section titled “查询语法参考”Tree-sitter 查询使用 S-expression 模式:
; 匹配节点类型(identifier)
; 使用字段名匹配(function_declaration name: (identifier))
; 使用 @name 捕获(function_declaration name: (identifier) @func_name)
; 多个模式[ (function_declaration) (method_declaration)] @declaration
; 通配符(_) ; 任意节点(identifier)+ ; 一个或多个(identifier)* ; 零个或多个(identifier)? ; 可选
; 谓词((identifier) @var (#match? @var "^_")) ; 正则匹配完整文档请参见 Tree-sitter 查询语法。