抽象定义

  1. // The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec.
  2. type Spec interface {
  3. Node
  4. specNode()
  5. }

导入声明

  1. // An ImportSpec node represents a single package import.
  2. type ImportSpec struct {
  3. Doc *CommentGroup // associated documentation; or nil
  4. Name *Ident // local package name (including "."); or nil
  5. Path *BasicLit // import path
  6. Comment *CommentGroup // line comments; or nil
  7. EndPos token.Pos // end of spec (overrides Path.Pos if nonzero)
  8. }
  1. func main() {
  2. fset := token.NewFileSet()
  3. f, err := parser.ParseFile(fset, "hello.go", src, parser.ImportsOnly)
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. for _, s := range f.Imports {
  8. fmt.Printf("import: name = %v, path = %#v\n", s.Name, s.Path)
  9. }
  10. }
  11. const src = `package foo
  12. import "pkg-a"
  13. import pkg_b_v2 "pkg-b"
  14. import . "pkg-c"
  15. import _ "pkg-d"
  16. `
  17. -------------------------------output-------------------------------
  18. import: name = <nil>, path = &ast.BasicLit{ValuePos:20, Kind:9, Value:"\"pkg-a\""}
  19. import: name = pkg_b_v2, path = &ast.BasicLit{ValuePos:44, Kind:9, Value:"\"pkg-b\""}
  20. import: name = ., path = &ast.BasicLit{ValuePos:61, Kind:9, Value:"\"pkg-c\""}
  21. import: name = _, path = &ast.BasicLit{ValuePos:78, Kind:9, Value:"\"pkg-d\""}

在使用parser.ParseFile分析文件时,采用的是parser.ImportsOnly模式,这样语法分析只会解析包声明和导入包的部分,其后的类型、常量、变量和函数的声明则不会解析。

类型声明

Go语言中通过type关键字声明类型:一种是声明新的类型,另一种是为已有的类型创建一个别名。

  1. // A TypeSpec node represents a type declaration (TypeSpec production).
  2. type TypeSpec struct {
  3. Doc *CommentGroup // associated documentation; or nil
  4. Name *Ident // type name
  5. TypeParams *FieldList // type parameters; or nil
  6. Assign token.Pos // position of '=', if any
  7. Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes
  8. Comment *CommentGroup // line comments; or nil
  9. }
  1. func main() {
  2. fset := token.NewFileSet()
  3. f, err := parser.ParseFile(fset, "hello.go", src, parser.AllErrors)
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. for _, decl := range f.Decls {
  8. if v, ok := decl.(*ast.GenDecl); ok {
  9. for _, spec := range v.Specs {
  10. fmt.Printf("%T\n", spec)
  11. }
  12. }
  13. }
  14. }
  15. const src = `package foo
  16. type MyInt1 int
  17. type MyInt2 = int
  18. `
  19. -------------------------------output-------------------------------
  20. *ast.TypeSpec
  21. *ast.TypeSpec

常量声明

  1. type ValueSpec struct {
  2. Doc *CommentGroup // associated documentation; or nil
  3. Names []*Ident // value names (len(Names) > 0)
  4. Type Expr // value type; or nil
  5. Values []Expr // initial values; or nil
  6. Comment *CommentGroup // line comments; or nil
  7. }
  1. const Pi = 3.14
  2. const E float64 = 2.71828
  3. for _, decl := range f.Decls {
  4. if v, ok := decl.(*ast.GenDecl); ok {
  5. for _, spec := range v.Specs {
  6. fmt.Printf("%T\n", spec)
  7. }
  8. }
  9. }
  10. -------------------------------output-------------------------------
  11. *ast.ValueSpec
  12. *ast.ValueSpec

因为Go语言支持多赋值语法,因此其中Names和Values分别表示常量的名字和值列表。而Type部分则用于区分常量是否指定了强类型(比如例子中的E被定义为float64类型)。可以通过ast.Print(nil, spec)输出每个常量的语法树结构:

  1. 0 *ast.ValueSpec {
  2. 1 . Names: []*ast.Ident (len = 1) {
  3. 2 . . 0: *ast.Ident {
  4. 3 . . . NamePos: 19
  5. 4 . . . Name: "Pi"
  6. 5 . . . Obj: *ast.Object {
  7. 6 . . . . Kind: const
  8. 7 . . . . Name: "Pi"
  9. 8 . . . . Decl: *(obj @ 0)
  10. 9 . . . . Data: 0
  11. 10 . . . }
  12. 11 . . }
  13. 12 . }
  14. 13 . Values: []ast.Expr (len = 1) {
  15. 14 . . 0: *ast.BasicLit {
  16. 15 . . . ValuePos: 24
  17. 16 . . . Kind: FLOAT
  18. 17 . . . Value: "3.14"
  19. 18 . . }
  20. 19 . }
  21. 20 }
  22. 0 *ast.ValueSpec {
  23. 1 . Names: []*ast.Ident (len = 1) {
  24. 2 . . 0: *ast.Ident {
  25. 3 . . . NamePos: 35
  26. 4 . . . Name: "E"
  27. 5 . . . Obj: *ast.Object {
  28. 6 . . . . Kind: const
  29. 7 . . . . Name: "E"
  30. 8 . . . . Decl: *(obj @ 0)
  31. 9 . . . . Data: 0
  32. 10 . . . }
  33. 11 . . }
  34. 12 . }
  35. 13 . Type: *ast.Ident {
  36. 14 . . NamePos: 37
  37. 15 . . Name: "float64"
  38. 16 . }
  39. 17 . Values: []ast.Expr (len = 1) {
  40. 18 . . 0: *ast.BasicLit {
  41. 19 . . . ValuePos: 47
  42. 20 . . . Kind: FLOAT
  43. 21 . . . Value: "2.71828"
  44. 22 . . }
  45. 23 . }
  46. 24 }

变量声明

变量声明的语法规范和常量声明几乎是一样的,只是开始的var关键字不同而已。

按组声明

  1. const Pi = 3.14
  2. var (
  3. a int
  4. b bool
  5. )

以上代码对应语法树的逻辑结构如图所示:
image.png
因为第一个出现的是const关键字,因此ast.File.Decls的第一个元素是表示常量的ast.GenDecl类型,其中Specs列表只有一个元素对应Pi常量。第二个出现是var关键字,因此ast.File.Decls的第二个元素是表示变量的ast.GenDecl类型,其中Specs列表有两个元素分别对应a和b两个变量,Specs列表的长度对应组声明中元素的个数。

函数声明

在顶级声明中包含函数和方法的声明,从语法角度看函数是没有接收者参数的方法特例。函数的语法规则如下:

  1. FunctionDecl = "func" MethodName Signature [ FunctionBody ] .
  2. MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .

其中FunctionDecl表示函数,而MethodDecl表示方法。MethodDecl表示的方法规范比函数多了Receiver语法结构,Receiver表示方法的接收者参数。

下面是一个方法函数:

  1. func (p *xType) Hello(arg1, arg2 int) (bool, error) { ... }

通过parser.ParseFile解析得到*ast.File类型的f返回值之后,可以通过以下的代码打印方法的声明:

  1. for _, decl := range f.Decls {
  2. if fn, ok := decl.(*ast.FuncDecl); ok {
  3. ast.Print(nil, fn)
  4. }
  5. }

函数的声明对应*ast.FuncDecl类型,它的定义如下:

  1. type FuncDecl struct {
  2. Doc *CommentGroup // associated documentation; or nil
  3. Recv *FieldList // receiver (methods); or nil (functions)
  4. Name *Ident // function/method name
  5. Type *FuncType // function signature: parameters, results, and position of "func" keyword
  6. Body *BlockStmt // function body; or nil for external (non-Go) function
  7. }

其中Recv对应接收者列表,在这里是指(p *xType)部分。Name是函数的名字,这里的名字是Hello。而Type表示方法或函数的类型(函数的类型和接口的定义一致,因为接口并不包含接收者信息),其中包含输入参数和返回值信息。最后的Body表示函数体中的语句部分,我们暂时忽略函数体部分。
函数声明最重要的是名字、接收者、输入参数和返回值,其中除名字之外的三者都是ast.FieldList类型,而输入参数和返回值又被封装为ast.FuncType类型。表示函数类型的ast.FuncType结构体定义如下:

  1. type FuncType struct {
  2. Func token.Pos // position of "func" keyword (token.NoPos if there is no "func")
  3. Params *FieldList // (incoming) parameters; non-nil
  4. Results *FieldList // (outgoing) results; or nil
  5. }

其中Params和Results分别表示输入参数和返回值列表,它们和函数的接收者参数列表是相同的类型。因此该方法的定义可以用下图表示:
image.png

接收者、输入和返回值参数均由ast.FieldList定义,该结构体定义如下:

  1. type FieldList struct {
  2. Opening token.Pos // position of opening parenthesis/brace, if any
  3. List []*Field // field list; or nil
  4. Closing token.Pos // position of closing parenthesis/brace, if any
  5. }
  6. type Field struct {
  7. Doc *CommentGroup // associated documentation; or nil
  8. Names []*Ident // field/method/parameter names; or nil
  9. Type Expr // field/method/parameter type
  10. Tag *BasicLit // field tag; or nil
  11. Comment *CommentGroup // line comments; or nil
  12. }

ast.FieldList其实是[]ast.Field切片类型的再次包装,注意是增加了开始和结束的位置信息。每一个ast.Field表示一组参数,所有参数的名字由[]ast.Ident切片表示,而通一组参数有着相同的类型。Type表示一组参数的类型,是一个类型表达式。
查看下面的例子:

  1. func Hello1(s0, s1 string, s2 string)

其中s0省略了类型,和s1共享string类型,因此s0和s1是一组参数,对应一个ast.Field。而s2是另一个独立的ast.Field。

函数的接收者、输入和返回值参数均可以省略名字,如果省略了名字则使用后面第一次出现的类型。如果全部参数都省略了名字,那么每个参数就只有类型信息,函数体内部无法再通过参数名字访问参数。