美文网首页
使用go/scanner库来查找golang标准库源码中最常用的

使用go/scanner库来查找golang标准库源码中最常用的

作者: zooeymoon | 来源:发表于2020-03-20 02:11 被阅读0次

    在YouTube上又看了Francesc Campoy大神的视频。这一期他主要讲了go/scanner库的用法。那么什么是go/scanner呢?

    官方的定义是:

    Package scanner implements a scanner for Go source text. It takes a []byte as source which can then be tokenized through repeated calls to the Scan method.

    翻译过来就是:

    scanner包实现了一个对于go源码文本的扫描器,它把[]byte作为一个源,通过重复的调用Scan方法来进行标记

    以下是官方给的例子:

    package main
    
    import (
        "fmt"
        "go/scanner"
        "go/token"
    )
    
    func main() {
        // src is the input that we want to tokenize.
        src := []byte("cos(x) + 1i*sin(x) // Euler")
    
        // Initialize the scanner.
        var s scanner.Scanner
        fset := token.NewFileSet()                      // positions are relative to fset
        file := fset.AddFile("", fset.Base(), len(src)) // register input "file"
        s.Init(file, src, nil /* no error handler */, scanner.ScanComments)
    
        // Repeated calls to Scan yield the token sequence found in the input.
        for {
            pos, tok, lit := s.Scan()
            if tok == token.EOF {
                break
            }
            fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit)
        }
    
    }
    

    这里有几点需要说明

    • var s scanner.Scanner为扫描器
    • fset := token.NewFileSet()为创建一个新的文件集
    • s.Init(file, src, nil /* no error handler */, scanner.ScanComments) 初始化一个扫描器
    • pos, tok, lit := s.Scan() 返回的三个参数分别为:标识符位置,标识符文字字符串。需要有一点说明:如果标识符为token.EOF,则说明扫描结束

    现在我们开始编写代码:

    1.首先我们需要保证运行的参数大于等于两个:

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main() {
        if len(os.Args) < 2 {
            fmt.Fprintf(os.Stderr , "usage:\n\t%s [files] \n",os.Args[0])
            os.Exit(1)
        }
    
    }
    

    2.之后开始循环os.Args把从第二个开始的所有参数遍历出来

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main() {
        if len(os.Args) < 2 {
            fmt.Fprintf(os.Stderr , "usage:\n\t%s [files] \n",os.Args[0])
            os.Exit(1)
        }
    
        for _ , arg := range os.Args[1:] {
    
            fmt.Println(arg)
    
        }
    
    }
    

    3.接下来我们开始要使用我们的go/scanner库了,这里我们要依次完成

    • 文件集创建
    • ioutil读取文件
    • 把文件加入到文件集
    • 初始化扫描器
    • 循环迭代,如果标识符为token.EOF,则跳出循环
    • 打印输出
    package main
    
    import (
        "fmt"
        "go/scanner"
        "go/token"
        "io/ioutil"
        "log"
        "os"
    )
    
    func main() {
        if len(os.Args) < 2 {
            fmt.Fprintf(os.Stderr , "usage:\n\t%s [files] \n",os.Args[0])
            os.Exit(1)
        }
    
        fs := token.NewFileSet()
    
        for _ , arg := range os.Args[1:] {
    
            b , err := ioutil.ReadFile(arg)
            if err != nil {
                log.Fatal(err)
            }
            f := fs.AddFile(arg,fs.Base(),len(b))
            var s scanner.Scanner
            s.Init(f ,b,nil,scanner.ScanComments)
    
            for {
                pos , tok , lit := s.Scan()
                if tok == token.EOF {
                    break
                }
    
                fmt.Println(pos , tok , lit)
            }
    
        }
    
    }
    

    我们运行代码产生的结果如下:

    ➜  scanner git:(master) ✗ ./scanner stdlib.go
    1 package package
    9 IDENT main
    13 ; 
    
    15 import import
    22 ( 
    25 STRING "fmt"
    30 ; 
    
    32 STRING "go/scanner"
    44 ; 
    
    46 STRING "go/token"
    56 ; 
    
    58 STRING "io/ioutil"
    69 ; 
    
    71 STRING "log"
    76 ; 
    
    78 STRING "os"
    82 ; 
    
    83 ) 
    84 ; 
    
    86 func func
    91 IDENT main
    95 ( 
    96 ) 
    98 { 
    101 if if
    104 IDENT len
    107 ( 
    108 IDENT os
    110 . 
    111 IDENT Args
    115 ) 
    117 < 
    119 INT 2
    121 { 
    125 IDENT fmt
    128 . 
    129 IDENT Fprintf
    136 ( 
    137 IDENT os
    139 . 
    140 IDENT Stderr
    147 , 
    149 STRING "usage:\n\t%s [files] \n"
    174 , 
    175 IDENT os
    177 . 
    178 IDENT Args
    182 [ 
    183 INT 0
    184 ] 
    185 ) 
    186 ; 
    
    189 IDENT os
    191 . 
    192 IDENT Exit
    196 ( 
    197 INT 1
    198 ) 
    199 ; 
    
    201 } 
    202 ; 
    
    205 IDENT fs
    208 := 
    211 IDENT token
    216 . 
    217 IDENT NewFileSet
    227 ( 
    228 ) 
    229 ; 
    
    232 for for
    236 IDENT _
    238 , 
    240 IDENT arg
    244 := 
    247 range range
    253 IDENT os
    255 . 
    256 IDENT Args
    260 [ 
    261 INT 1
    262 : 
    263 ] 
    265 { 
    270 IDENT b
    272 , 
    274 IDENT err
    278 := 
    281 IDENT ioutil
    287 . 
    288 IDENT ReadFile
    296 ( 
    297 IDENT arg
    300 ) 
    301 ; 
    
    304 if if
    307 IDENT err
    311 != 
    314 IDENT nil
    318 { 
    323 IDENT log
    326 . 
    327 IDENT Fatal
    332 ( 
    333 IDENT err
    336 ) 
    337 ; 
    
    340 } 
    341 ; 
    
    344 IDENT f
    346 := 
    349 IDENT fs
    351 . 
    352 IDENT AddFile
    359 ( 
    360 IDENT arg
    363 , 
    364 IDENT fs
    366 . 
    367 IDENT Base
    371 ( 
    372 ) 
    373 , 
    374 IDENT len
    377 ( 
    378 IDENT b
    379 ) 
    380 ) 
    381 ; 
    
    384 var var
    388 IDENT s
    390 IDENT scanner
    397 . 
    398 IDENT Scanner
    405 ; 
    
    408 IDENT s
    409 . 
    410 IDENT Init
    414 ( 
    415 IDENT f
    417 , 
    418 IDENT b
    419 , 
    420 IDENT nil
    423 , 
    424 IDENT scanner
    431 . 
    432 IDENT ScanComments
    444 ) 
    445 ; 
    
    449 for for
    453 { 
    458 IDENT pos
    462 , 
    464 IDENT tok
    468 , 
    470 IDENT lit
    474 := 
    477 IDENT s
    478 . 
    479 IDENT Scan
    483 ( 
    484 ) 
    485 ; 
    
    489 if if
    492 IDENT tok
    496 == 
    499 IDENT token
    504 . 
    505 IDENT EOF
    509 { 
    515 break break
    520 ; 
    
    524 } 
    525 ; 
    
    530 IDENT fmt
    533 . 
    534 IDENT Println
    541 ( 
    542 IDENT pos
    546 , 
    548 IDENT tok
    552 , 
    554 IDENT lit
    557 ) 
    558 ; 
    
    561 } 
    562 ; 
    
    565 } 
    566 ; 
    
    568 } 
    569 ; 
    

    这里有几点我们可以发现

    • 虽然我们没有使用;符号,但是源码自动给我们添加上了,所以说有些ide不推荐我们在换行的时候使用分号。
    • 如果标识符是关键字,那么lit参数回打印出来,比如说125 IDENT fmt

    3.现在我们有了我们想要的,所以我们现在需要修正代码,把出现最多的标识符字符串打印出来,这里我们需要通过以下步骤实现

    • 创建一个map映射
    • 在循环中如果发现token为标识符,则放入映射中
    • 定义一个pair结构体
    • 创建一个pair结构体切片
    • 把map映射的元素放入到切片中
    • 排序
    • 输出

    代码如下:

    package main
    
    import (
        "fmt"
        "go/scanner"
        "go/token"
        "io/ioutil"
        "log"
        "os"
        "sort"
    )
    
    func main() {
        if len(os.Args) < 2 {
            fmt.Fprintf(os.Stderr , "usage:\n\t%s [files] \n",os.Args[0])
            os.Exit(1)
        }
    
        counts := map[string]int{}
    
        fs := token.NewFileSet()
    
        for _ , arg := range os.Args[1:] {
    
            b , err := ioutil.ReadFile(arg)
            if err != nil {
                log.Fatal(err)
            }
            f := fs.AddFile(arg,fs.Base(),len(b))
            var s scanner.Scanner
            s.Init(f ,b,nil,scanner.ScanComments)
    
            for {
                _ , tok , lit := s.Scan()
                if tok == token.EOF {
                    break
                }
    
                if tok == token.IDENT {
                    counts[lit]++
                }
    
            }
    
        }
    
        type pair struct {
            s string
            n int
        }
    
        pairs := make([]pair , 0 , len(counts))
        for s ,n := range counts {
            pairs = append(pairs , pair{s,n})
        }
    
        sort.Slice(pairs, func(i, j int) bool {
            return pairs[i].n > pairs[j].n
        })
    
        for i := 0 ; i <len(pairs)&& i <5 ;i++ {
            fmt.Printf("%5d %s\n",pairs[i].n , pairs[i].s)
        }
    }
    

    输出的结果为:

        9 pairs
        8 i
        7 s
        6 n
        5 os
    

    我们感觉有一些字符有些短,没有意义,我们需要过滤统计的字符,让其大于3。
    这里我们修改以下循环counts的条件

    for s ,n := range counts {
    
            if len(s) > 3 {
                pairs = append(pairs , pair{s,n})
            }
        }
    

    我们重新运行一次代码,产生的结果如下:

        9 pairs
        4 counts
        3 Args
        3 pair
        3 token
    

    代码测试通过,接下来我们需要把go run后面的参数修改以下,让其可以扫描整个golang的标准库

    52720 Args
    32277 uintptr
    31416 AddArg
    27414 true
    25265 string
    

    我们发现标准库里面用的最多的是Args

    这里我们有个小插曲发现,如果绝对路径定义要扫描的文件路径

    ➜  scanner git:(master) ✗ ./scanner /usr/local/Cellar/go/1.14/libexec/src/**/*.go
    zsh: argument list too long: ./scanner
    

    发现参数过长,所以我把二进制文件放入到了/usr/local/Cellar/go/1.14/文件夹下
    之后使用命令:

    ./scanner ./libexec/src/**/*.go
    ➜  1.14 ./scanner ./libexec/src/**/*.go
    52720 Args
    32277 uintptr
    31416 AddArg
    27414 true
    25265 string
    

    完美!!!!!

    最终的代码为:

    package main
    
    import (
        "fmt"
        "go/scanner"
        "go/token"
        "io/ioutil"
        "log"
        "os"
        "sort"
    )
    
    func main() {
        if len(os.Args) < 2 {
            fmt.Fprintf(os.Stderr , "usage:\n\t%s [files] \n",os.Args[0])
            os.Exit(1)
        }
    
        counts := map[string]int{}
    
        fs := token.NewFileSet()
    
        for _ , arg := range os.Args[1:] {
    
            b , err := ioutil.ReadFile(arg)
            if err != nil {
                log.Fatal("haha",err)
            }
            f := fs.AddFile(arg,fs.Base(),len(b))
            var s scanner.Scanner
            s.Init(f ,b,nil,scanner.ScanComments)
    
            for {
                _ , tok , lit := s.Scan()
                if tok == token.EOF {
                    break
                }
    
                if tok == token.IDENT {
                    counts[lit]++
                }
    
            }
    
        }
    
        type pair struct {
            s string
            n int
        }
    
        pairs := make([]pair , 0 , len(counts))
        for s ,n := range counts {
    
            if len(s) > 3 {
                pairs = append(pairs , pair{s,n})
            }
        }
    
        sort.Slice(pairs, func(i, j int) bool {
            return pairs[i].n > pairs[j].n
        })
    
        for i := 0 ; i <len(pairs)&& i <5 ;i++ {
            fmt.Printf("%5d %s\n",pairs[i].n , pairs[i].s)
        }
    }
    

    相关文章

      网友评论

          本文标题:使用go/scanner库来查找golang标准库源码中最常用的

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