块语句支持
在这一篇文章中,我们要加入对于块语句的支持。所谓的块语句其实就是{}包起来的语句块。
我们需要做如下的更改:
- 在词元(Token)源码
token.go中加入两个新的词元(Token)类型({和}) - 在词法分析器(Lexer)源码
lexer.go中加入对{和}的识别 - 在抽象语法树(AST)的源码
ast.go中加入块语句对应的抽象语法表示。 - 在语法解析器(Parser)的源码
parser.go中加入对块语句的语法解析。 - 在解释器(Evaluator)的源码
eval.go中加入对块语句的解释。
词元(Token)更改
第一处改动
//token.go
const (
//...
TOKEN_LBRACE // {
TOKEN_RBRACE // }
)
第二处改动
//token.go
//词元类型的字符串表示
func (tt TokenType) String() string {
switch tt {
//...
case TOKEN_LBRACE:
return "{"
case TOKEN_RBRACE:
return "}"
}
}
词法分析器(Lexer)的更改
我们需要在词法分析器(Lexer)的NextToken()函数中加入对{和}的识别:
//lexer.go
//获取下一个词元(Token)
func (l *Lexer) NextToken() token.Token {
//...
switch l.ch {
//...
case '{':
tok = newToken(token.TOKEN_LBRACE, l.ch)
case '}':
tok = newToken(token.TOKEN_RBRACE, l.ch)
//...
}
//...
}
抽象语法树(AST)的更改
我们的块语句,需要什么信息呢?
- 词元信息
- 语句数组(
块语句中可以包含多条语句) - 右花括弧的位置(这个主要是为了计算
块语句的结束位置用)
//ast.go
//块语句:
// {
// stmt1
// stmt2
// ...
// }
//
type BlockStatement struct {
Token token.Token
Statements []Statement
RBraceToken token.Token //'End()'方法会使用
}
//开始位置
func (bs *BlockStatement) Pos() token.Position {
return bs.Token.Pos
}
//结束位置
func (bs *BlockStatement) End() token.Position {
return token.Position{Filename: bs.Token.Pos.Filename, Line:
bs.RBraceToken.Pos.Line, Col: bs.RBraceToken.Pos.Col + 1}
}
func (bs *BlockStatement) statementNode() {}
func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal }
//块语句的字符串表示
func (bs *BlockStatement) String() string {
var out bytes.Buffer
//循环遍历块语句中的每个statement
for _, s := range bs.Statements {
str := s.String()
out.WriteString(str)
if str[len(str)-1:] != ";" {//如果statement的字符串表示的最后一个字符不是';'号的话
out.WriteString(";")
}
}
return out.String()
}
这种代码我们已经见过太多了,就不再解释了。
语法解析器(Parser)的更改
我们需要做两处更改:
- 在
parseStatement()函数的switch分支中加入对词元类型为TOKEN_LBRACE的判断 - 增加解析
块语句的函数
//parser.go
func (p *Parser) parseStatement() ast.Statement {
switch p.curToken.Type {
//...
case token.TOKEN_LBRACE: // '{'
return p.parseBlockStatement()
default:
return p.parseExpressionStatement()
}
}
//解析'块语句'
func (p *Parser) parseBlockStatement() *ast.BlockStatement {
blockStmt := &ast.BlockStatement{Token: p.curToken} //生成块语句节点
p.nextToken()
for !p.curTokenIs(token.TOKEN_RBRACE) {//如果没有遇到右'}',就继续解析
stmt := p.parseStatement() //解析块语句中的每个语句
if stmt != nil {
blockStmt.Statements = append(blockStmt.Statements, stmt)
}
if p.peekTokenIs(token.TOKEN_EOF) {
break
}
p.nextToken()
}
blockStmt.RBraceToken = p.curToken //记录右'}'的位置
return blockStmt
}
我们在ParseStatement()函数的switch分支中增加了对词元类型为TOKEN_LBRACE)即{的判断(第5-6行)。
同时增加了对块语句的解析(代码13-30行)。
解释器(Evaluator)的更改
我们需要在解释器(Evaluator)的Eval函数的switch分支中加入对块语句的处理:
//eval.go
func Eval(node ast.Node, scope *Scope) (val Object) {
switch node := node.(type) {
//...
case *ast.BlockStatement:
return evalBlockStatement(node, scope)
//...
}
return nil
}
//解释'块语句'
func evalBlockStatement(block *ast.BlockStatement, scope *Scope) Object {
var result Object
//循环解释块语句中的每一个语句
for _, statement := range block.Statements {
result = Eval(statement, scope)
if result != nil {
rt := result.Type()
//如果处理结果为`return类型`或者'错误类型'就提前结束循环,无需处理后续的语句
if rt == RETURN_VALUE_OBJ || rt == ERROR_OBJ {
return result
}
}
}
return result
}
一切都是熟悉的味道!!!一切都是熟悉的老干妈!:smile:
测试
下面我们写一个简单的程序测试一下块语句:
//main.go
func TestEval() {
tests := []struct {
input string
expected string
}{
{`{ let x = 10 { x } }`, "10"},
}
for _, tt := range tests {
l := lexer.NewLexer(tt.input)
p := parser.NewParser(l)
program := p.ParseProgram()
scope := eval.NewScope(nil, os.Stdout)
evaluated := eval.Eval(program, scope)
if evaluated != nil {
if evaluated.Inspect() != tt.expected {
fmt.Printf("%s\n", evaluated.Inspect())
} else {
fmt.Printf("%s = %s\n", tt.input, tt.expected)
}
}
}
}
func main() {
TestEval()
}
第7行看起来比较费劲,我们把它格式化一下:
{
let x = 10
{
x
}
}
下一节,我们将加入对字符串表达式的支持。