4.5 简短定义和多赋值

简短定义和多赋值是Go中最常用的语法糖,类似x2, x3 := x2+1, 1+2这种语法,本节我们将实现这个特性。

4.5.1 完善token包

简短定义和多赋值出现了2个新的Token类型::=,。token包增加相应的定义:

const (
	EOF TokenType = iota // = 0
	...
	DEFINE // :=
	COMMA  // ,
	...
)

var tokens = [...]string{
	DEFINE: ":=",
	COMMA:  ",",
}

其他定义保持不变。

4.5.2 完善lexer

Token的解析可以看作是简化版本的语法解析,在switch扩展相应的case即可:

func (p *Lexer) run() (tokens []token.Token) {
	...
	for {
		r := p.src.Read()
		...

		switch {
		case r == ':': // :, :=
			switch p.src.Read() {
			case '=':
				p.emit(token.DEFINE)
			default:
				p.errorf("unrecognized character: %#U", r)
			}
		case r == ',':
			p.emit(token.COMMA)
		...
		}
	}
}

完成后可以自行通过go run main.go lex file.ugo命令测试。

4.5.3 完善ast.AssignStmt

修改如下:

type AssignStmt struct {
	Target []*Ident        // 要赋值的目标
	OpPos  token.Pos       // ':=' 的位置
	Op     token.TokenType // '=' or ':='
	Value  []Expr          // 值
}

TargetValue属性改成了切片以支持多赋值。如果Optoken.DEFINE类型则表示简短定义。

4.5.4 完善语法解析

新增多表达式的解析方法:

// x, y :=
func (p *Parser) parseExprList() (exprs []ast.Expr) {
	for {
		exprs = append(exprs, p.parseExpr())
		if p.PeekToken().Type != token.COMMA {
			break
		}
		p.ReadToken()
	}
	return
}

然后调整Parser.parseStmt_block方法:

func (p *Parser) parseStmt_block() *ast.BlockStmt {
	...
Loop:
	for {
		...
		switch tok := p.PeekToken(); tok.Type {
		...
		default:
			// exprList ;
			// exprList := exprList;
			// exprList = exprList;
			exprList := p.parseExprList()
			switch tok := p.PeekToken(); tok.Type {
			case token.SEMICOLON:
				if len(exprList) != 1 {
					p.errorf(tok.Pos, "unknown token: %v", tok.Type)
				}
				block.List = append(block.List, &ast.ExprStmt{
					X: exprList[0],
				})
			case token.DEFINE, token.ASSIGN:
				p.ReadToken()
				exprValueList := p.parseExprList()
				if len(exprList) != len(exprValueList) {
					p.errorf(tok.Pos, "unknown token: %v", tok)
				}
				var assignStmt = &ast.AssignStmt{
					Target: make([]*ast.Ident, len(exprList)),
					OpPos:  tok.Pos,
					Op:     tok.Type,
					Value:  make([]ast.Expr, len(exprList)),
				}
				for i, target := range exprList {
					assignStmt.Target[i] = target.(*ast.Ident)
					assignStmt.Value[i] = exprValueList[i]
				}
				block.List = append(block.List, assignStmt)
			default:
				p.errorf(tok.Pos, "unknown token: %v", tok)
			}
		}
	}
	...
}

将之前的单赋值解析改造为多赋值解析,并支持简短定义。

4.5.5 完善代码生成

ast.AssignStmt节点的翻译在Compiler.compileStmt方法,改造后的代码:

func (p *Compiler) compileStmt(w io.Writer, stmt ast.Stmt) {
	switch stmt := stmt.(type) {
	case *ast.AssignStmt:
		var valueNameList = make([]string, len(stmt.Value))
		for i := range stmt.Target {
			valueNameList[i] = p.compileExpr(w, stmt.Value[i])
		}

		if stmt.Op == token.DEFINE {
			for _, target := range stmt.Target {
				if _, obj := p.scope.Lookup(target.Name); obj == nil {
					var mangledName = fmt.Sprintf("%%local_%s.pos.%d", target.Name, target.NamePos)
					p.scope.Insert(&Object{
						Name:        target.Name,
						MangledName: mangledName,
						Node:        target,
					})
					fmt.Fprintf(w, "\t%s = alloca i32, align 4\n", mangledName)
				}
			}
		}
		for i := range stmt.Target {
			var varName string
			if _, obj := p.scope.Lookup(stmt.Target[i].Name); obj != nil {
				varName = obj.MangledName
			} else {
				panic(fmt.Sprintf("var %s undefined", stmt.Target[0].Name))
			}

			fmt.Fprintf(
				w, "\tstore i32 %s, i32* %s\n",
				valueNameList[i], varName,
			)
		}
	...
	}

首先计算右侧的表达式列表(记住不能先处理表达式左边的目标)。然后判断是否为简短定义,如果是并且赋值的目标在当前Scope没有定义则定义新的局部变量(全局变量不支持简短定义)。最后处理表达式左边的目标,将右边的值依次保存到目标中。

4.5.6 测试

构造以下测试代码:

package main

var x1 int
var x2 int = 134

func main() {
	{
		x2, x3 := x2+1, 456
		println(x2)
		println(x3)
	}
	println(x1)
	println(x2)
}

执行结果如下:

$ go run main.go run ./_examples/hello.ugo 
135
456
0
135

结果正常。


© 2021-2022 | 柴树杉 保留所有权利