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  }

其中iffor和等于比较运算符已经成功解析。


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