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之后,我们就可以考虑如何将变量转化为汇编代码了。


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