go语言的ast生成简述(解析初级表达式和条件语句) | 青训营笔记

84 阅读3分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 12 天

昨天讲到解析单目表达式的优先级高于双目表达式,而当单目表达式解析中没有发现代表单目表达式的前缀符号,那么就认为这个表达式是一个初级表达式(Primary Expression)。解析初级表达式的函数是parsePrimaryExpr,它是通过循环和switch来不断构建表达式的。函数有一个参数x ast.ExprparseUnaryExpr最后调用它的时候这个参数是nil,如果这个参数为空那么函数会调用另一个函数parseOperand来解析表达式的操作目。我们先不看函数parseOperand,先看它拥有了一个表达式之后还要进行怎样的处理。也就是循环和switch的部分,这部分其实就是解析表达式操作目解析后的后面的token是什么,比如表达式操作目后接操作符.一般代表一个选择子表达式或是类型断言转换表达式,因此其后面要么接一个标识符代表对某个属性进行选择,要么是一个左圆括号后面跟一个类型表达式这种类型断言。还有可能是一个操作符[,代表一个索引、切片或是实例(这里的实例应该是代表泛型中的类型实例这种,因为在其调用的函数)。也可能是一个(操作符代表一个函数调用或是一个类型转换表达式。如果接的是{那一般可以认为是类型后面跟着复合数据类型的字面量,因此在这个case最后会调用从这里也可以看出循环的作用,是为了将多个选择子和索引等一起解析进去组成完整的表达式。那么如果解析时在循环时没有碰到上述的这几种token认为一个初级表达式的解析已经完成,将解析后的ast返回即可。

上面讲完,就将Simple Statement的解析大概过了一遍,接下来看一下不太简单的语句,这里先看一下分支语句的解析,这部分在函数parseIfStmt先回忆一下,在go语言中if分支语句,一个是分支中的语句必须作为块语句存在,一个是条件语句前可以还有一个简单语句做一下操作。这里的第一点是否让你看到一些端倪,那就是else-if在第一点是不成立的,但是实际在go语言中是支持else-if多条分支的。这几是因为一个是语言规范中是可以这样写的,并且在函数parseIfStmt中对这种后缀进行了特判和递归调用来解析。

func (p *parser) parseIfStmt() *ast.IfStmt {
	defer decNestLev(incNestLev(p))

	if p.trace {
		defer un(trace(p, "IfStmt"))
	}

	pos := p.expect(token.IF)

	// 在这里就是刚才提到的第二点
        // 在条件语句之前可以有一个简单语句做一些操作
        init, cond := p.parseIfHeader()
	body := p.parseBlockStmt()

	var else_ ast.Stmt
	if p.tok == token.ELSE {
		p.next()
		switch p.tok {
                // 当else后接if关键字时要转换成else-if多条件分支
                // 对if分支语句进行递归调用
                // 因此在ast的Else属性中可能有两种块语句或if分支语句
		case token.IF:
			else_ = p.parseIfStmt()
		case token.LBRACE:
			else_ = p.parseBlockStmt()
			p.expectSemi()
		default:
			p.errorExpected(p.pos, "if statement or block")
			else_ = &ast.BadStmt{From: p.pos, To: p.pos}
		}
	} else {
		p.expectSemi()
	}

	return &ast.IfStmt{If: pos, Init: init, Cond: cond, Body: body, Else: else_}
}

从go语言的规范中也可以看到go的语法定义也是这样的。

IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .