乐趣区

关于golang:Go编译原理系列4语法分析

前言

在上一篇文章中,分享了 Go 编译器是如何将源文件解析成 Token 的。本文次要是分享,语法分析阶段是如何依据不同的 Token 来进行语法解析的。本文你能够理解到以下内容:

  1. Go 语法分析整体概览
  2. Go 语法解析具体过程
  3. 示例展现语法解析残缺过程

💡 Tips:文中会波及到文法、产生式相干内容,如果你不太理解,请先看一下前边的词法剖析 & 语法分析根底这篇文章

文章比拟长,但代码比拟多,次要是不便了解。置信看完肯定有所播种

Go 语法分析整体概览

为了不便后边的了解,我这里提供一个源文件,后边的内容,你都能够依照这个源文件中的内容,带入去了解:

package main

import (
    "fmt"
    "go/token"
)

type aType string
const A = 666
var B = 888

func main() {fmt.Println("Test Parser")
    token.NewFileSet()}

入口

在上一篇文章介绍词法剖析的时候,具体的阐明了 Go 的编译入口,在编译入口处初始化了语法分析器,并且在初始化语法分析器的过程中初始化了词法分析器,所以词法分析器是内嵌在了语法分词器的外部的,咱们能够在:src/cmd/compile/internal/syntax/parser.go 中看到语法分析器的构造如下:

