

编译原理有啥用之Go语言懒人工具
source link: https://studygolang.com/articles/16142?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

动机
笔者在使用Go语言进行开发的过程中发现一些机械化重构代码的需求,而IDE(Goland)没有相应的功能,导致每次都需要手动写,非常不便。举两个例子:
例子1
type EsNginxLogInfo struct { RemoteAddr string RemoteUser string TimeLocal string Host string Method string Path string Query url.Values Status string BytesSent string HttpReferer string HttpUserAgent string HttpXForwardedFor string UpstreamResponseTime string RequestTime string Time time.Time }
为了转json给前端以及从前端接收参数,需要增加json和url以及require的tag,然后10分钟过去了,得到:
type EsNginxLogInfo struct { RemoteAddr string `json:"remote_addr" url:"remote_addr" require:"true"` RemoteUser string `json:"remote_user" url:"remote_user" require:"true"` TimeLocal string `json:"time_local" url:"time_local" require:"true"` Host string `json:"host" url:"host" require:"true"` Method string `json:"method" url:"method" require:"true"` Path string `json:"path" url:"path" require:"true"` Query url.Values `json:"query" url:"query" require:"true"` Status string `json:"status" url:"status" require:"true"` BytesSent string `json:"bytes_sent" url:"bytes_sent" require:"true"` HttpReferer string `json:"http_referer" url:"http_referer" require:"true"` HttpUserAgent string `json:"http_user_agent" url:"http_user_agent" require:"true"` HttpXForwardedFor string `json:"http_x_forwarded_for" url:"http_x_forwarded_for" require:"true"` UpstreamResponseTime string `json:"upstream_response_time" url:"upstream_response_time" require:"true"` RequestTime string `json:"request_time" url:"request_time" require:"true"` Time time.Time `json:"time" url:"time" require:"true"` }
例子2
随着业务的扩展,函数在改动时参数可能越来越多,为避免顺序问题,希望重构过多参数的函数为单参数。例如重构前:
func fun1() { structA := 0 structB := 0 structC := 0 structD := 0 structE := 0 structF := 0 structG := 0 fun2(structA,structB,structC,structD,structE,structF,structG) } func fun2(structA *StructA, structB *StructB, structC *StructC, structD *StructD, structE *StructE,structF *StructF, structG *StructG) { }
重构后:
// 新建的input结构 type CombinedInputs struct { StructA *StructA StructB *StructB StructC *StructC StructD *StructD StructE *StructE StructF *StructF StructG *StructG } func fun1() { structA := 0 structB := 0 structC := 0 structD := 0 structE := 0 structF := 0 structG := 0 // 打包 combinedInputs := &CombinedInputs{ StructA: structA, StructB: structB, StructC: structC, StructD: structD, StructE: structE, StructF: structF, StructG: structG, } fun2(combinedInputs) } func fun2(combinedInputs *CombinedInputs) { // 解包 structA := combinedInputs.StructA structB := combinedInputs.StructB structC := combinedInputs.StructC structD := combinedInputs.StructD structE := combinedInputs.StructE structF := combinedInputs.StructF structG := combinedInputs.StructG }
可见要额外写很多代码。类似的需求还有:
- MarshalJSON和UnMarshalJSON代码;
- ORM的update代码;
- 转结构变量代码;
- struct转json(写api文档用)。
解决方案
解决方案就是做一个即开即用的网页,输入待重构的代码,点击按钮实现代码的转换。关键在于核心算法如何实现?
第一次尝试
一开始尝试用正则表达式去匹配,来获得struct字段定义的各个组成部分,于是写出了这个恶心的正则:
/^\s*?([^\s]+)\s+([^\s]+)\s*?([^\s]*?)(`.+?`\s*)?(\/\/.*?)?$/
结果发现,这还只能匹配简单的情况,如果遇到输入为:
FoodId ***[/* test*/]map[a./*test*/C]/*test*/[/*test*/]*[]package.Int `pk:"true"` // test
它就gg了。
第二次尝试
最后用上了编译原理的知识:根据Golang struct的词法、语法定义,用递归下降编写词法分析器、语法分析器,产生抽象语法树,然后就可以精确地得到任意一项,从而happy地实现代码重构了。其中,摸索着写出的语法定义的EBNF为:
<NodeCode> ::= {TokenNewLine} {<NodeStructDefine>{TokenNewLine}}[<NodeMultiParamtersDecl>{TokenNewLine}] TokenEof <NodeStructDefine> ::= TokenKwType <NodeStructName> TokenKwStruct TokenLeftBrace {<NodeStructFieldDefine>} TokenRightBrace (TokenNewLine|TokenEOF) <NodeStructName> ::= TokenIdentifier <NodeStructFieldDefine> ::= <NodeStructFieldName> <NodeStructFieldType> [<NodeStructFieldTag>] TokenNewLine <NodeStructFieldName> ::= TokenIdentifier <NodeStructFieldType> ::= <TypeIdentifier> <TypeIdentifier> ::= <Slice>|<Pointer>|<SimpleIdentifier>|<Map> <Slice> ::= "[""]"<TypeIdentifier> <Pointer> ::= TokenStar<TypeIdentifier> <SimpleIdentifier> ::= [<PackageName>.]TokenIdentifier <PackageName> ::= TokenIdentifier <Map> ::= TokenKwMap"["<MapKey>"]"<TypeIdentifier> <MapKey> ::= <Pointer>|<SimpleIdentifier> // map key不能是function ,map, slice,可以是指针 <NodeStructFieldTag> ::= TokenReverseQuote {<NodeStructFieldTagKey>:<NodeStructFieldTagValue>} TokenReverseQuote <NodeStructFieldTagKey> ::= TokenIdentifier <NodeStructFieldTagValue> ::= TokenDoubleQuoteString <NodeMultiParamtersDecl> ::= <NodeStructFieldName><NodeStructFieldType>{,<NodeStructFieldName><NodeStructFieldType>}
其中, NodeMultiParamtersDecl
是为了实现多参数重构功能而额外定义的文法符号,形式为 structA *StructA, structB *StructB, ..., structG *StructG
最后顺便实现了下词法着色和代码对齐,得到这个效果:

