Are you sure you want to delete this task? Once this task is deleted, it cannot be recovered.
chai2010 8c88218523 | 1 year ago | |
---|---|---|
.. | ||
examples | 1 year ago | |
readme.md | 1 year ago |
语句近似看作是函数体内可独立执行的代码,语句块是由大括弧定义的语句容器,语句块和语句只能在函数体内部定义。本章节我们学习语句块和语句的语法树构造。
语句块和语句是在函数体部分定义,函数体就是一个语句块。语句块的语法规范如下:
FunctionBody = Block .
Block = "{" StatementList "}" .
StatementList = { Statement ";" } .
Statement = Declaration | LabeledStmt | SimpleStmt
| GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt
| FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt
| DeferStmt
.
FunctionBody函数体对应一个Block语句块。每个Block语句块内部由多个语句列表StatementList组成,每个语句之间通过分号分隔。语句又可分为声明语句、标签语句、普通表达式语句和其它诸多控制流语句。需要注意的是,Block语句块也是一种合法的语句,因此函数体实际上是由Block组成的多叉树结构表示,每个Block结点又可以递归保存其他的可嵌套Block的控制流等语句。
一个最简单的函数不仅仅没有任何的输入参数和返回值,函数体中也没有任何的语句。下面代码分析func main() {}
函数体语法树结构:
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", src, parser.AllErrors)
if err != nil {
log.Fatal(err)
return
}
ast.Print(nil, f.Decls[0].(*ast.FuncDecl).Body)
}
const src = `package pkgname
func main() {}
`
函数的声明由ast.FuncDecl
结构体定义,其中的Body成员是ast.BlockStmt
类型。ast.BlockStmt
类型的定义如下:
type Stmt interface {
Node
// contains filtered or unexported methods
}
type BlockStmt struct {
Lbrace token.Pos // position of "{"
List []Stmt
Rbrace token.Pos // position of "}"
}
语句由ast.Stmt接口表示,各种具体的满足ast.Stmt接口的类型大多会以Stmt为后缀名。其中BlockStmt语句块也是一种语句,BlockStmt其实是一个语句容器,其中List成员是一个[]ast.Stmt
语句列表。
func main() {}
函数体部分输出的语法树结果如下:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . Rbrace: 30
3 }
表示函数体没有任何其它的语句。
因为由大括弧定义的语句块也是一种合法的语句,因此我们可以在函数体再定义任意个空的语句块:
func main() {
{}
{}
}
再次分析函数体的语法树,可以得到以下的结果:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 2) {
3 . . 0: *ast.BlockStmt {
4 . . . Lbrace: 32
5 . . . Rbrace: 33
6 . . }
7 . . 1: *ast.BlockStmt {
8 . . . Lbrace: 36
9 . . . Rbrace: 37
10 . . }
11 . }
12 . Rbrace: 39
13 }
其中List部分有两个新定义的语句块,每个语句块依然是ast.BlockStmt类型。函数体中的语句块构成的语法树和类型中的语法树结构是很相似的,但是语句的语法树最大的特点是可以循环递归定义,而类型的语法树不能递归定义自身(语义层面禁止)。
实际上定义空的语句块并不能算真正的语句,它只是在编译阶段定义新的变量作用域,并没有产生新的语句或计算。最简单的语句是表达式语句,不管是简单表达式还是复杂的表达式都可以作为一个独立的语句。表达式语句语法规范如下:
ExpressionStmt = Expression .
其实一个表达式语句就是对应一个表达式,而关于表达式的语法我们已经学习过。我们这里以一个最简单的常量作为标识符,来研究表达式语句的语法结构。下面是只有一个常量表达式语句的main函数:
func main() {
42
}
输出的语句的语法树如下:
chai-mba:02 chai$ go run main.go
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.ExprStmt {
4 . . . X: *ast.BasicLit {
5 . . . . ValuePos: 32
6 . . . . Kind: INT
7 . . . . Value: "42"
8 . . . }
9 . . }
10 . }
11 . Rbrace: 35
12 }
表达式语句由ast.ExprStmt
结构体定义:
type ExprStmt struct {
X Expr // expression
}
它只是ast.Expr
表达式的再次包装,以满足ast.Stmt
接口。因为ast.Expr
表达式本身也是一个接口类型,因此可以包含任意复杂的表达式。表达式语句最终会产生一个值,但是表达式的值没有被赋值到变量,因此表达式的返回值会被丢弃。不过表达式中可能还有函数调用,而函数调用可能有其它的副作用,因此表达式语句一般常用于触发函数调用。
表达式不仅仅可以作为独立的表达式语句,同时表达式也是其它更复杂控制流语句的组成单元。对于函数比较重要的控制流语句是返回语句,返回语句的语法规范如下:
ReturnStmt = "return" [ ExpressionList ] .
ExpressionList = Expression { "," Expression } .
返回语句以return关键字开始,后面跟着多个以逗号分隔的表达式,当然也可以没有返回值。下面例子在main函数增加一个返回两个值的返回语句:
func main() {
return 42, err
}
当然,按照Go语言规范main函数是没有返回值的,因此return语句也不能有返回值。不过我们目前还处在语法树解析阶段,并不会检查返回语句和函数的返回值类型是否匹配,这种类型匹配检查要在语法树构建之后才会进行。
main函数体的语法树结果如下:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.ReturnStmt {
4 . . . Return: 32
5 . . . Results: []ast.Expr (len = 2) {
6 . . . . 0: *ast.BasicLit {
7 . . . . . ValuePos: 39
8 . . . . . Kind: INT
9 . . . . . Value: "42"
10 . . . . }
11 . . . . 1: *ast.Ident {
12 . . . . . NamePos: 43
13 . . . . . Name: "err"
14 . . . . }
15 . . . }
16 . . }
17 . }
18 . Rbrace: 47
19 }
返回语句由ast.ReturnStmt
类型表示,其中Results成员对应返回值列表,这里分别是基础的数值常量42和标识符err。ast.ReturnStmt
类型定义如下:
type ReturnStmt struct {
Return token.Pos // position of "return" keyword
Results []Expr // result expressions; or nil
}
其中Return成员表示return关键字的位置,Results成员对应一个表达式列表,如果为nil表示没有返回值。
函数中除了输入参数和返回值参数之外,还可以定义临时的局部变量保存函数的状态。如果临时变量被闭包函数捕获,那么临时变量维持的函数状态将伴随闭包函数的整个生命周期。因此声明变量和声明函数一样重要。声明变量的声明语法和顶级包变量的语法是类似的:
Declaration = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
其中Declaration就是函数体内部的声明语法,可以在函数内部声明常量、变量和类型,但是不能声明函数和方法。关于TopLevelDecl定义顶级常量、变量和类型声明我们已经讨论过,其中已经包含了函数内部的声明语法。我们这里以一个简单的例子展示如何在语句块中保存声明语句:
func main() {
var a int
}
在main函数内部定义一个int类型变量,这个语法格式和全局变量的定义是一样的。语法树解析输出如下:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.DeclStmt {
4 . . . Decl: *ast.GenDecl {
5 . . . . TokPos: 32
6 . . . . Tok: var
7 . . . . Lparen: 0
8 . . . . Specs: []ast.Spec (len = 1) {
9 . . . . . 0: *ast.ValueSpec {
10 . . . . . . Names: []*ast.Ident (len = 1) {
11 . . . . . . . 0: *ast.Ident {
12 . . . . . . . . NamePos: 36
13 . . . . . . . . Name: "a"
14 . . . . . . . . Obj: *ast.Object {...}
20 . . . . . . . }
21 . . . . . . }
22 . . . . . . Type: *ast.Ident {
23 . . . . . . . NamePos: 38
24 . . . . . . . Name: "int"
25 . . . . . . }
26 . . . . . }
27 . . . . }
28 . . . . Rparen: 0
29 . . . }
30 . . }
31 . }
32 . Rbrace: 42
33 }
声明的变量在ast.DeclStmt
结构体中表示,结构体定义如下:
type DeclStmt struct {
Decl Decl // *GenDecl with CONST, TYPE, or VAR token
}
虽然Decl成员是ast.Decl
类型的接口,但是注释已经明确表示只有常量、类型和变量几种声明,并不包含函数和方法的声明。因此,Decl成员只能是ast.GenDecl
类型。
函数内变量还可以采用短声明方式。短声明语法和多赋值语句类似,它是在声明变量的同时进行多赋值初始化,变量的类型从赋值表达式自动推导。短声明和多赋值语句语法规范如下:
Assignment = ExpressionList assign_op ExpressionList .
ShortVarDecl = IdentifierList ":=" ExpressionList .
其中多赋值语句的左边是一个表达式列表,而短声明语句的左边是一组标识符列表。短声明和多赋值语句的右边都是一组表达式列表。我们以一个短声明多个变量来展示短声明和多赋值语句的语法树:
func main() {
a, b := 1, 2
}
输出的语法树结果如下:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.AssignStmt {
4 . . . Lhs: []ast.Expr (len = 2) {
5 . . . . 0: *ast.Ident {
6 . . . . . NamePos: 32
7 . . . . . Name: "a"
8 . . . . . Obj: *ast.Object {
9 . . . . . . Kind: var
10 . . . . . . Name: "a"
11 . . . . . . Decl: *(obj @ 3)
12 . . . . . }
13 . . . . }
14 . . . . 1: *ast.Ident {
15 . . . . . NamePos: 35
16 . . . . . Name: "b"
17 . . . . . Obj: *ast.Object {
18 . . . . . . Kind: var
19 . . . . . . Name: "b"
20 . . . . . . Decl: *(obj @ 3)
21 . . . . . }
22 . . . . }
23 . . . }
24 . . . TokPos: 37
25 . . . Tok: :=
26 . . . Rhs: []ast.Expr (len = 2) {
27 . . . . 0: *ast.BasicLit {
28 . . . . . ValuePos: 40
29 . . . . . Kind: INT
30 . . . . . Value: "1"
31 . . . . }
32 . . . . 1: *ast.BasicLit {
33 . . . . . ValuePos: 43
34 . . . . . Kind: INT
35 . . . . . Value: "2"
36 . . . . }
37 . . . }
38 . . }
39 . }
40 . Rbrace: 45
41 }
短声明和多赋值语句都通过ast.AssignStmt
结构体表达,其定义如下:
type AssignStmt struct {
Lhs []Expr
TokPos token.Pos // position of Tok
Tok token.Token // assignment token, DEFINE
Rhs []Expr
}
其中Lhs表示左边的表达式或标识符列表,而Rhs表示右边的表达式列表。短声明和多赋值语句是通过Tok来进行区分。
顺序、分支和循环是编程语言中三种基本的控制流语句。Go语言的if语句语法规范如下:
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
分支由if关键字开始,首先是可选的SimpleStmt简单初始化语句(可以是局部变量短声明、赋值或表达式等语句),然后是if的条件表达式,最后是分支的主体部分。分支的主体Block为一个语句块,其中可以包含多个语句或嵌套其它的语句块。同时if可以携带一个else分支,对应分支条件为假的情况。
我们以一个不带短声明的if/else为例:
func main() {
if true {} else {}
}
输出的语法树如下:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.IfStmt {
4 . . . If: 32
5 . . . Cond: *ast.Ident {
6 . . . . NamePos: 35
7 . . . . Name: "true"
8 . . . }
9 . . . Body: *ast.BlockStmt {
10 . . . . Lbrace: 40
11 . . . . Rbrace: 41
12 . . . }
13 . . . Else: *ast.BlockStmt {
14 . . . . Lbrace: 48
15 . . . . Rbrace: 49
16 . . . }
17 . . }
18 . }
19 . Rbrace: 51
20 }
if由ast.IfStmt
结构体表示,其中的Cond为分支的条件表达式,Body为分支的主体语句块,Else为补充的语句块。ast.IfStmt
结构体完整定义如下:
type IfStmt struct {
If token.Pos // position of "if" keyword
Init Stmt // initialization statement; or nil
Cond Expr // condition
Body *BlockStmt
Else Stmt // else branch; or nil
}
除了分支调整、主体块、补充块,还有Init用于初始化部分。需要注意的是Else都被定义为ast.Stmt
接口类型,而Body被明确定义为ast.BlockStmt
类型,是否是想以接口类型来暗示else可能为空的情况。
Go语言中只有一种for循环语句,但是for语句的语法却最为复杂。for语句的语法规范如下:
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
分析语法规范,可以对应以下四种类型:
for {}
for true {}
for i := 0; true; i++ {}
for i, v := range m {}
其中第一个没有循环条件,默认条件是true,因此和第二个循环语句一样都是死循环。而第一和第二个循环语句其实是第三个经典循环结构的特例,在第三个循环语句中增加了初始化语句和循环迭代语句。最后第四个循环语句是一种新的循环结构,主要用于数组、切片和map的迭代。以上四个循环语句可以再次归纳为以下两种:
for x; y; z {}
for x, y := range z {}
除了map只能通过for range
迭代之外(如果借助标准包,可以通过reflect.MapKeys
或reflect.MapRange
等方式迭代循环map),其它的for range
格式的循环都可以通过for x; y; z {}
经典风格的循环替代。
因此我们先分析经典风格的for x; y; z {}
循环:
func main() {
for x; y; z {}
}
其语法树结构如下:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.ForStmt {
4 . . . For: 32
5 . . . Init: *ast.ExprStmt {
6 . . . . X: *ast.Ident {
7 . . . . . NamePos: 36
8 . . . . . Name: "x"
9 . . . . }
10 . . . }
11 . . . Cond: *ast.Ident {
12 . . . . NamePos: 39
13 . . . . Name: "y"
14 . . . }
15 . . . Post: *ast.ExprStmt {
16 . . . . X: *ast.Ident {
17 . . . . . NamePos: 42
18 . . . . . Name: "z"
19 . . . . }
20 . . . }
21 . . . Body: *ast.BlockStmt {
22 . . . . Lbrace: 44
23 . . . . Rbrace: 45
24 . . . }
25 . . }
26 . }
27 . Rbrace: 47
28 }
ast.ForStmt
结构体表示经典的for循环,其中Init、Cond、Post和Body分别对应初始化语句、条件语句、迭代语句和循环体语句。ast.ForStmt
结构体的定义如下:
type ForStmt struct {
For token.Pos // position of "for" keyword
Init Stmt // initialization statement; or nil
Cond Expr // condition; or nil
Post Stmt // post iteration statement; or nil
Body *BlockStmt
}
其中条件部分必须是表达式,初始化和迭代部分可以是普通的语句(普通语句是短声明和多赋值等,不能包含分支等复杂语句)。
在了解了经典风格的循环之后,我们再来看看最简单的for range
循环:
func main() {
for range ch {}
}
我们省略了循环中的Key和Value部分。其语法树如下:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.RangeStmt {
4 . . . For: 32
5 . . . TokPos: 0
6 . . . Tok: ILLEGAL
7 . . . X: *ast.Ident {
8 . . . . NamePos: 42
9 . . . . Name: "ch"
10 . . . }
11 . . . Body: *ast.BlockStmt {
12 . . . . Lbrace: 45
13 . . . . Rbrace: 46
14 . . . }
15 . . }
16 . }
17 . Rbrace: 48
18 }
for range
循环的语法树由ast.RangeStmt
结构表示,其完整定义如下:
type RangeStmt struct {
For token.Pos // position of "for" keyword
Key, Value Expr // Key, Value may be nil
TokPos token.Pos // position of Tok; invalid if Key == nil
Tok token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE
X Expr // value to range over
Body *BlockStmt
}
其中Key和Value对应循环时的迭代位置和值,X成员是生成要循环对象的表达式(可能是数组、切片、map和管道等),Body表示循环体语句块。另外,Tok成员可以区别Key和Value是多赋值语句还是短变量声明语句。
和分支语句类似,类型识别也有两种:类型断言和类型switch。类型断言类似分支的if语句,通过多个if/else组合类型断言就可以模拟出类型switch。因此我们重点学习类型断言部分,下面是类型断言的语法规范:
PrimaryExpr = PrimaryExpr TypeAssertion.
TypeAssertion = "." "(" Type ")" .
类型断言是在一个表达式之后加点和小括弧定义,其中小括弧中的是期望查询的类型。从Go语言语义角度看,类型断言开始的表达式必须产生一个接口类型的值。不过在语法树阶段并不会做详细的语义检查。
下面的例子在main函数中定义一个最简单的类型断言:
func main() {
x.(int)
}
对x做类型断言,如果成功则返回x里面存储的int类型的值,如果失败则抛出异常。生成的语法树如下:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.ExprStmt {
4 . . . X: *ast.TypeAssertExpr {
5 . . . . X: *ast.Ident {
6 . . . . . NamePos: 32
7 . . . . . Name: "x"
8 . . . . }
9 . . . . Lparen: 34
10 . . . . Type: *ast.Ident {
11 . . . . . NamePos: 35
12 . . . . . Name: "int"
13 . . . . }
14 . . . . Rparen: 38
15 . . . }
16 . . }
17 . }
18 . Rbrace: 40
19 }
需要注意语法树的结构:首先是ast.ExprStmt
结构体表示的表达式语句,其中的X成员才是对应类型断言表达式。类型断言由ast.TypeAssertExpr
结构体表示,其定义如下:
type TypeAssertExpr struct {
X Expr // expression
Lparen token.Pos // position of "("
Type Expr // asserted type; nil means type switch X.(type)
Rparen token.Pos // position of ")"
}
其中X成员是类型断言的主体表达式(产生一个接口值),Type成员是类型的表达式。如果Type为nil,则表示对应x.(type)
形式的断言,这是类型switch中使用的形式。
go和defer语句是Go语言中最有特色的语句,它们的语法结构也是非常相似的。下面是go和defer语句的语法规范:
GoStmt = "go" Expression .
DeferStmt = "defer" Expression .
简而言之,就是在go和defer关键字后面跟一个表达式,不过这个表达式必须是函数或方法调用。go和defer语句在语法树中分别以ast.GoStmt
和ast.DeferStmt
结构定义:
type GoStmt struct {
Go token.Pos // position of "go" keyword
Call *CallExpr
}
type DeferStmt struct {
Defer token.Pos // position of "defer" keyword
Call *CallExpr
}
其中都有一个Call成员表示函数或方法调用。下面以go语句为例:
func main() {
go hello("光谷码农")
}
其对应的语法树结果:
0 *ast.BlockStmt {
1 . Lbrace: 29
2 . List: []ast.Stmt (len = 1) {
3 . . 0: *ast.GoStmt {
4 . . . Go: 32
5 . . . Call: *ast.CallExpr {
6 . . . . Fun: *ast.Ident {
7 . . . . . NamePos: 35
8 . . . . . Name: "hello"
9 . . . . }
10 . . . . Lparen: 40
11 . . . . Args: []ast.Expr (len = 1) {
12 . . . . . 0: *ast.BasicLit {
13 . . . . . . ValuePos: 41
14 . . . . . . Kind: STRING
15 . . . . . . Value: "\"光谷码农\""
16 . . . . . }
17 . . . . }
18 . . . . Ellipsis: 0
19 . . . . Rparen: 55
20 . . . }
21 . . }
22 . }
23 . Rbrace: 57
24 }
除了ast.GoStmt
结构体,Call成员部分和表达式中函数调用的语法树结构完全一样。
数据结构是程序状态的载体,语句是程序算法的灵魂。在了解了语句的语法树之后,我们就可以基于语法树对代码做很多事情,比如特殊模式的BUG检查、生成文档或特定平台的可执行代码等,甚至我们可以基于语法树解释执行Go语言程序。
《Go2编程指南》开源图书,重点讲解Go2新特性,以及Go1教程中较少涉及的特性
Go C Handlebars JavaScript Markdown other
Dear OpenI User
Thank you for your continuous support to the Openl Qizhi Community AI Collaboration Platform. In order to protect your usage rights and ensure network security, we updated the Openl Qizhi Community AI Collaboration Platform Usage Agreement in January 2024. The updated agreement specifies that users are prohibited from using intranet penetration tools. After you click "Agree and continue", you can continue to use our services. Thank you for your cooperation and understanding.
For more agreement content, please refer to the《Openl Qizhi Community AI Collaboration Platform Usage Agreement》