- KusonStack一站式可编程配置技术栈(Go): https://github.com/KusionStack/kusion
- KCL 配置编程语言(Rust): https://github.com/KusionStack/KCLVM
- 凹语言™: https://github.com/wa-lang/wa
4.2 完善AST和解析器
Token类型和词法解析的准备工作都就绪了,本节我们将继续完善AST结点定义和语法树的解析工作。
4.2.1 完善AST结点
新增加的VAR和ASSIGN两个Token类型分别对应2个新的语法树结点。
新增 VarSpec
类型表示变量的定义:
// 变量信息
type VarSpec struct {
VarPos token.Pos // var 关键字位置
Name *Ident // 变量名字
Type *Ident // 变量类型, 可省略
Value Expr // 变量表达式
}
其中重要的信息时变量的名字,变量的类型和变量的初始化值。初始化表达式是一个 Expr 表达式接口类型,可以为空。有初始化表达式时,类型也可以为空。
然后新增加 AssignStmt
表示一个赋值语句(目前只支持一个变量的赋值):
type AssignStmt struct {
Target *Ident // 要赋值的目标
OpPos token.Pos // Op 的位置
Op token.TokenType // '='
Value Expr // 值
}
其中Target表示被赋值的目标,Value是值表达式,Op表示赋值的操作符号。目前Op只有=
一种情况,之所以添加Op是为了后面支持:=
简短定义赋值的语法,目前可以忽略。
最后File增加全局变量的信息:
type File struct {
Filename string // 文件名
Source string // 源代码
Pkg *PackageSpec // 包信息
Globals []*VarSpec // 全局变量
Funcs []*Func // 函数列表
}
为了好区分,我们将Package类型改名为PackageSpec类型。Globals表示全局变量列表。Funcs依然是函数列表。
4.2.2 完善嵌套的块语句解析
在第3章的例子中我们已经可以实现多个语句的解析,但是还不支持嵌套块语句的解析。比如以下的代码:
func main() {
{
println(123)
}
}
在解析内层的块语句时会出现unknown tok: type={, lit="{"
错误。
块语句由Parser.parseStmt_block
方法解析,只需要增加对嵌套块语句处理即可:
func (p *Parser) parseStmt_block() *ast.BlockStmt {
block := &ast.BlockStmt{}
tokBegin := p.MustAcceptToken(token.LBRACE) // {
Loop:
for {
switch tok := p.PeekToken(); tok.Type {
...
case token.LBRACE: // {}
block.List = append(block.List, p.parseStmt_block())
case token.RBRACE: // }
break Loop
...
}
}
tokEnd := p.MustAcceptToken(token.RBRACE) // }
block.Lbrace = tokBegin.Pos
block.Rbrace = tokEnd.Pos
return block
}
当遇到token.LBRACE
左大括弧时递归调用处理子块语句。
4.2.3 变量定义解析
目前只支持以下几种形式的变量定义:
var x1 int
var x2 int = 134
var x3 = 456
增加Parser.parseStmt_var
方法解析这3种变量定义的语句:
func (p *Parser) parseStmt_var() *ast.VarSpec {
tokVar := p.MustAcceptToken(token.VAR)
tokIdent := p.MustAcceptToken(token.IDENT)
var varSpec = &ast.VarSpec{
VarPos: tokVar.Pos,
}
varSpec.Name = &ast.Ident{
NamePos: tokIdent.Pos,
Name: tokIdent.Literal,
}
// var name type?
if typ, ok := p.AcceptToken(token.IDENT); ok {
varSpec.Type = &ast.Ident{
NamePos: typ.Pos,
Name: typ.Literal,
}
}
// var name =
if _, ok := p.AcceptToken(token.ASSIGN); ok {
varSpec.Value = p.parseExpr()
}
p.AcceptTokenList(token.SEMICOLON)
return varSpec
}
首先必须匹配var
关键字和token.IDENT
表示的变量名。然后根据p.AcceptToken(token.IDENT)
判断是否有type信息,p.AcceptToken(token.ASSIGN)
用于判断是否有初始化表达式,如果有初始化表达式则通过p.parseExpr()
解析表达式。
有了变量定义解析之后,就可以在Parser.parseFile
中添加对全局变量的解析:
func (p *Parser) parseFile() {
...
for {
switch tok := p.PeekToken(); tok.Type {
...
case token.VAR:
p.file.Globals = append(p.file.Globals, p.parseStmt_var())
case token.FUNC:
p.file.Funcs = append(p.file.Funcs, p.parseFunc())
...
}
}
}
如果遇到token.VAR
类型的Token,就通过p.parseStmt_var()
解析并添加到p.file.Globals
。
对于局部变量的解析在Parser.parseStmt_block
中完成:
func (p *Parser) parseStmt_block() *ast.BlockStmt {
...
Loop:
for {
switch tok := p.PeekToken(); tok.Type {
...
case token.VAR:
block.List = append(block.List, p.parseStmt_var())
...
}
}
...
}
在块语句中如果遇到token.VAR
类型的Token,则通过p.parseStmt_var()
解析变量定义语句,并添加到block.List
中。
4.2.4 变量和函数名
之前的代码只支持builtin类型的函数调用,和普通的表达式语句。之前的parseExpr_primary
方法在遇到表示符时默认作为函数调用处理:
func (p *Parser) parseExpr_primary() ast.Expr {
...
switch tok := p.PeekToken(); tok.Type {
case token.IDENT: // call
return p.parseExpr_call()
...
}
}
为了支持变量读取,我们需要区分函数调用的函数名字和变量名字。改造代码如下:
func (p *Parser) parseExpr_primary() ast.Expr {
...
switch tok := p.PeekToken(); tok.Type {
case token.IDENT: // call
p.ReadToken()
nextTok := p.PeekToken()
p.UnreadToken()
switch nextTok.Type {
case token.LPAREN:
return p.parseExpr_call()
default:
p.MustAcceptToken(token.IDENT)
return &ast.Ident{
NamePos: tok.Pos,
Name: tok.Literal,
}
}
...
}
}
通过p.PeekToken()
向前多看2个Token,如果第2个是token.LPAREN
左小括弧类型的Token则对应函数调用,否则作为普通的标识符处理。
4.2.5 赋值语句解析
赋值语句是普通表达式语句到扩展。之前的parseStmt_block
解析块语句时,在default分支解析表达式语句:
func (p *Parser) parseStmt_block() *ast.BlockStmt {
...
Loop:
for {
switch tok := p.PeekToken(); tok.Type {
...
default:
block.List = append(block.List, p.parseStmt_expr())
}
}
...
}
在解析完成表达式之后(被赋值的变量名也是一种表达式),向前多看一个Token是否为token.ASSIGN
类型的赋值操作符,如果是则继续解析右侧的值表达式。改动后的代码片段如下:
func (p *Parser) parseStmt_block() *ast.BlockStmt {
...
Loop:
for {
switch tok := p.PeekToken(); tok.Type {
...
default:
// expr ;
// target = expr;
expr := p.parseExpr()
switch tok := p.PeekToken(); tok.Type {
case token.SEMICOLON:
block.List = append(block.List, &ast.ExprStmt{
X: expr,
})
case token.ASSIGN:
p.ReadToken()
exprValue := p.parseExpr()
block.List = append(block.List, &ast.AssignStmt{
Target: expr.(*ast.Ident),
OpPos: tok.Pos,
Op: tok.Type,
Value: exprValue,
})
default:
p.errorf(tok.Pos, "unknown token: %v", tok)
}
}
}
...
}
目前我们只支持单变量的赋值,如果以后要支持多变量赋值,只要将p.parseExpr()
扩展为p.parseExprList()
即可。
4.2.6 测试AST解析
构造以下例子:
package main
var x1 int
var x2 int = 134
func main() {
{
var x2 = x2
x2 = 100
}
}
其中有3种变量定义的形式,和一个内层嵌套的赋值语句。解析结果如下:
$ go run main.go -debug=false 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 . Globals: []*ast.VarSpec (len = 2) {
9 . . 0: *ast.VarSpec {
10 . . . VarPos: ./_examples/hello.ugo:3:1
11 . . . Name: *ast.Ident {
12 . . . . NamePos: ./_examples/hello.ugo:3:5
13 . . . . Name: "x1"
14 . . . }
15 . . . Type: *ast.Ident {
16 . . . . NamePos: ./_examples/hello.ugo:3:8
17 . . . . Name: "int"
18 . . . }
19 . . }
20 . . 1: *ast.VarSpec {
21 . . . VarPos: ./_examples/hello.ugo:4:1
22 . . . Name: *ast.Ident {
23 . . . . NamePos: ./_examples/hello.ugo:4:5
24 . . . . Name: "x2"
25 . . . }
26 . . . Type: *ast.Ident {
27 . . . . NamePos: ./_examples/hello.ugo:4:8
28 . . . . Name: "int"
29 . . . }
30 . . . Value: *ast.Number {
31 . . . . ValuePos: ./_examples/hello.ugo:4:14
32 . . . . ValueEnd: ./_examples/hello.ugo:4:17
33 . . . . Value: 134
34 . . . }
35 . . }
36 . }
37 . Funcs: []*ast.Func (len = 1) {
38 . . 0: *ast.Func {
39 . . . FuncPos: ./_examples/hello.ugo:6:1
40 . . . NamePos: ./_examples/hello.ugo:6:6
41 . . . Name: "main"
42 . . . Body: *ast.BlockStmt {
43 . . . . Lbrace: ./_examples/hello.ugo:6:13
44 . . . . List: []ast.Stmt (len = 1) {
45 . . . . . 0: *ast.BlockStmt {
46 . . . . . . Lbrace: ./_examples/hello.ugo:7:2
47 . . . . . . List: []ast.Stmt (len = 2) {
48 . . . . . . . 0: *ast.VarSpec {
49 . . . . . . . . VarPos: ./_examples/hello.ugo:8:3
50 . . . . . . . . Name: *ast.Ident {
51 . . . . . . . . . NamePos: ./_examples/hello.ugo:8:7
52 . . . . . . . . . Name: "x2"
53 . . . . . . . . }
54 . . . . . . . . Value: *ast.Ident {
55 . . . . . . . . . NamePos: ./_examples/hello.ugo:8:12
56 . . . . . . . . . Name: "x2"
57 . . . . . . . . }
58 . . . . . . . }
59 . . . . . . . 1: *ast.AssignStmt {
60 . . . . . . . . Target: *ast.Ident {
61 . . . . . . . . . NamePos: ./_examples/hello.ugo:9:3
62 . . . . . . . . . Name: "x2"
63 . . . . . . . . }
64 . . . . . . . . OpPos: ./_examples/hello.ugo:9:6
65 . . . . . . . . Op: =
66 . . . . . . . . Value: *ast.Number {
67 . . . . . . . . . ValuePos: ./_examples/hello.ugo:9:8
68 . . . . . . . . . ValueEnd: ./_examples/hello.ugo:9:11
69 . . . . . . . . . Value: 100
70 . . . . . . . . }
71 . . . . . . . }
72 . . . . . . }
73 . . . . . . Rbrace: ./_examples/hello.ugo:10:2
74 . . . . . }
75 . . . . }
76 . . . . Rbrace: ./_examples/hello.ugo:11:1
77 . . . }
78 . . }
79 . }
80 }
有了AST之后,我们就可以考虑如何将变量转化为汇编代码了。