前言
在上一篇文章中,分享了Go编译器是如何将源文件解析成Token的。本文次要是分享,语法分析阶段是如何依据不同的Token来进行语法解析的。本文你能够理解到以下内容:
- Go语法分析整体概览
- Go语法解析具体过程
- 示例展现语法解析残缺过程
Tips:文中会波及到文法、产生式相干内容,如果你不太理解,请先看一下前边的词法剖析&语法分析根底这篇文章
文章比拟长,但代码比拟多,次要是不便了解。置信看完肯定有所播种
Go语法分析整体概览
为了不便后边的了解,我这里提供一个源文件,后边的内容,你都能够依照这个源文件中的内容,带入去了解:
package mainimport ( "fmt" "go/token")type aType stringconst A = 666var B = 888func 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是可选的,可有可无,这就是中括号的含意)。也就是说,每一个源文件,都应该是合乎这个文法规定的首先是包申明:PackageClausePackageClause = "package" PackageName . PackageName = identifier . PackageClause是由一个终结符package和一个非终结符PackageName形成的,而PackageName由一个标识符形成的所以,在扫描源文件的时候,应该会最先获取到的是package的Token,而后是一个标识符的Token。解析完package申明之后,后边就应该是导入申明而后是导入申明:ImportDeclImportDecl = "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这个包起个别名叫aAliasimport . "c" // 将依赖包的公开符号间接导入到以后文件的名字空间import _ "d" // 只是导入依赖包触发其包的初始化动作,然而不导入任何符号到以后文件名字空间
因为篇幅的起因,我就不把importDecl()办法的源代码粘进去了。我这里梳理进去它次要干了哪些事件:
- 创立一个ImportDeal的构造体(最终会被append到File.DeclList中)
- 初始化构造体中的一些数据,比方解析到的token的地位信息、组(group)等
- 而后去匹配下一个token,看它是
_Name
的token类型(标识符),还是_Dot(.)
- 如果获取到的token是
_Name,
则获取到包名,如果获取到的是_Dot(.)
,则新建一个名字. - 而后就是匹配包门路,次要是由
oliteral()
办法实现 - 返回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 mainimport ( "fmt" "go/parser" "go/token")const src = `package testimport "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 stringtype 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 = 666const B float64 = 6.66const ( _ 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申明解析的外部实现。其实语法解析里边还有表达式解析、包含其余的一些语法的解析,内容比拟多,没法一一介绍,感兴趣的小伙伴能够自行钻研
感激浏览,下一篇主题为:形象语法树构建