6.1 return语句

µGo的后端是LLVM-IR,天然支持了递归函数调用。但是因为我们目前只实现了变量定义、分支和循环等基本特性,函数甚至缺少返回语句。在还没有指针和数组等高级数据结构等前提下,如果想要展示递归函数调用的能力,我们需要先实现一个带返回值的return语句。

6.1.1 添加 token.RETURN 记号

首先是为return添加一个新记号类型:

package token

// 记号类型
type TokenType int

// ch3中 µGo程序用到的记号类型
const (
	EOF TokenType = iota // = 0

	...
	RETURN // return
	...
)

更新每个记号类型对应的字符串(方便打印):

var tokens = [...]string{
	RETURN: "return",
}

注册关键字:

var keywords = map[string]TokenType{
	"return": RETURN,
}

token包添加关键字的套路几乎是一样的。

6.1.2 完善 lexer 的分号处理

token包添加return关键字之后,词法解析部分其实已经可以正常解析了。不过在return语句之后可能遇到省略的情况,添加return的处理:

func (p *Lexer) run() (tokens []token.Token) {
	...
	for {
		r := p.src.Read()
		...
		switch {
		case r == '\n':
			p.src.IgnoreToken()
			if len(p.tokens) > 0 {
				switch p.tokens[len(p.tokens)-1].Type {
				case token.RPAREN, token.IDENT, token.NUMBER, token.RETURN:
					p.emit(token.SEMICOLON)
				}
			}
		...
		}
	...
}

6.1.3 AST 添加 ReturnStmt 节点

为语法树添加表示 return 语句的节点:

type ReturnStmt struct {
	Return token.Pos
	Result Expr
}

uGo为了简化,目前只支持可选的单个返回值。

6.1.4 解析 return 语句

修改 parser 包解析语句的 Parser.parseStmt 方法:

func (p *Parser) parseStmt() ast.Stmt {
	switch tok := p.PeekToken(); tok.Type {
	...
	case token.RETURN:
		return p.parseStmt_return()
	case token.VAR:
	...
	}
}

return 语言通过新加的 Parser.parseStmt_return 方法解析:

func (p *Parser) parseStmt_return() *ast.ReturnStmt {
	tokReturn := p.MustAcceptToken(token.RETURN)

	retStmt := &ast.ReturnStmt{
		Return: tokReturn.Pos,
	}
	if _, ok := p.AcceptToken(
		token.SEMICOLON, // ;
		token.LBRACE,    // {
		token.RBRACE,    // }
	); !ok {
		retStmt.Result = p.parseExpr()
	} else {
		p.UnreadToken()
	}

	return retStmt
}

在 return 关键字之后如果不是表达式则没有返回值,否则通过 p.parseExpr() 解析返回值。

6.1.5 翻译 return 语句

改在 compiler 包的 Compiler.compileStmt 方法,增加 return 语句的翻译:

func (p *Compiler) compileStmt(w io.Writer, stmt ast.Stmt) {
	switch stmt := stmt.(type) {
	case *ast.ReturnStmt:
		p.compileStmt_return(w, stmt)

	case *ast.IfStmt:
	...
	}
}

输出 LLVM-IR 汇编语言返回语句在 Compiler.compileStmt_return 方法实现:

func (p *Compiler) compileStmt_return(w io.Writer, stmt *ast.ReturnStmt) {
	if stmt.Result != nil {
		fmt.Fprintf(w, "\tret i32 %v\n", p.compileExpr(w, stmt.Result))
	} else {
		fmt.Fprintf(w, "\tret i32 0\n")
	}
}

如果有返回值,则通过 p.compileExpr(w, stmt.Result) 编译返回值表达式并返回。如果没有返回值则默认返回int32类型的0

6.1.6 测试迭代版的斐波那契

基于分支和循环构造一个迭代版的斐波那契:

package main

func main() {
	var n1 = 0
	var n2 = 1
	var n3 = 1

	println(n3)
	for i := 1; i < 20; i = i + 1 {
		n3 = n1 + n2
		n1 = n2
		n2 = n3

		if n3 > 100 {
			return
		}

		println(n3)
	}
}

并通过if语句判断数列的值大于100时,通过return语句退出main函数。执行如下:

$ go run main.go -debug run ./_examples/fib1.ugo
1
1
2
3
5
8
13
21
34
55
89

结果正常。


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