python分析c语言生成语法树 - tree-sitter工具使用
python 进行c语言代码的分析生成语法树主要有两个库可供使用:pycparser、tree-sitter库。
相比较来说:
pycparser
pycparser 工具能生成严谨且完整的语法树,但是缺点也很明显,
- 针对比较复杂的代码及类型,容易出错抛异常无法解析成功,需要进行一部分修改定制才可。一些类型例如size_t类型和宏,pycparser无法识别会抛异常。
- 需要进行gcc预处理编译,没有经过预编译的代码pycparser无法识别。
tree-sitter 工具
tree-sitter 是一款功能更强大的代码语法解析工具,支持多种语言,相比较pycparser来说,它的好处:
- 无需编译即可进行语法树解析
- 强大的容错性,即使存在语法错误,也能够解析出语法树,有问题的节点会变成ERROR节点。
因此,对于大型的项目,采用tree-sitter工具更适合。
tree-sitter
相关文档
官方文档地址:tree-sitter.github.io/tree-sitter… 官方python库的文档地址:pypi.org/project/tre…
官方提供了一个小工具,可以通过这个url访问,在上面输入代码,即可实时生成分析结果,便于进行代码解析 tree-sitter.github.io/tree-sitter…
在python中使用tree-sitter
下载tree-sitter库
pip install tree-sitter
下载对应语言的解析包
tree-sitter提供了基础的解析库函数,除了这个之外,想要解析哪个语言,还要下载对应语言的解析工具,或者自行编译一个.so供使用。
- 下载对应语言安装包的方法
pip install tree-sitter-c
直接pip install,更简单,使用方便
- 自行编译.so:
这个链接里可以找到各个语言的工具 github.com/orgs/tree-s…
我用到的是c语言的:github.com/tree-sitter…
下载下来后,在当前目录创建一个verdor目录,把代码解压到vendor目录里,然后写一个build_language.py
from tree_sitter import Language
Language.build_library(
"build/c-language.so",
['vendor/tree-sitter-c']
)
运行后,会在build目录生成一个.so, 这个.so可以用作后续的解析,这种方式可以
创建代码解析器
根据上面下载语言安装包的方法,有两种方式
- pip install 方法
from typing import Generator
import tree_sitter_c as tsc
from tree_sitter import Language, Parser, Node, Tree
parser = Parser()
c_language = Language(tsc.language(), "c")
parser.set_language(c_language)
2、自己编译的.so的方式
from typing import Generator
import tree_sitter_c as tsc
from tree_sitter import Language, Parser, Node, Tree
parser = Parser()
c_language = Language("build/c-language.so", "c")
parser.set_language(c_language)
解析语法树
with open(source_file, 'rb') as reader:
code = reader.read()
tree = parser.parse(code)
tree_node = tree.root_node
遍历所有语法树节点
tree-sitter库封装的tree.walk()方法遍历语法树节点比较复杂,官方提供了一个示例方法,可以遍历完整的语法树节点 github.com/tree-sitter…
代码如下
def traverse_tree(tree: Tree) -> Generator[Node, None, None]:
cursor = tree.walk()
visited_children = False
while True:
if not visited_children:
yield cursor.node
if not cursor.goto_first_child():
visited_children = True
elif cursor.goto_next_sibling():
visited_children = False
elif not cursor.goto_parent():
break
node_names = map(lambda node: node.type, traverse_tree(tree))
访问节点
语法树的每一个节点都是tree_sitter.Node对象,该对象有下面几种常用的方法:
node_typed = node.type # 获取节点类型
child_node = node.child_by_field_name() # 根据字段名获取子节点, 如果没有找到返回None
named_children = node.named_children # 获取有名的子节点
all_children = node.children # 获取所有子节点
node_text = node.text # 获取节点的代码文本, bytes对象, 获取字符串需要用str(node.text, "utf8") 进行转换
start_point = node.start_point # 节点的起始代码行、列
end_point = node.end_point # 节点的结束代码行、列
根据这些方法,可以通过节点类型、文本等,进行我们所需要的操作了。
搜索节点
tree-sitter提供了通过表达式搜索语法树节点的功能,表达式写法参考官方文档:tree-sitter.github.io/tree-sitter… 中Query Syntax 章节的描述
例如:想要查找所有的结构体定义节点。
query = c_language.query("""
(struct_specifier
name: (type_identifier) @name
body: (field_declaration_list) @body) @struct
""")
# 会将匹配的结果进行分组
matches = query.matches(tree_node)
if matches:
for match in matches:
pass
# 返回的是匹配到的节点,需要根据
captures = query.captures(tree_node)
if captures:
for capture, in captures:
pass
想要测试写的表达式是否正确,也可以在上面提到的playground官方工具进行测试,输入源码和Query表达式,会自动将匹配到的代码高亮,可以清楚的看到表达式是否匹配成功以及匹配到的结果是什么。
代码树结构
详细的代码树构成,一个类型的节点下面可能有哪些子节点,可以通过git clone的语言安装包源码,在src目录找到一个node-types.json文件,这个文件声明了语法树节点的定义,可以参考这个和playground来进行分析代码。
例如:
结构体声明节点struct_specifier在node-types.json的声明:
{
"type": "struct_specifier",
"named": true,
"fields": {
"body": {
"multiple": false,
"required": false,
"types": [
{
"type": "field_declaration_list",
"named": true
}
]
},
"name": {
"multiple": false,
"required": false,
"types": [
{
"type": "type_identifier",
"named": true
}
]
}
},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "attribute_specifier",
"named": true
},
{
"type": "ms_declspec_modifier",
"named": true
}
]
}
}
"named": true 代表它是一个有名的节点 "fields" 列出了这个节点下的所有节点,child_by_field_name方法能拿到哪些field,就是从这里看的,struct下有body、name两种字段名的节点。
遇到的一些坑
有一些语法,tree-sitter也是无法解析的,例如 extension 关键字,可以在解析前把代码文本中的这个关键字替换为即可。
转载自:https://juejin.cn/post/7372911159350116403