另外改进了下生成的update函数,避免单行过长的情况(为增加可读性,不能在单词中间断开。。仿佛回到了当年刷leetCode的时光):

工具地址
Recommend
-
61
上次介绍了ExchangeOWA上启用MFA多因子认证,今天介绍下,通过MFA的RADIUS来实现多因子认证!安装启用MFA、导入AD账号、设置电话号码、语言选项等过程可参考上篇文章(http://blog.51cto.com/hubuxcg/2068870)中1-14步骤的内容,这里只介绍下Radius的配置部分1、...
-
56
基本介绍 交叉编译是为了在不同平台编译出其他平台的程序,比如在Linux编译出Windows程序,在Windows能编译出Linux程序,32位系统下编译出64位程序,今天介绍的gox就是其中一款交叉编译工具。 配置环境 首先...
-
20
鸿蒙和阿里YunOS系统原理上有啥区别? 102 18035 只看楼主
-
5
有啥快速扫描端口的工具吗? - V2EX V2EX › 程序员 有啥快速扫描端口的工具吗?
-
26
V2EX › 程序员 有啥能用的跨语言模型定义导入方案么 calmzhu · 10 小时 1 分钟前 · 211...
-
21
V2EX › 程序员 有啥非常优秀的 Gif 压缩优化工具? ALLROBOT · 11 小时 47 分钟前 · 98...
-
2
编译原理工具系列(1)——lex 发表于 ...
-
7
编译原理工具系列(2)——yacc 发表于 ...
-
3
正确解锁领英开发客户懒人工具的方法 ...
-
3
Redis高可用之主从复制原理演进分析 在很久之前写过一篇 Redis 主从复制原理的简略分析,基本是一个笔记类文章。 一、...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK