likes
comments
collection
share

python分析c语言生成语法树 - tree-sitter工具使用

作者站长头像
站长
· 阅读数 461

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供使用。

  1. 下载对应语言安装包的方法

pip install tree-sitter-c

直接pip install,更简单,使用方便

  1. 自行编译.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可以用作后续的解析,这种方式可以

创建代码解析器

根据上面下载语言安装包的方法,有两种方式

  1. 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
评论
请登录