抽象定义
// The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec.type Spec interface {NodespecNode()}
导入声明
// An ImportSpec node represents a single package import.type ImportSpec struct {Doc *CommentGroup // associated documentation; or nilName *Ident // local package name (including "."); or nilPath *BasicLit // import pathComment *CommentGroup // line comments; or nilEndPos token.Pos // end of spec (overrides Path.Pos if nonzero)}
func main() {fset := token.NewFileSet()f, err := parser.ParseFile(fset, "hello.go", src, parser.ImportsOnly)if err != nil {log.Fatal(err)}for _, s := range f.Imports {fmt.Printf("import: name = %v, path = %#v\n", s.Name, s.Path)}}const src = `package fooimport "pkg-a"import pkg_b_v2 "pkg-b"import . "pkg-c"import _ "pkg-d"`-------------------------------output-------------------------------import: name = <nil>, path = &ast.BasicLit{ValuePos:20, Kind:9, Value:"\"pkg-a\""}import: name = pkg_b_v2, path = &ast.BasicLit{ValuePos:44, Kind:9, Value:"\"pkg-b\""}import: name = ., path = &ast.BasicLit{ValuePos:61, Kind:9, Value:"\"pkg-c\""}import: name = _, path = &ast.BasicLit{ValuePos:78, Kind:9, Value:"\"pkg-d\""}
在使用parser.ParseFile分析文件时,采用的是parser.ImportsOnly模式,这样语法分析只会解析包声明和导入包的部分,其后的类型、常量、变量和函数的声明则不会解析。
类型声明
Go语言中通过type关键字声明类型:一种是声明新的类型,另一种是为已有的类型创建一个别名。
// A TypeSpec node represents a type declaration (TypeSpec production).type TypeSpec struct {Doc *CommentGroup // associated documentation; or nilName *Ident // type nameTypeParams *FieldList // type parameters; or nilAssign token.Pos // position of '=', if anyType Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypesComment *CommentGroup // line comments; or nil}
func main() {fset := token.NewFileSet()f, err := parser.ParseFile(fset, "hello.go", src, parser.AllErrors)if err != nil {log.Fatal(err)}for _, decl := range f.Decls {if v, ok := decl.(*ast.GenDecl); ok {for _, spec := range v.Specs {fmt.Printf("%T\n", spec)}}}}const src = `package footype MyInt1 inttype MyInt2 = int`-------------------------------output-------------------------------*ast.TypeSpec*ast.TypeSpec
常量声明
type ValueSpec struct {Doc *CommentGroup // associated documentation; or nilNames []*Ident // value names (len(Names) > 0)Type Expr // value type; or nilValues []Expr // initial values; or nilComment *CommentGroup // line comments; or nil}
const Pi = 3.14const E float64 = 2.71828for _, decl := range f.Decls {if v, ok := decl.(*ast.GenDecl); ok {for _, spec := range v.Specs {fmt.Printf("%T\n", spec)}}}-------------------------------output-------------------------------*ast.ValueSpec*ast.ValueSpec
因为Go语言支持多赋值语法,因此其中Names和Values分别表示常量的名字和值列表。而Type部分则用于区分常量是否指定了强类型(比如例子中的E被定义为float64类型)。可以通过ast.Print(nil, spec)输出每个常量的语法树结构:
0 *ast.ValueSpec {1 . Names: []*ast.Ident (len = 1) {2 . . 0: *ast.Ident {3 . . . NamePos: 194 . . . Name: "Pi"5 . . . Obj: *ast.Object {6 . . . . Kind: const7 . . . . Name: "Pi"8 . . . . Decl: *(obj @ 0)9 . . . . Data: 010 . . . }11 . . }12 . }13 . Values: []ast.Expr (len = 1) {14 . . 0: *ast.BasicLit {15 . . . ValuePos: 2416 . . . Kind: FLOAT17 . . . Value: "3.14"18 . . }19 . }20 }0 *ast.ValueSpec {1 . Names: []*ast.Ident (len = 1) {2 . . 0: *ast.Ident {3 . . . NamePos: 354 . . . Name: "E"5 . . . Obj: *ast.Object {6 . . . . Kind: const7 . . . . Name: "E"8 . . . . Decl: *(obj @ 0)9 . . . . Data: 010 . . . }11 . . }12 . }13 . Type: *ast.Ident {14 . . NamePos: 3715 . . Name: "float64"16 . }17 . Values: []ast.Expr (len = 1) {18 . . 0: *ast.BasicLit {19 . . . ValuePos: 4720 . . . Kind: FLOAT21 . . . Value: "2.71828"22 . . }23 . }24 }
变量声明
变量声明的语法规范和常量声明几乎是一样的,只是开始的var关键字不同而已。
按组声明
const Pi = 3.14var (a intb bool)
以上代码对应语法树的逻辑结构如图所示:
因为第一个出现的是const关键字,因此ast.File.Decls的第一个元素是表示常量的ast.GenDecl类型,其中Specs列表只有一个元素对应Pi常量。第二个出现是var关键字,因此ast.File.Decls的第二个元素是表示变量的ast.GenDecl类型,其中Specs列表有两个元素分别对应a和b两个变量,Specs列表的长度对应组声明中元素的个数。
函数声明
在顶级声明中包含函数和方法的声明,从语法角度看函数是没有接收者参数的方法特例。函数的语法规则如下:
FunctionDecl = "func" MethodName Signature [ FunctionBody ] .MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
其中FunctionDecl表示函数,而MethodDecl表示方法。MethodDecl表示的方法规范比函数多了Receiver语法结构,Receiver表示方法的接收者参数。
下面是一个方法函数:
func (p *xType) Hello(arg1, arg2 int) (bool, error) { ... }
通过parser.ParseFile解析得到*ast.File类型的f返回值之后,可以通过以下的代码打印方法的声明:
for _, decl := range f.Decls {if fn, ok := decl.(*ast.FuncDecl); ok {ast.Print(nil, fn)}}
函数的声明对应*ast.FuncDecl类型,它的定义如下:
type FuncDecl struct {Doc *CommentGroup // associated documentation; or nilRecv *FieldList // receiver (methods); or nil (functions)Name *Ident // function/method nameType *FuncType // function signature: parameters, results, and position of "func" keywordBody *BlockStmt // function body; or nil for external (non-Go) function}
其中Recv对应接收者列表,在这里是指(p *xType)部分。Name是函数的名字,这里的名字是Hello。而Type表示方法或函数的类型(函数的类型和接口的定义一致,因为接口并不包含接收者信息),其中包含输入参数和返回值信息。最后的Body表示函数体中的语句部分,我们暂时忽略函数体部分。
函数声明最重要的是名字、接收者、输入参数和返回值,其中除名字之外的三者都是ast.FieldList类型,而输入参数和返回值又被封装为ast.FuncType类型。表示函数类型的ast.FuncType结构体定义如下:
type FuncType struct {Func token.Pos // position of "func" keyword (token.NoPos if there is no "func")Params *FieldList // (incoming) parameters; non-nilResults *FieldList // (outgoing) results; or nil}
其中Params和Results分别表示输入参数和返回值列表,它们和函数的接收者参数列表是相同的类型。因此该方法的定义可以用下图表示:
接收者、输入和返回值参数均由ast.FieldList定义,该结构体定义如下:
type FieldList struct {Opening token.Pos // position of opening parenthesis/brace, if anyList []*Field // field list; or nilClosing token.Pos // position of closing parenthesis/brace, if any}type Field struct {Doc *CommentGroup // associated documentation; or nilNames []*Ident // field/method/parameter names; or nilType Expr // field/method/parameter typeTag *BasicLit // field tag; or nilComment *CommentGroup // line comments; or nil}
ast.FieldList其实是[]ast.Field切片类型的再次包装,注意是增加了开始和结束的位置信息。每一个ast.Field表示一组参数,所有参数的名字由[]ast.Ident切片表示,而通一组参数有着相同的类型。Type表示一组参数的类型,是一个类型表达式。
查看下面的例子:
func Hello1(s0, s1 string, s2 string)
其中s0省略了类型,和s1共享string类型,因此s0和s1是一组参数,对应一个ast.Field。而s2是另一个独立的ast.Field。
函数的接收者、输入和返回值参数均可以省略名字,如果省略了名字则使用后面第一次出现的类型。如果全部参数都省略了名字,那么每个参数就只有类型信息,函数体内部无法再通过参数名字访问参数。
