在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)
}
}