Tree Sitter

基本介绍

语法树

这里介绍下语法树及其变种, 以如下的源代码为例:

def hello(name): pass

语法树的生成依赖 1. 句法分析得到基本的词元; 2. 根据一些特定的语法规则, 形成树形结构.

AST vs CST

特性CSTAST
信息完整性保留所有 token省略语法糖
可还原源码可以(因为信息完整)不能完全还原
节点数量较多较少
典型用途代码格式化、语法高亮编译、静态分析
代表工具tree-sitterPython ast 模块

tree-sitter

from tree_sitter import Language, Parser, Query, QueryCursor
import tree_sitter_python as tspython

def print_tree(node, indent=0):
    """缩进打印语法树的所有节点。

    Parameters
    ----------
    node : tree_sitter.Node
        起始节点。
    indent : int
        当前缩进层级。
    """
    # 叶子节点显示源码文本
    text = ""
    if node.child_count == 0:
        text = f'  "{node.text.decode()}"'

    # Named 节点直接显示 type,Anonymous 节点用引号包裹
    name = node.type if node.is_named else f'"{node.type}"'
    # name = node.type

    print(f"{'  ' * indent}{name}{text}")

    for child in node.children:
        print_tree(child, indent + 1)

code = """
def add(a, b):
    return a + b

def neg(a):
    '''negation operation'''
    return -a

def sub(a, b):
    # negative
    b_neg = neg(b)
    return add(a, b_neg)
"""

代码解析: Parser


# 1. 从语言包创建 Language 对象
PY_LANGUAGE = Language(tspython.language())

# 2. 创建 Parser 并指定语言
parser = Parser(PY_LANGUAGE)

# 3. 解析代码(必须传入 bytes,不能是 str)
tree = parser.parse(bytes(code, encoding="utf-8"))

# 4. 查看语法树的 S-expression 表示
print(str(tree.root_node))
print("=" * 100)
print_tree(tree.rootnode)
new_code = code + """
def mul(a, b):
    return a * b
"""

new_tree = parser.parse(bytes(new_code, encoding="utf-8"), old_tree=tree)
print_tree(new_tree.root_node)
# 查看变化范围
for r in tree.changed_ranges(new_tree):
    print(f"变化范围: byte {r.start_byte}..{r.end_byte}")

Node: 节点

类别属性/方法类型说明
身份type属性 (str)节点类型名,如 "function_definition"
is_named属性 (bool)是否为 Named 节点(对应 AST 节点)
kind_id属性 (int)节点类型的数值 ID
is_error属性 (bool)该节点自身是否存在语法错误
has_error属性 (bool)该节点的子树中是否存在语法错误
文本text属性 (bytes)节点对应的源码,返回 bytes 类型
位置start_point / end_point属性 (Point)起始/结束位置,Point(row, column),0-indexed
start_byte / end_byte属性 (int)起始/结束位置的字节偏移量
byte_range属性 (range)字节范围,可直接用于切片
子节点children属性 (iterable)所有子节点(Named + Anonymous)
named_children属性 (iterable)仅 Named 子节点(遍历它相当于遍历 AST)
child_count属性 (int)子节点总数
named_child_count属性 (int)Named 子节点总数
child(index)方法返回第 index 个子节点
child_by_field_name(name)方法按字段名获取子节点,无则返回 None
导航parent属性父节点,根节点为 None
next_sibling / prev_sibling属性下一个/上一个兄弟节点(含 Anonymous)
next_named_sibling属性下一个 Named 兄弟节点
prev_named_sibling属性上一个 Named 兄弟节点
查找descendant_for_point_range(start, end)方法返回覆盖指定行列范围的最小子节点
遍历walk()方法返回 TreeCursor,用于高效遍历子树

S-表达式查询

语法类型写法示例说明
基础匹配(function_definition)匹配所有类型为 function_definition 的节点
(identifier)匹配所有类型为 identifier 的节点
字段约束(function_definition name: (identifier))匹配 function_definition 节点,且其 name 字段必须是 identifier 类型
捕获(function_definition name: (identifier) @func_name)@name 标记节点,执行后可通过名称提取
通配符(_)匹配任意 Named 节点(不关心具体类型)
(assignment left: (identifier) @var right: (_) @value)匹配赋值语句,右侧可以是任意类型
匿名节点"def"匹配关键字 def(字面量,Anonymous 节点)
"("匹配左括号
"return"匹配 return 关键字
交替[...]匹配方括号内任一模式
[ (function_definition name: (identifier) @name) (class_definition name: (identifier) @name) ]同时匹配函数名和类名,捕获到同一个 @name

谓词(Predicates)

谓词 (Predicates) 是加在查询模式内部的附加条件, 用 (#predicate) 的形式写在模式的末尾. 它的作用是进一步筛选已匹配到的节点.

核心写法: 谓词需要写在圆括号 () 内, 紧跟在你想要约束的查询模式后面. 一个模式可以有多个谓词.

谓词写法功能示例
#eq? @capture "string"判断捕获节点的文本是否等于某字符串(#eq? @name "main")
#eq? @cap1 @cap2判断两个捕获节点的文本是否相等(#eq? @left @right)
#match? @capture "regex"判断捕获节点的文本是否匹配正则表达式(#match? @name "^test")
#not-eq? / #not-match?上述谓词的否定形式(#not-eq? @name "main")
#any-xxx?量化捕获(如 +, *)中任意一个满足条件即通过(#any-eq? @comments "#")
pattern = """
(
  (function_definition
    name: (identifier) @func_name
    parameters: (parameters
      (identifier) @param_name)
    body: (block
      (expression_statement
        (string) @docstring)?)
  )
  (#match? @param_name "^a$")
)
"""
query = Query(
    PY_LANGUAGE, pattern
)

cursor = QueryCursor(query)
captures = cursor.captures(root)

for tag, nodes in captures.items():
    for node in nodes:
        print(f"[{tag:10s}]\t[{node.type}]\t{node.text.decode():30s}")

附录

S-expression

S-expression 起源于 Lisp 语言. 现在常作为一种用括号表示树形结构的极简语法:

(+ 1 2)          ; 表达 1 + 2
(* (+ 2 3) 4)    ; 表达 (2 + 3) * 4
S-expressionPython 等价概念
(+ 1 2)["+", 1, 2]
(print "hi")["print", "hi"]

参考文献

  1. tree-sitter [Doc]
  2. py-tree-sitter [Doc]