最新消息:yaf表单扩展中新增加了浮点数、日期和集合的校验。php yaf框架扩展实践三——表单

golang中ast库及初步使用

Golang 353浏览 0评论

本文需要你有写Golang代码经验,阅读大概需要20分钟。

最近一直在研究Go的依赖注入(dependency injection),方便日后写比较容易测试的代码(以便偷懒)。目前学到ast解析代码,现拿出来跟大家分享一下:)

Tokenizer 和 Lexical anaylizer

如果你知道tokenizer和lexical anaylizer是什么的话,请跳到下一章,不熟的话请看下面这个最简单的go代码

package main
func main() {
     println("Hello, World!")
}

这段go代码做了什么?很简单吧,package是main,定义了个main函数,main函数里调用了println函数,参数是“Hello, World!”。好,你是知道了,可当你运行go run时,go怎么知道的?go先要把你的代码打散成自己可以理解的构成部分(token),这一过程就叫tokenize。例如,第一行就被拆成了package和main。 这个阶段,go就像小婴儿只会理解我、要、吃饭等词,但串不成合适句子。因为“吃饭我要”是讲不通的,所以把词按一定的语法串起来的过程就是lexical anaylize或者parse,简单吧!和人脑不同的是,被程序理解的代码,通常会以abstract syntax tree(AST)的形式存储起来,方便进行校验和查找。

Go的AST

那我们来看看go的ast库对代码的理解程度是不是小婴儿吧(可运行的源代码在此),其实就是token+parse刚才我们看到的上一章代码,并且按AST的方式打印出来,结果在这里

package main

import (
	"go/ast"
	"go/parser"
	"go/token"
)

func main() {
	// 这就是上一章的代码.
	src := `
package main
func main() {
	println("Hello, World!")
}
`

	// Create the AST by parsing src.
	fset := token.NewFileSet() // positions are relative to fset
	f, err := parser.ParseFile(fset, "", src, 0)
	if err != nil {
		panic(err)
	}

	// Print the AST.
	ast.Print(fset, f)

}

为了不吓到你,我先只打印前6行:

     0  *ast.File {
     1  .  Package: 2:1
     2  .  Name: *ast.Ident {
     3  .  .  NamePos: 2:9
     4  .  .  Name: "main"
     5  .  }
     // 省略之后的50+行

可见,go 解析出了package这个关键词在文本的第二行的第一个(2:1)。“main”也解析出来了,在第二行的第9个字符,但是go的解析器还给它安了一个叫法:ast.Ident, 标示符 或者大家常说的ID,如下图所示:

Ident +------------+
                   |                         
Package +-----+    |
              v    v
           package main

接下来我们看看那个main函数被整成了什么样。

     6  .  Decls: []ast.Decl (len = 1) {
     7  .  .  0: *ast.FuncDecl {
     8  .  .  .  Name: *ast.Ident {
     9  .  .  .  .  NamePos: 3:6
    10  .  .  .  .  Name: "main"
    11  .  .  .  .  Obj: *ast.Object {
    12  .  .  .  .  .  Kind: func
    13  .  .  .  .  .  Name: "main"
    14  .  .  .  .  .  Decl: *(obj @ 7)

此处func main被解析成ast.FuncDecl(function declaration),而函数的参数(Params)和函数体(Body)自然也在这个FuncDecl中。Params对应的是*ast.FieldList,顾名思义就是项列表;而由大括号“{}”组成的函数体对应的是ast.BlockStmt(block statement)。如果不清楚,可以参考下面的图:

                 FuncDecl.Params +----------+
                                            |
                   FuncDecl.Name +--------+ |
                                          v v
          +----------------------> func main() {
          |                    +->
FuncDecl ++    FuncDecl.Body +-+       println("Hello, World!")
          |                    +->
          +----------------------> }

而对于main函数的函数体中,我们可以看到调用了println函数,在ast中对应的是ExprStmt(Express Statement),调用函数的表达式对应的是CallExpr(Call Expression),调用的参数自然不能错过,因为参数只有字符串,所以go把它归为ast.BasicLis (a literal of basic type)。如下图所示:

+-----+ ExprStmt +---------------+
|                                |
|    CallExpr   BasicLit         |
|        +          +            |
|        v          v            |
+---> println("Hello, World!")<--+

还有什么?

    50  .  Scope: *ast.Scope {
    51  .  .  Objects: map[string]*ast.Object (len = 1) {
    52  .  .  .  "main": *(obj @ 11)
    53  .  .  }
    54  .  }
    55  .  Unresolved: []*ast.Ident (len = 1) {
    56  .  .  0: *(obj @ 29)
    57  .  }
    58  }

我们可以看出ast还解析出了函数的作用域,以及作用域对应的对象。

小结

Go将所有可以识别的token抽象成Node,通过interface方式组织在一起,它们之间的关系如下图示意:

golang_ast_relation

golang_ast_relation

转载请注明:快乐编程 » golang中ast库及初步使用

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址