美文网首页程序员
编译原理有啥用之Go语言懒人工具

编译原理有啥用之Go语言懒人工具

作者: ztinpn | 来源:发表于2018-11-10 10:25 被阅读1039次

    动机

    笔者在使用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
    }
    

    可见要额外写很多代码。类似的需求还有:

    1. MarshalJSON和UnMarshalJSON代码;
    2. ORM的update代码;
    3. 转结构变量代码;
    4. 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的时光):


    工具地址

    http://xndh.net/go

    相关文章

      网友评论

        本文标题:编译原理有啥用之Go语言懒人工具

        本文链接:https://www.haomeiwen.com/subject/ptlnxqtx.html