- KusonStack一站式可编程配置技术栈(Go): https://github.com/KusionStack/kusion
- KCL 配置编程语言(Rust): https://github.com/KusionStack/KCLVM
- 凹语言™: https://github.com/wa-lang/wa
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
结果正常。