这是我参与「第五届青训营」伴学笔记创作活动的第 10 天
继续看函数声明解析的部分,解析完了函数签名和函数名字之后,要开始对函数体进行解析了,函数声明的函数体是一个块语句ast.BlockStmt,其通过Parser结构体的方法parseBody进行解析。这个函数的逻辑十分简单,我们知道块语句在语法上就是花括号包裹的语句序列,因此只要解析这些语法单元即可。parseBody函数调用parseStmtList来解析语句列表,parseStmtList内部的逻辑也很直观,就是不断解析语句直到发现语法错误,或是达到块语句的结尾或文件结尾。那么真正处理语句解析的函数就是parseStmtList在for循环中调用的parseStmt,要注意到的是块语句本身也是一种语句所以解析肯定存在递归调用,这时就有注意token上的成对出现,在数量上不能出现错误,并且辨别一些语法上的二义性,这部分可以在parseStmt的第一个defer中可以体现,defer decNestLev(incNestLev(p)),这里还可以看出go语言对defer调用的函数的参数的计算是原地立即计算的。
func (p *parser) parseBody() *ast.BlockStmt {
if p.trace {
defer un(trace(p, "Body"))
}
// 读取左括号
lbrace := p.expect(token.LBRACE)
// 读取块语句中的语句列表
list := p.parseStmtList()
// 读取右括号
rbrace := p.expect2(token.RBRACE)
return &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace}
}
func (p *parser) parseStmtList() (list []ast.Stmt) {
if p.trace {
defer un(trace(p, "StatementList"))
}
// 当读取的token不是switch内部的case和default关键字
// 且不是一个块语句的结束括号或文件结尾
// 不断尝试读取语句
for p.tok != token.CASE && p.tok != token.DEFAULT && p.tok != token.RBRACE && p.tok != token.EOF {
list = append(list, p.parseStmt())
}
return
}
由于go语言的语法解析器是递归下降的方式处理,那其底层的逻辑其实就是look ahead。因此在函数parseStmt中首先就使用一个switch语句根据读入的第一个token分出接下来应该大概进行怎样的解析。例如,如果读取到的是type、var、const关键字那么接下来就应该解析声明语句。
case token.CONST, token.TYPE, token.VAR:
s = &ast.DeclStmt{Decl: p.parseDecl(stmtStart)}
当读取到时是可以作为单目操作符的操作符、复合数据类型开头的关键字或可以作为表达式开头的关键字、字面量和操作符时,使用简单语句的解析方法解析。
case
// tokens that may start an expression
token.IDENT, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // 操作目
token.LBRACK, token.STRUCT, token.MAP, token.CHAN, token.INTERFACE, // 复合数据类型的关键字和实例的操作符
token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT: // 可以用做单目操作符的符号
s, _ = p.parseSimpleStmt(labelOk)
// 对标签语句特殊处理,因为标签以冒号结尾,不能有分号
if _, isLabeledStmt := s.(*ast.LabeledStmt); !isLabeledStmt {
p.expectSemi()
}
对于其他的分支语句、循环语句、返回语句等语句就是调用相应的语法解析函数进行处理。从上面的语句可以看出parseSimpleStmt就是负责一些单一语句的的解析,这里的单一语句可以是一个赋值/列表赋值/短变量声明/操作赋值语句,也可以是一个自增/自减语句或是单个表达式的语句(如函数调用语句,这里就有函数作为first class value定义的语句即function literal)或是前面提到要特殊处理的标签语句。这里不管处理左值还是右值的表达式都是调用parseList函数批量完成的。这个函数内部调用的就是parseExprList,在go语言的语法解析时,为了区分并检查左值右值的一一对应其在数据结构中存储了这部分状态,通过在函数调用时判断是否为空判断正在处理一个左值还是右值列表,并且为了保证嵌套时解析语句左值右值不会发生错误,要在函数调用中保存上下文在做状态改变操作。
func (p *parser) parseList(inRhs bool) []ast.Expr {
// 保存上下文状态
old := p.inRhs
// 本次表达式解析使用的是解析左值还是右值
p.inRhs = inRhs
// 解析表达式列表
list := p.parseExprList()
// 还原上下文状态
p.inRhs = old
return list
}
在调用parseExprList函数的内部其逻辑就是使用一个循环不断地解析表达式并检查一个表达式不是一个类型。通过检查当一个表达式被解析完成后接着一个逗号操作符来判断是否继续循环,这也意味着go语言是不支持逗号表达式的,因为这样会产生语法的二义性。
p.checkExpr(p.parseExpr())
函数parseExpr内部调用函数parseBinaryExpr,这个函数会判断一个表达式是一个单目操作的表达式、双目操作的表达式或是单个表达式。下一篇继续解析这个函数