- KusonStack一站式可编程配置技术栈(Go): https://github.com/KusionStack/kusion
- KCL 配置编程语言(Rust): https://github.com/KusionStack/KCLVM
- 凹语言™: https://github.com/wa-lang/wa
5.2 完善AST和解析器
本节内容和前章套路类似:先准备AST然后配合新语法的解析即可。
5.2.1 完善AST节点
主要是针对if和for节点,均满足ast.Stmt
接口。if节点定义如下:
// IfStmt 表示一个 if 语句节点.
type IfStmt struct {
If token.Pos // if 关键字的位置
Init Stmt // 初始化语句
Cond Expr // if 条件, *BinaryExpr
Body *BlockStmt // if 为真时对应的语句列表
}
字段含义如注释所示。需要注意的是uGo为了简化并没有支持else语句。
for循环的AST节点如下:
// ForStmt 表示一个 for 语句节点.
type ForStmt struct {
For token.Pos // for 关键字的位置
Init Stmt // 初始化语句
Cond Expr // 条件表达式
Post Stmt // 迭代语句
Body *BlockStmt // 循环对应的语句列表
}
是一个标准的C语言风格的迭代循环。
5.2.2 重构 block 和 stmt 解析
在第4章我们实现了对Block和多赋值的支持,在 Parser.parseStmt_block
方法实现。在增加if和for解析之前我们先重构简化该方法:
func (p *Parser) parseStmt_block() *ast.BlockStmt {
...
Loop:
for {
switch tok := p.PeekToken(); tok.Type {
case token.EOF:
break Loop
case token.ERROR:
p.errorf(tok.Pos, "invalid token: %s", tok.Literal)
case token.SEMICOLON:
p.AcceptTokenList(token.SEMICOLON)
default:
if stmt := p.parseStmt(); stmt != nil {
block.List = append(block.List, stmt)
} else {
break Loop
}
}
}
...
}
我们将正常的语句解析封装到独立的 Parser.parseStmt
方法实现,如果没有语句则退出循环。
parseStmt 方法实现如下:
func (p *Parser) parseStmt() ast.Stmt {
switch tok := p.PeekToken(); tok.Type {
...
case token.VAR:
return p.parseStmt_var()
case token.IF:
return p.parseStmt_if()
case token.FOR:
return p.parseStmt_for()
default:
return p.parseStmt_exprOrAssign()
}
}
其中针对if和for增加了响应的解析,解析方法的实现在稍后讲述。另一个新的 Parser.parseStmt_exprOrAssign
方法解析表达式或多赋值语句:
func (p *Parser) parseStmt_exprOrAssign() ast.Stmt {
// exprList ;
// exprList := exprList;
// exprList = exprList;
exprList := p.parseExprList()
switch tok := p.PeekToken(); tok.Type {
case token.SEMICOLON, token.LBRACE:
...
case token.DEFINE, token.ASSIGN:
...
default:
p.errorf(tok.Pos, "unknown token: %v", tok)
}
}
表达式或多赋值语句的解析代码重构之前在Parser.parseStmt_block
方法中 switch
语句的 default
部分实现。
5.2.3 if 语句解析
if
语句有一个可选的初始化语句,但是没有else
分支。有以下2种情形:
if x > 0 {}
if x := 1; x > 0 {}
if
解析如下:
func (p *Parser) parseStmt_if() *ast.IfStmt {
tokIf := p.MustAcceptToken(token.IF)
ifStmt := &ast.IfStmt{
If: tokIf.Pos,
}
stmt := p.parseStmt()
if _, ok := p.AcceptToken(token.SEMICOLON); ok {
ifStmt.Init = stmt
ifStmt.Cond = p.parseExpr()
ifStmt.Body = p.parseStmt_block()
} else {
ifStmt.Init = nil
if cond, ok := stmt.(*ast.ExprStmt); ok {
ifStmt.Cond = cond.X
} else {
p.errorf(tokIf.Pos, "if cond expect expr: %#v", stmt)
}
ifStmt.Body = p.parseStmt_block()
}
return ifStmt
}
首先通过我们新封装的 parseStmt
解析语句,然后根据后续 token 的类型选择不同的解析分支。如果后续的 token 是分号 ;
,则表示是有初始化语句的 if init; cond {}
风格的 if
,否则是普通的 C 语言风格的 if
。if 条件部分可能是由 parseStmt
方法解析得到(带初始化语句的),或者是显式调研 parseExpr
方法解析得到。if
的语句则通过 parseStmt_block
方法作为一个块语句解析。
5.2.4 for 语句解析
for
有以下几种情形:
for {}
for x > 10 {}
for x := 0; x < 10; x = x+1 {}
下面开始解析 for
语句:
func (p *Parser) parseStmt_for() *ast.ForStmt {
tokFor := p.MustAcceptToken(token.FOR)
forStmt := &ast.ForStmt{
For: tokFor.Pos,
}
// for {}
if _, ok := p.AcceptToken(token.LBRACE); ok {
p.UnreadToken()
forStmt.Body = p.parseStmt_block()
return forStmt
}
...
}
在 p.MustAcceptToken(token.FOR)
匹配完 for
关键字之后,向前查看下一个 Token 是什么类型。如果下一个 Token 是 {
则对应 for {}
风格的 for
语句。
然后继续解析 for cond {}
和 for Init?; Cond?; Post? {}
风格的 for
语句:
func (p *Parser) parseStmt_for() *ast.ForStmt {
...
// for Cond {}
// for Init?; Cond?; Post? {}
// for ; ...
if _, ok := p.AcceptToken(token.SEMICOLON); ok {
forStmt.Init = nil
// for ;; ...
if _, ok := p.AcceptToken(token.SEMICOLON); ok {
if _, ok := p.AcceptToken(token.LBRACE); ok {
// for ;; {}
p.UnreadToken()
forStmt.Body = p.parseStmt_block()
return forStmt
} else {
// for ; ; postStmt {}
forStmt.Post = p.parseStmt()
forStmt.Body = p.parseStmt_block()
return forStmt
}
} else {
// for ; cond ; ... {}
forStmt.Cond = p.parseExpr()
p.MustAcceptToken(token.SEMICOLON)
if _, ok := p.AcceptToken(token.LBRACE); ok {
// for ; cond ; {}
p.UnreadToken()
forStmt.Body = p.parseStmt_block()
return forStmt
} else {
// for ; cond ; postStmt {}
forStmt.Post = p.parseStmt()
forStmt.Body = p.parseStmt_block()
return forStmt
}
}
} else {
...
}
...
}
for 的初始化语句同样是可选的,因此如果 for
关键字之后是 ;
则忽略初始化部分。然后解析同样可选的 cond 和 postStmt 部分。
然后是 for
跟着语句的情形,可能有 for cond {}
和 for init; cond?; postStmt {}
两种情况。for cond {}
的解析如下:
func (p *Parser) parseStmt_for() *ast.ForStmt {
...
// for ; ...
if _, ok := p.AcceptToken(token.SEMICOLON); ok {
...
} else {
// for expr ... {}
stmt := p.parseStmt()
if _, ok := p.AcceptToken(token.LBRACE); ok {
// for cond {}
p.UnreadToken()
if expr, ok := stmt.(ast.Expr); ok {
forStmt.Cond = expr
}
forStmt.Body = p.parseStmt_block()
return forStmt
} else {
...
}
}
}
for
之后唯一的表达式后如果遇到{
则表达式作为循环的条件处理。
最后是有初始化语句的情况:
func (p *Parser) parseStmt_for() *ast.ForStmt {
...
// for ; ...
if _, ok := p.AcceptToken(token.SEMICOLON); ok {
...
} else {
// for expr ... {}
...
if _, ok := p.AcceptToken(token.LBRACE); ok {
// for cond {}
...
} else {
// for init;
p.MustAcceptToken(token.SEMICOLON)
forStmt.Init = stmt
...
}
}
}
在处理完成初始化语句之后,后续的可选 cond 和 postStmt 和前忽略初始化语句的解析代码一样。
5.2.5 输出语法树
解析以下代码:
package main
func main() {
for {
if 1 == 0 {}
}
}
查看其语法树:
$ go run main.go ast ./_examples/hello.ugo
0 ast.File {
1 . Filename: "./_examples/hello.ugo"
2 . Source: "package ..."
3 . Pkg: *ast.PackageSpec {
4 . . PkgPos: ./_examples/hello.ugo:1:1
5 . . NamePos: ./_examples/hello.ugo:1:9
6 . . Name: "main"
7 . }
8 . Funcs: []*ast.Func (len = 1) {
9 . . 0: *ast.Func {
10 . . . FuncPos: ./_examples/hello.ugo:3:1
11 . . . NamePos: ./_examples/hello.ugo:3:6
12 . . . Name: "main"
13 . . . Body: *ast.BlockStmt {
14 . . . . Lbrace: ./_examples/hello.ugo:3:13
15 . . . . List: []ast.Stmt (len = 1) {
16 . . . . . 0: *ast.ForStmt {
17 . . . . . . For: ./_examples/hello.ugo:4:2
18 . . . . . . Body: *ast.BlockStmt {
19 . . . . . . . Lbrace: ./_examples/hello.ugo:4:6
20 . . . . . . . List: []ast.Stmt (len = 1) {
21 . . . . . . . . 0: *ast.IfStmt {
22 . . . . . . . . . If: ./_examples/hello.ugo:5:3
23 . . . . . . . . . Cond: *ast.BinaryExpr {
24 . . . . . . . . . . OpPos: ./_examples/hello.ugo:5:8
25 . . . . . . . . . . Op: ==
26 . . . . . . . . . . X: *ast.Number {
27 . . . . . . . . . . . ValuePos: ./_examples/hello.ugo:5:6
28 . . . . . . . . . . . ValueEnd: ./_examples/hello.ugo:5:7
29 . . . . . . . . . . . Value: 1
30 . . . . . . . . . . }
31 . . . . . . . . . . Y: *ast.Number {
32 . . . . . . . . . . . ValuePos: ./_examples/hello.ugo:5:11
33 . . . . . . . . . . . ValueEnd: ./_examples/hello.ugo:5:12
34 . . . . . . . . . . . Value: 0
35 . . . . . . . . . . }
36 . . . . . . . . . }
37 . . . . . . . . . Body: *ast.BlockStmt {
38 . . . . . . . . . . Lbrace: ./_examples/hello.ugo:5:13
39 . . . . . . . . . . Rbrace: ./_examples/hello.ugo:5:14
40 . . . . . . . . . }
41 . . . . . . . . }
42 . . . . . . . }
43 . . . . . . . Rbrace: ./_examples/hello.ugo:6:2
44 . . . . . . }
45 . . . . . }
46 . . . . }
47 . . . . Rbrace: ./_examples/hello.ugo:7:1
48 . . . }
49 . . }
50 . }
51 }
其中if
、for
和等于比较运算符已经成功解析。