python环境解析任意编程语言 tree-sitter使用方法(2)

背景

上一篇文章(即python环境解析任意编程语言 tree-sitter使用方法(1))介绍了tree-sitter功能特点、安装流程以及可能发生的安装错误。

同时还介绍了一些使用tree-sitter科研相关的论文(GraphCodeBert、UnixCoder、CodeT5、TreeBert以及SynCoBert【没开源】),顺带着给出了一个tree-sitter用于Tokenizer分词器样例(来自GraphCodeBert)。

这一篇文章将会介绍如何利用tree-sitter,编写query,定位语法树节点。以节省书写dfs+回溯等复杂python代码的时间。

我想这有可能对以下研究提供些许帮助:

  • 分析代码功能【深度学习分析局部节点特征】
  • 提取代码节点类型【代码token表征】
  • 获取特定标识符
  • 检索数据流【可定位到代码中不同的位置】
  • 研究控制依赖【if、while、dowhile和switch语法树节点解析】

当然,这个query还是需要自己来写的(就像写sql)一样。这里给出了一些简单的样例作为参考。

准备工作

  1. 合适python环境下安装tree-sitter

没有安装看我写的这里(推荐),或者看官网py-tree-sitter这里(也推荐)

  1. 打开官网给出的playground

建议打开,因为官网的playground做的语法树可视化界面直观,方便代码语法树调试。

个人尽管科研一年多的时间,也用过不少语法树工具:

这些语法树解析工具都很好,但是以上大多工具无法解析语法错误代码【难以接受】。更何况这些工具基本都没有在线的可视化调试工具

所以建议打开playground,在CodeQuery以及Tree的可视化界面中调试代码!

准备写在playground中写query

官方语法介绍在这里,先搬运过来:Pattern Matching with Queries

query格式

query是一个S-表达式,该表达式由一对嵌套结构的阔号组成,阔号内包含两部分:本节点类型 和 0个或多个子节点的S-表达式。

(节点类型 
	(子节点类型_1)
	字段: (字节点类型_2)
)

节点类型在Tree中以蓝色突出显示,后面紧跟[行, 列]-[行, 列]起止位置。

比如样例代码中:

// 来自b站
int mian{
  piantf("hell world");
  remake O;
}

根节点是translation_unit节点类型,可写作:

( translation_unit ) @1

样例代码行"piantf("hell world");",如果选择实参列表,在Tree中对应的是一个arguments: argument_list【前面是字段,后面网页标出蓝色的argument_list才是节点类型】,可写作:

( argument_list ) @2

其中@1 @2是节点选择表达式【@自定义符号】,写在成对的括号外面。
网页中,选择的节点在Code界面高亮显示。
后面在写代码时,会通过自定义符号获取节点。

带有字段的query

前两个例子只选择了某个节点,但是没有区分其上下文,可能选择意想不到的部分。

int mian{
	long main;
	remake O;
}

现在,要选择函数定义语句的int的节点,可以发现只写( primitive_type )是不行的,会选择main的long,所以,加入字段以后,可以发现选中了正确的本部分:

( function_definition 
	type:(primitive_type) @3
)

// 或者写仔细点
( function_definition 
	type: (primitive_type) @3
	declarator: (identifier)
)

// 字段是有先后顺序的,不可以颠倒type和decalarator顺序。
// 官方界面可以很直观地显示出错误
// 这是错误的表达式:
( function_definition 
	declarator: (identifier)
	type: (primitive_type) @3
)

像这样,写得越细致,越能避免选择错误的节点。同时也避免了手写dfs+回溯代码的困难场景。

注意:选择哪个节点,就在哪个节点括号对后加@自定义符号,如果:

( function_definition 
	type:(primitive_type) @3
) @4

就会选择代码声明以及原始类型两种节点。

匿名节点

该匿名节点指的是代码中,像+, -, *, /, [, ],=等等符号,tree-sitter没有标志出他们的具体名称。【可能有些语法树节点就蕴含了语法构成吧,所以作者认为这些符号很琐碎,没有取名字】

a++;

双引号包含他们即可:

( update_expression 
	"++" @6
)

通配符节点

类似正则表达式,在节点类型阔号后使用+或*

  • + 表示一个或多个
  • * 表示0个或多个

节点如果没有限制,可以通配符_

( _ ) @all

还有其他使用方法,官方介绍得很仔细,我估计个人还要继续做相关介绍😅。

在Python代码中获取节点

cpp代码:

#include <stdio.h>

int cmp(a, b){
	return a > b? 1: 0;
}

int main(){
	int arr[] = {5, 2, 1, 3, 0};
	char s[] = {'h', 'e', 'l', 'l', 'o'};
	int n = 5;
	int i, j, tmp;
	
	for(i = 0; i < n; i++){
		for(j = n - i - 1; j > 0; i--) {
			if(cmp(arr[j - 1], arr[j])){
				tmp = arr[j-1];
				arr[j-1] = arr[j];
				arr[j] = tmp;
			}
		}
	}
	printf("%s\n", s);
	return 0;
    希望有人能看到这里,🤗这是代码中错误的片段。
}

需求:

  1. 获取函数名称
  2. 获取数组初始化列表
  3. 获取函数调用语句
  4. 获取赋值语句的右侧
  5. 获取错误(error)
(function_declarator declarator: (identifier)@1 )
(initializer_list) @2
( call_expression ) @3
(assignment_expression  right:(_) @4)
(ERROR) @error

需要使用的python代码语句:

# 需要使用的语句
from tree_sitter import Language, Parser

# 实例化
CPP_LANGUAGE = Language('build/my-languages.so', 'cpp')
# 书写query
query_text = '( translation_unit ) @1'
# 构建query
query = CPP_LANGUAGE.query(cpp_query_text)

# 获取节点【root_node来源与Parser解析的代码文本】
root_node = xxx_parser.parse(bytes(code_snippet, 'utf8)).root_node
# capture: list[Node, str]
capture = query.captures(root_node)
for node, alias in capture: # node为代码节点,alias为自定义符号
    print(node.type, alias)

所以可以这么写:

from tree_sitter import Language, Parser

# 声明CPP代码解析器
CPP_LANGUAGE = Language('build/my-languages.so', 'cpp')
cpp_parser = Parser()
cpp_parser.set_language(CPP_LANGUAGE)

cpp_code_snippet = '''
// 把上面cpp代码赋值粘贴到这里
'''

# 定义query
cpp_query_text = '''
(function_declarator declarator: (identifier)@1 )
(initializer_list) @2
( call_expression ) @3
(assignment_expression  right:(_) @4)
(ERROR) @error
'''
query = CPP_LANGUAGE.query(cpp_query_text)

# 获取具体语法树
tree = cpp_parser.parse(bytes(cpp_code_snippet, "utf8"))
root_node = tree.root_node

# 获取节点
# capture: list[Node, str]
capture = query.captures(root_node)
for node, alias in capture:
    print(node.type, alias)

后序

希望对大家有帮助。还有很多节点这里没做介绍,官网写得很详细。
希望大家多多支持🤗

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值