type parser struct {
    file  *PosBase // 记录关上的文件的信息的(比方文件名、行、列信息)errh  ErrorHandler // 报错回调
    mode  Mode // 语法分析模式
    pragh PragmaHandler
    scanner // 词法分析器

    base   *PosBase // current position base
    first  error    // first error encountered
    errcnt int      // number of errors encountered
    pragma Pragma   // pragmas

    fnest  int    // function nesting level (for error handling) 函数的嵌套层级
    xnest  int    // expression nesting level (for complit ambiguity resolution) 表达式嵌套级别
    indent []byte // tracing support(跟踪反对)}

因为在上一篇文章中曾经具体的分享了入口文件的地位及它都做了什么(src/cmd/compile/internal/syntax/syntax.go → Parse(…)),在语法分析器和词法分析器初始化实现后,咱们会看到它调用了语法分析器的 fileOrNil() 办法,它就是语法分析的外围办法,下边就具体介绍一下这个外围办法具体做了哪些事件

Go 语法解析构造

这部分会波及到每种申明类型的解析、及每种申明类型的构造体,还包含语法解析器生成的语法树的结点之间的关系。有些中央的确比拟难了解,你可先大抵看一遍文字内容,而后再联合我下边的那张语法解析的图,了解起来可能就轻松一些

Go 语法分析中的文法规定

我先对这部分做整体的介绍,而后再去理解里边的细节

进入到 fileOrNil() 办法,在正文中会看到这么一行正文

SourceFile = PackageClause ";" {ImportDecl ";"} {TopLevelDecl ";"} .

它就是 Go 解析源文件的文法规定,这个在系列文章的第二篇词法剖析和语法分析根底局部有提到。Go 的编译器在初始化完语法分析器和词法分析器之后,就会调用该办法,该办法会通过词法分词器提供的 next()办法,来一直的获取 Token,语法分析就依照上边这个文法规定进行语法分析

可能你不晓得 PackageClause、ImportDecl、TopLevelDecl 的产生式,你能够间接在这个文件中找到这三个产生式(对于产生式,在词法剖析和语法分析根底局部有介绍)

SourceFile = PackageClause ";" {ImportDecl ";"} {TopLevelDecl ";"} .

PackageClause = "package" PackageName . 
PackageName = identifier . 

ImportDecl = "import" (ImportSpec | "(" { ImportSpec ";"} ")" ) . 
ImportSpec = ["." | PackageName] ImportPath . 
ImportPath = string_lit .

TopLevelDecl = Declaration | FunctionDecl | MethodDecl . 
Declaration = ConstDecl | TypeDecl | VarDecl . 

ConstDecl = "const" (ConstSpec | "(" { ConstSpec ";"} ")" ) . 
ConstSpec = IdentifierList [[ Type] "=" ExpressionList ] . 

TypeDecl = "type" (TypeSpec | "(" { TypeSpec ";"} ")" ) . 
TypeSpec = AliasDecl | TypeDef . 
AliasDecl = identifier "=" Type . 
TypeDef = identifier Type . 

VarDecl = "var" (VarSpec | "(" { VarSpec ";"} ")" ) . 
VarSpec = IdentifierList (Type [ "=" ExpressionList] | "=" ExpressionList ) .

fileOrNil()说白了就是依照 SourceFile 这个文法来对源文件进行解析,最终返回的是一个语法树(File 构造,下边会介绍),咱们晓得,编译器会将每一个文件都解析成一颗语法树

下边就简略的介绍一下 Go 语法分析中的几个文法的含意(如果你看了前边的词法剖析和语法分析根底这篇文章,应该很容易就能看懂 Go 这些文法的含意)

SourceFile = PackageClause ";" {ImportDecl ";"} {TopLevelDecl ";"} .

咱们能够看到 SourceFile 是由 PackageClause、ImportDecl、TopLevelDecl 这三个非终结符号形成的。它的意思就是:每一个源文件次要是由 package 申明、导入申明、和顶层申明形成的(其中 ImportDecl 和 TopLevelDecl 是可选的,可有可无,这就是中括号的含意)。也就是说,每一个源文件,都应该是合乎这个文法规定的

首先是包申明:PackageClause
PackageClause = "package" PackageName . 
PackageName = identifier . 
PackageClause 是由一个终结符 package 和一个非终结符 PackageName 形成的,而 PackageName 由一个标识符形成的

所以,在扫描源文件的时候,应该会最先获取到的是 package 的 Token,而后是一个标识符的 Token。解析完 package 申明之后,后边就应该是导入申明

而后是导入申明:ImportDecl
ImportDecl = "import" (ImportSpec | "(" { ImportSpec ";"} ")" ) . 
ImportSpec = ["." | PackageName] ImportPath . 
ImportPath = string_lit .

理解了上边这些文法的含意之后,下边就看 fileNil()这个办法是如何依照上边的文法进行语法解析的。然而在这之前,须要先晓得 fileOrNil()这个办法生成的语法树的各个节点构造是什么样的

语法树各个节点的构造

咱们晓得,fileOrNil()办法会依照 SourceFile 的文法规定,生成一棵语法树,而每棵语法树的构造是这样的一个构造体:(src/cmd/compile/internal/syntax/nodes.go → File)

type File struct {
    Pragma   Pragma
    PkgName  *Name
    DeclList []Decl
    Lines    uint
    node
}

它次要蕴含的是 源文件的包名 (PkgName)、和 源文件中所有的申明(DeclList)。须要留神的是,它会将 import 也当做申明解析到 DeclList 中

fileOrNil()会将源文件中的所有申明(比方 var、type、const)依照每种申明的构造(每种申明都定义的有相应的构造体,用来保留申明信息,而这些构造体都是语法树的子节点)解析到 DeclList 中。在上边的文法中,咱们能看到常量、类型、变量申明文法,其实还有函数和办法的文法,能够在 src/cmd/compile/internal/syntax/parser.go 中找到

FunctionDecl = "func" FunctionName (Function | Signature) .
FunctionName = identifier .
Function     = Signature FunctionBody .

MethodDecl   = "func" Receiver MethodName (Function | Signature) .
Receiver     = Parameters .

这棵语法树的根节点构造是 File 构造体,它的子节点构造就是:

ImportDecl struct {
        Group        *Group // nil means not part of a group
        Pragma       Pragma
        LocalPkgName *Name // including "."; nil means no rename present
        Path         *BasicLit
        decl
    }

ConstDecl struct {
        Group    *Group // nil means not part of a group
        Pragma   Pragma
        NameList []*Name
        Type     Expr // nil means no type
        Values   Expr // nil means no values
        decl
    }

TypeDecl struct {
        Group  *Group // nil means not part of a group
        Pragma Pragma
        Name   *Name
        Alias  bool
        Type   Expr
        decl
    }

VarDecl struct {
        Group    *Group // nil means not part of a group
        Pragma   Pragma
        NameList []*Name
        Type     Expr // nil means no type
        Values   Expr // nil means no values
        decl
    }

FuncDecl struct {
        Pragma Pragma
        Recv   *Field // nil means regular function
        Name   *Name
        Type   *FuncType
        Body   *BlockStmt // nil means no body (forward declaration)
        decl
    }

这些节点的构造定义在:src/cmd/compile/internal/syntax/nodes.go 中。也就是说在解析的过程中,如果解析到满足 ImportDecl 文法规定的,它就会创立相应构造的节点来保留相干信息;遇到满足 var 类型申明文法的,就创立 var 类型申明相干的构造体(VarDecl)来保留申明信息

如果解析到函数就略微简单一点,从函数节点的构造能够看到它蕴含 接收者 函数名称 类型 函数体 这几局部,最简单的中央在函数体,它是一个 BlockStmt 的构造:

BlockStmt struct {List   []Stmt //Stmt 是一个接口
        Rbrace Pos
        stmt
    }

BlockStmt 是由一系列的申明和表达式形成的,你在 src/cmd/compile/internal/syntax/nodes.go 中能够看到很多表达式和申明的构造体(这些构造体,也都是函数申明下边的节点构造)

// ----------------------------------------------------------------------------
// Statements
......
SendStmt struct {
        Chan, Value Expr // Chan <- Value
        simpleStmt
    }

    DeclStmt struct {DeclList []Decl
        stmt
    }

    AssignStmt struct {
        Op       Operator // 0 means no operation
        Lhs, Rhs Expr     // Rhs == ImplicitOne means Lhs++ (Op == Add) or Lhs-- (Op == Sub)
        simpleStmt
    }
......
ReturnStmt struct {
        Results Expr // nil means no explicit return values
        stmt
    }

    IfStmt struct {
        Init SimpleStmt
        Cond Expr
        Then *BlockStmt
        Else Stmt // either nil, *IfStmt, or *BlockStmt
        stmt
    }

    ForStmt struct {
        Init SimpleStmt // incl. *RangeClause
        Cond Expr
        Post SimpleStmt
        Body *BlockStmt
        stmt
    }
......

// ----------------------------------------------------------------------------
// Expressions
......
// [Len]Elem
    ArrayType struct {// TODO(gri) consider using Name{"..."} instead of nil (permits attaching of comments)
        Len  Expr // nil means Len is ...
        Elem Expr
        expr
    }

    // []Elem
    SliceType struct {
        Elem Expr
        expr
    }

    // ...Elem
    DotsType struct {
        Elem Expr
        expr
    }

    // struct {FieldList[0] TagList[0]; FieldList[1] TagList[1]; ... }
    StructType struct {FieldList []*Field
        TagList   []*BasicLit // i >= len(TagList) || TagList[i] == nil means no tag for field i
        expr
    }
......
// X[Index]
    IndexExpr struct {
        X     Expr
        Index Expr
        expr
    }

    // X[Index[0] : Index[1] : Index[2]]
    SliceExpr struct {
        X     Expr
        Index [3]Expr
        // Full indicates whether this is a simple or full slice expression.
        // In a valid AST, this is equivalent to Index[2] != nil.
        // TODO(mdempsky): This is only needed to report the "3-index
        // slice of string" error when Index[2] is missing.
        Full bool
        expr
    }

    // X.(Type)
    AssertExpr struct {
        X    Expr
        Type Expr
        expr
    }
......

是不是看着很眼生,有 if、for、return 这些。BlockStmt 中的 Stmt 是一个接口类型,也就意味着上边的各种表达式类型或申明类型构造体都能够实现 Stmt 接口

晓得了语法树中各个节点之间的关系,以及这些节点都可能会有哪些构造,下边就看 fileOrNil 是如何一步一步的解析出这棵语法树的各个节点的

fileOrNil() 的源码实现

这部分我不会深刻到每一个函数里边去看它是怎么解析的,只是从大抵轮廓上介绍它是怎么一步一步解析源文件中的 token 的,因为这部分次要是先从整体上意识。具体它是怎么解析 import、var、const、func 的,我会在下一部分具体的介绍

能够看到 fileOrNil()的代码实现次要蕴含以下几个局部:

// SourceFile = PackageClause ";" {ImportDecl ";"} {TopLevelDecl ";"} .
func (p *parser) fileOrNil() *File {
    ......

  //1. 创立 File 构造体
    f := new(File)
    f.pos = p.pos()

  // 2. 首先解析文件结尾的 package 定义
    // PackageClause
    if !p.got(_Package) { // 查看第一行是不是先定义了 package
        p.syntaxError("package statement must be first")
        return nil
    }
    
    // 3. 当解析完 package 之后,解析 import 申明(每一个 import 在解析器看来都是一个申明语句)// {ImportDecl ";"}
    for p.got(_Import) {f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
        p.want(_Semi)
    }

    // 4. 依据获取的 token 去 switch 抉择相应的分支,去解析对应类型的语句
    // {TopLevelDecl ";"}
    for p.tok != _EOF {
        switch p.tok {
        case _Const:
            p.next() // 获取到下一个 token
            f.DeclList = p.appendGroup(f.DeclList, p.constDecl)

        ......
    }
    // p.tok == _EOF

    p.clearPragma()
    f.Lines = p.line

    return f
}

在 fileOrNil()中有两个比拟重要的办法,是了解 fileOrNil()在做的事件的要害:

  • got
  • appendGroup

首先是 got 函数,它的参数是一个 token,用来 判断从词法分析器获取到的 token 是不是参数中传入的这个 token

而后是 appendGroup 函数,它有两个参数,第一个是 DeclList(前边介绍 File 的构造体的成员时介绍过,它是用来寄存 源文件中所有的申明 的,是一个切片类型);第二个参数是一个函数,这个函数是 每种类型申明语句的剖析函数(比方,我以后解析到 import 申明语句,那我就将解析 import 的办法作为第二个参数,传递给 appendGroup)

在解析 import 申明语句的时候,是下边这么一段代码:

for p.got(_Import) {f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
        p.want(_Semi)
}

appendGroup 的作用其实就是 找出批量的定义,就比方下边这些状况

//import 的批量申明状况
import (
    "fmt"
    "io"
    "strconv"
    "strings"
)

//var 的批量申明状况
var (
    x int
    y int
)

对于存在批量申明状况的申明语句构造体中,它会有一个 Group 字段,用来表明这些变量是属于同一个组,比方 import 的申明构造体和 var 的申明构造体

ImportDecl struct {
        Group        *Group // nil means not part of a group
        Pragma       Pragma
        LocalPkgName *Name // including "."; nil means no rename present
        Path         *BasicLit
        decl
}

VarDecl struct {
        Group    *Group // nil means not part of a group
        Pragma   Pragma
        NameList []*Name
        Type     Expr // nil means no type
        Values   Expr // nil means no values
        decl
}

在 appendGroup 的办法里边,会调用相应申明类型的解析办法,跟 fileOrNil()一样,依照相应类型申明的文法进行解析。比方对 import 申明进行解析的办法importDecl()

for p.got(_Import) {f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
        p.want(_Semi)
}
......

// ImportSpec = ["." | PackageName] ImportPath .
// ImportPath = string_lit .
func (p *parser) importDecl(group *Group) Decl {
    if trace {defer p.trace("importDecl")()}

    d := new(ImportDecl)
    d.pos = p.pos()
    d.Group = group
    d.Pragma = p.takePragma()

    switch p.tok {
    case _Name:
        d.LocalPkgName = p.name()
    case _Dot:
        d.LocalPkgName = p.newName(".")
        p.next()}
    d.Path = p.oliteral()
    if d.Path == nil {p.syntaxError("missing import path")
        p.advance(_Semi, _Rparen)
        return nil
    }

    return d
}

咱们能够看见,它也是先创立相应申明 (这里是 import 申明) 的构造体,而后记录申明的信息,并依照该种申明的文法对后边的内容进行解析

其它的还有像 const、type、var、func 等申明语句,通过 switch 去匹配并解析。他们也有相应的解析办法(其实就是依照各自的文法规定实现的代码),我这里不都列出来,你能够本人在 src/cmd/compile/internal/syntax/parser.go 中查看

前边咱们说到,语法分析器最终会应用不通的构造体来构建语法树的各个节点,其中 根节点 就是:src/cmd/compile/internal/syntax/nodes.go → File。在前边曾经介绍了它的构造,次要蕴含包名和所有的申明类型,这些不同类型的申明,就是语法树的子节点

可能通过上边的文字描述,略微还是有点难了解,下边就通过图的形式来展现整个语法解析的过程是什么样的

图解语法分析过程

以文章结尾提供的源代码为例来展现语法解析的过程。在 Go 词法剖析这篇文章中有提到,Go 的编译入口是从 src/cmd/compile/internal/gc/noder.go → parseFiles 开始的

到这里置信你对 Go 的语法分析局部曾经有个整体上的意识了。然而上边并没有画出各种申明语句是如何往下解析的,这就须要深刻的去看每种申明语句的解析办法是如何实现

Go 语法解析具体过程

对于 Go 语法分析中各种申明及表达式的解析,你都能够在 src/cmd/compile/internal/syntax/parser.go 中找到对应的办法

变量申明 & 导入申明解析

变量申明相干解析办法的调用,在 src/cmd/compile/internal/syntax/parser.go→fileOrNil()办法中都能够找到

导入申明解析

在 src/cmd/compile/internal/syntax/parser.go→fileOrNil()中能够看到下边这段代码

for p.got(_Import) {f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
        p.want(_Semi)
    }

在这个 appendGroup 中,最终会调用 importDecl()这个办法(从上边这段代码能够发现,当匹配到 import 的 token 之后才会执行导入申明的解析办法)

首先须要晓得的是,import 有以下几种应用形式:

import "a" // 默认的导入形式,a 是包名
import aAlias "x/a" // 给 x / a 这个包起个别名叫 aAlias
import . "c" // 将依赖包的公开符号间接导入到以后文件的名字空间
import _ "d" // 只是导入依赖包触发其包的初始化动作,然而不导入任何符号到以后文件名字空间

因为篇幅的起因,我就不把 importDecl()办法的源代码粘进去了。我这里梳理进去它次要干了哪些事件:

  1. 创立一个 ImportDeal 的构造体(最终会被 append 到 File.DeclList 中)
  2. 初始化构造体中的一些数据,比方解析到的 token 的地位信息、组 (group) 等
  3. 而后去匹配下一个 token,看它是 _Name 的 token 类型(标识符),还是_Dot(.)
  4. 如果获取到的 token 是 _Name, 则获取到包名,如果获取到的是_Dot(.),则新建一个名字.
  5. 而后就是匹配包门路,次要是由 oliteral() 办法实现
  6. 返回 ImportDeal 构造

这里值得说的是 oliteral() 办法,它会获取到下一个 token,看它是不是一个根底面值类型的 token,也就是_Literal

💡 Tips:根底面值只有整数、浮点数、复数、符文和字符串几种类型

如果是根底面值类型,它就会创立一个根底面值类型的构造体 nodes.BasicLit,并初始化它的一些信息

BasicLit struct {
        Value string   // 值
        Kind  LitKind  // 那种类型的根底面值,范畴(IntLit、FloatLit、ImagLit、RuneLit、StringLit)Bad   bool // true means the literal Value has syntax errors
        expr
}

这就不难看出,当解析到根底面值类型的时候,它就曾经是不可在合成的了,就是文法中说的终结符。在语法树上,这些终结符都是叶子节点

在 go 的规范库中有提供相干的办法来测试一下语法解析,我这里通过 go/parser 提供的接口来测一下导入的时候,语法分析的后果:

💡 Tips:跟词法剖析一样(你能够看一下 Go 词法分析器这篇文章的规范库测试局部),规范库中语法分析的实现和 go 编译器中的实现不一样,次要是构造体的设计(比方跟结点的构造变了,你能够本人去看一下,比较简单,你明确了编译器中语法分析的实现,规范库里的也能看懂),实现思维是一样的

package main

import (
    "fmt"
    "go/parser"
    "go/token"
)
const src = `
package test
import "a"
import aAlias "x/a"
import . "c"
import _ "d"
`

func main() {fileSet := token.NewFileSet()
    f, err := parser.ParseFile(fileSet, "", src, parser.ImportsOnly) //parser.ImportsOnly 模式,示意只解析包申明和导入
    if err != nil {panic(err.Error())
    }
    for _, s := range f.Imports{fmt.Printf("import:name = %v, path = %#v\n", s.Name, s.Path)
    }
}

打印后果:

import:name = <nil>, path = &ast.BasicLit{ValuePos:22, Kind:9, Value:"\"a\""}
import:name = aAlias, path = &ast.BasicLit{ValuePos:40, Kind:9, Value:"\"x/a\""}
import:name = ., path = &ast.BasicLit{ValuePos:55, Kind:9, Value:"\"c\""}
import:name = _, path = &ast.BasicLit{ValuePos:68, Kind:9, Value:"\"d\""}

后边的各种类型申明解析或表达式的解析,你都能够通过规范库里边提供的办法来进行测试,下边的我就不一一的展现测试了(测试方法一样,只是你须要打印的字段变一下就行了)

type 类型申明解析

当语法分析器获取到 _Type 这个 token 的时候,它就会调用 type 的解析办法去解析,同样在 fileOrNil()中,你能够看到如下代码:

......
case _Type:
            p.next()
            f.DeclList = p.appendGroup(f.DeclList, p.typeDecl)
......

它会在 appendGroup 中调用 typeDecl()办法,该办法就是依照 type 类型申明的文法去进行语法解析,这个在前边曾经介绍了。咱们晓得 type 有以下用法:

type a string
type b = string

下边看这个办法中具体做了哪些事件:

  • 创立一个 TypeDecl 的构造体(最终会被 append 到 File.DeclList 中)
  • 初始化构造体中的一些数据,比方解析到的 token 的地位信息、组 (group) 等
  • 下一个 token 是否是_Assign,如果是,则获取下一个 token
  • 验证下一个 token 的类型,比方是 chan、struct、map 还是 func 等(在 typeOrNil() 办法中实现的,其实就是一堆 switch case)
  • 返回 TypeDecl 构造

在获取最左边那个 token 的类型的时候,须要依据它的类型,持续往下解析。假如是一个 chan 类型,那就会创立一个 chan 类型的构造体,初始化这个 chan 类型的信息(在解析过程中,创立的每一种类型的构造体,都是一个结点)

你同样能够用 go/parser→ParseFile 来测试 type 的语法分析

const 类型申明解析

当语法分析器获取到 _Const 这个 token 的时候,它就会调用 const 的解析办法去解析,同样在 fileOrNil()中,你能够看到如下代码:

......
case _Const:
            p.next()
            f.DeclList = p.appendGroup(f.DeclList, p.constDecl)
......

const 有以下用法:

const A = 666
const B float64 = 6.66
const (
    _   token = iota
    _EOF       
    _Name    
    _Literal
)

而后就能够去看 constDecl 办法中的具体实现(其实依照 const 申明的文法进行解析)。这里就不再反复了,跟上边 type 的解析差不多,都是先创立相应构造体,而后记录该类型的一些信息。它不一样的中央就是,它有个名字列表,因为 const 能够同时申明多个。解析办法是 parser.go→nameList()

var 类型申明解析

当语法分析器获取到 _Var 这个 token 的时候,它就会调用 var 的解析办法去解析,同样在 fileOrNil()中,你能够看到如下代码:

......
case _Var:
            p.next()
            f.DeclList = p.appendGroup(f.DeclList, p.varDecl)
......

跟上边的两种申明一样,会调用相应的解析办法。var 不同的是,它的申明里边,可能波及表达式,所以在 var 的解析办法中波及到表达式的解析,我会在后边局部详细分析表达式的解析

函数申明解析实现

💡 阐明:后边会加个图来展现函数解析的过程

最初就是函数申明的解析。前边曾经提到,函数申明节点的构造如下:

FuncDecl struct {
        Pragma Pragma
        Recv   *Field // 接收者
        Name   *Name  // 函数名
        Type   *FuncType // 函数类型
        Body   *BlockStmt // 函数体
        decl
    }

在 fileOrNil()中,你能够看到如下代码:

case _Func:
            p.next()
            if d := p.funcDeclOrNil(); d != nil {f.DeclList = append(f.DeclList, d)
            }

解析函数的外围办法就是 funcDeclOrNil,因为函数的解析略微简单点,我这里把它的实现粘进去,通过正文来阐明每行代码在做什么

// 函数的文法
// FunctionDecl = "func" FunctionName (Function | Signature) .
// FunctionName = identifier .
// Function     = Signature FunctionBody .

// 办法的文法
// MethodDecl   = "func" Receiver MethodName (Function | Signature) .
// Receiver     = Parameters . // 办法的接收者
func (p *parser) funcDeclOrNil() *FuncDecl {
    if trace {defer p.trace("funcDecl")()}

    f := new(FuncDecl) // 创立函数申明类型的构造体(节点)f.pos = p.pos()
    f.Pragma = p.takePragma()

    if p.tok == _Lparen { // 如果匹配到了左小括号(阐明是办法)rcvr := p.paramList() // 获取接收者列表
        switch len(rcvr) {
        case 0:
            p.error("method has no receiver")
        default:
            p.error("method has multiple receivers")
            fallthrough
        case 1:
            f.Recv = rcvr[0]
        }
    }

    if p.tok != _Name { // 判断下一个 token 是否是标识符(即函数名)p.syntaxError("expecting name or (")
        p.advance(_Lbrace, _Semi)
        return nil
    }

    f.Name = p.name()
    f.Type = p.funcType() // 获取类型(下边持续理解其外部实现)if p.tok == _Lbrace { // 如果匹配到左中括号,则开始解析函数体
        f.Body = p.funcBody() // 解析函数体(下边持续理解其外部实现)}

    return f
}

函数解析局部比拟重要的两个实现:funcType()funcBody()。具体看他们外部做了什么?

/*
FuncType struct {ParamList  []*Field
        ResultList []*Field
        expr
}
*/
func (p *parser) funcType() *FuncType {
    if trace {defer p.trace("funcType")()}

    typ := new(FuncType) // 创立函数类型构造体(次要成员是参数列表和返回值列表)typ.pos = p.pos()
    typ.ParamList = p.paramList() // 获取参数列表(它返回的是一个 Field 构造体,它的成员是参数名和类型)typ.ResultList = p.funcResult() // 获取返回值列表(它返回的也是一个 Field 构造体)return typ
}
func (p *parser) funcBody() *BlockStmt {
    p.fnest++ // 记录函数的调用层级
    errcnt := p.errcnt // 记录以后的谬误数
    body := p.blockStmt("") // 解析函数体中的语句(具体实现持续往下看)p.fnest--

    // Don't check branches if there were syntax errors in the function
    // as it may lead to spurious errors (e.g., see test/switch2.go) or
    // possibly crashes due to incomplete syntax trees.
    if p.mode&CheckBranches != 0 && errcnt == p.errcnt {checkBranches(body, p.errh)
    }

    return body
}

func (p *parser) blockStmt(context string) *BlockStmt {
    if trace {defer p.trace("blockStmt")()}

    s := new(BlockStmt) // 创立函数体的构造
    s.pos = p.pos()

    // people coming from C may forget that braces are mandatory in Go
    if !p.got(_Lbrace) {p.syntaxError("expecting { after" + context)
        p.advance(_Name, _Rbrace)
        s.Rbrace = p.pos() // in case we found "}"
        if p.got(_Rbrace) {return s}
    }

    s.List = p.stmtList() // 开始解析函数体中的申明及表达式(这里边的实现就是依据获取的 token 来判断是哪种申明或语句,也是通过 switch case 来实现,依据匹配的类型进行相应文法的解析)s.Rbrace = p.pos()
    p.want(_Rbrace)

    return s
}

上边函数解析过程用图来展现一下,不便了解:

对于在函数体中的一些像赋值、for、go、defer、select 等等是如何解析,能够自行去看

总结

本文次要是从整体上分享了语法解析的过程,并且简略的展现了 type、const、func 申明解析的外部实现。其实语法解析里边还有表达式解析、包含其余的一些语法的解析,内容比拟多,没法一一介绍,感兴趣的小伙伴能够自行钻研

感激浏览,下一篇主题为:形象语法树构建

退出移动版