这是系列博文的第三篇,第一篇在此:golang深入源代码之一:AST的遍历,第二篇在此:golang深入源代码系列之二:反向调用关系的生成。
问题描述
第一篇讲了怎么遍历一个项目的源代码,第二篇讲了怎么构建内部某个package的某个函数package.XYZ()
的反向调用关系(一颗多叉树)。那么问题来了,如果我们想在package.XYZ()
里面增加一些信息,该信息只能从最外层依次传递进来,中间可能经历若干个函数。手动写这个代码很烦躁,中间的函数都可能需要增加parameter,并传递给下一级。反向查找调用关系更是繁琐。本篇就来讲怎么自动生成这个代码。
一个例子
依然使用第二篇文章的例子,如下为测试项目的文件结构:
– /exmaple/test3.go
– /exmaple/test4.go
– /example/inner/itest1.go
我们希望所有调用context.WithCancel
的地方能把上下文串联起来,效果如下:
能自动变成
并且在最外层的main.main()
里生成原始Context:ctx := context.Background()
。中间有调用关系的函数都传递这个ctx。当然这个例子不符合实际场景,只用来说明这个思路。
自动生成代码
高光时刻到了,程序员终于可以让机器自己写代码了。当然golang还另有方法从模版来自动生成代码,此处不表。
首先根据这个例子,我们把涉及到的函数分为三类:例如test4a
这种的「关键函数」,中间的「传递函数」,main.main()
这种的「源头函数」。类似第一篇,定义了这样的结构:
「关键函数」
针对「关键函数」,要做两件事。一是把调用context.WithCancel
的实参nil
替换为ctx
(实际是字符串类型)。二是在行参列表中第一处插入一个ctx context.Context
(不要忘记逗号)。在找到「关键函数」后,用如下代码做第一件事:
很简单,就是把AST中对应结构中的这个Args[0]的字面量替换了。如下代码做第二件事:
其实就是把AST中的函数结构体的Type里面的Params中新插入一个*ast.Field
结构。不用担心,最后逗号会自动补上。
「传递函数」
针对「传递函数」,一是在函数体的调用关系处实参插一个ctx
,二也是在行参列表中第一处插入一个ctx context.Context
。在找到「传递函数」后,用如下代码做第一件事:
值得注意的是,如果源代码中以前就用nil
来传递了Context,此处需要替换为ctx
。
「源头函数」
针对「源头函数」,一也是在函数体的调用关系处实参插一个ctx
,而是在函数体最初生成原始Context,代码如下:
自动格式化
现在已经能自动生成相应的代码了,但是还需要自动import “context”,当package里面没有的时候。
go fmt && goimports
当AST修改完以后,重新写回源文件并覆盖:
用exec
包进行命令行处理,包括go fmt格式化和goimports自动处理包管理。具体如下:
执行代码见: https://github.com/baixiaoustc/go_code_analysis/blob/master/third_post_test.go中的TestAutoGenContext
。