- KusonStack一站式可编程配置技术栈(Go): https://github.com/KusionStack/kusion
- KCL 配置编程语言(Rust): https://github.com/KusionStack/KCLVM
- 凹语言™: https://github.com/wa-lang/wa
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 // 值
}
将Target
和Value
属性改成了切片以支持多赋值。如果Op
为token.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
结果正常。