事情的起因呢,还是要从算法开始。我同学在GitHub上建了一个组织,拉了我们班一些开发手。第一个仓库是刷力扣的算法仓库,我只提交一些平常碰到的有意思的题(感兴趣的话 -> SumeruCondensation 我同学说他忘了怎么取的名字)。提交解法的时候想着得把测试案例写上方便其他人跑代码,但是我提交的那题是一个设计题,就是实现一个数据结构然后调用一堆方法来测试,那些方法的返回值总不能测一次就打印一行吧。于是就有了这个小工具,prints.ExpPrintln 能将传入的各个参数原来的表达式和值一起输出,再也不会一个一个比对测试的输入输出看花眼了。

受到B站大佬码农高天的开源项目objprinter的启发(有期视频讲了他的项目中打印源表达式功能的实现 -> BV1Fa411h7p7),他是通过 python 的反射拿到当前函数栈帧的上一个帧的信息,通过这个信息就能知道是在哪个文件的哪一行调用的函数,所以只要解析文件的那一行就能拿到参数原来的表达式。但是高天佬的项目是用 python 写的,而 python 是动态类型,函数默认返回 None,而且这个返回值是编译合法的。所以在同一个函数多层嵌套的情况下直接解析文件就会分不清是嵌套中的哪个函数在调用。所以在 python 中需要解析虚拟语法树(AST)。而在 go 语言中就没有这样的顾虑,没有返回值的函数不能做为函数的参数,所以咱就大胆的解析文件咯(其实想递归 AST 的,多帅哦,奈何吃力不讨好,还可能有性能方面的问题虽然最后写出来感觉性能也没多高)。

总之拿到上一个函数栈帧的信息,有文件名和函数调用的行数。把文件都读出来然后按行做一个 split,把调用的那一行之前的代码都丢了(说好的性能呢),从函数调用开始用一个栈做括号匹配,同时根据逗号和栈的长度来确定参数表达式。参数表达式到手再逐个打印参数。为了输出的可读性更好,我还做了像 code format 那样把输入和输出都对齐到一列,看起来是真滴舒服。