「Go言語でつくるインタプリタ」で作ったインタプリタにfor文を実装してみました。
ちなみに、前回は再代入可能な変数を実装しました。
仕様
今回実装するfor文の書式は以下の通り。
for ( var i = 0; i < 10; i = i + 1) { // 初期化; 継続条件; 増分処理
puts(i); // 文
}
字句解析
for
をキーワードに追加します。
token.go
var keywords = map[string]TokenType{
...
"for": FOR,
}
構文解析
for文を表現するASTのNodeを以下のように定義します。
ast.go
type ForStatement struct {
Token token.Token
InitialStatement Statement // 初期化
Condition Expression // 継続条件
PostStatement Statement // 増分処理
Block *BlockStatement // 本体のブロック
}
そして、以下のように構文解析を行います。
parser.go
// "for"のTokenを読み込むと呼ばれる関数
func (p *Parser) parseForStatement() *ast.ForStatement {
stmt := &ast.ForStatement{Token: p.curToken}
// forの次は"("
if !p.expectPeek(token.LPAREN) {
return nil
}
p.nextToken()
// 初期化の文の解析
stmt.InitialStatement = p.parseStatement()
p.nextToken()
// 継続条件の式の解析
stmt.Condition = p.parseExpression(LOWEST)
if !p.expectPeek(token.SEMICOLON) {
return nil
}
p.nextToken()
// 増分処理の文の解析
stmt.PostStatement = p.parseStatement()
// 増分処理の次は")"のはず。ただし、増分処理が空の場合はすでに")"が読まれている。
if p.peekTokenIs(token.RPAREN) {
p.nextToken()
}
p.nextToken()
// 本体の文の解析
stmt.Block = p.parseBlockStatement()
return stmt
}
評価
評価は以下の通り。
evaluator.go
// for文のNodeを評価するときに呼ばれる関数
func evalForStatement(stmt *ast.ForStatement, env *object.Environment) object.Object {
// 初期化の文を評価
initStmt := Eval(stmt.InitialStatement, env)
if isError(initStmt) {
return initStmt
}
var result object.Object
for {
// まず継続条件を評価
condition := Eval(stmt.Condition, env)
if isError(condition) {
return condition
}
if !isTruthy(condition) {
// 評価結果が偽だったらループを抜ける
break
}
// 本体の文を評価
result = Eval(stmt.Block, env)
// エラーもしくはreturnで返り値が渡ってきたらそのまま返す
if isError(result) || isReturn(result) {
return result
}
// 増分処理の文を評価
postStmt := Eval(stmt.PostStatement, env)
if isError(postStmt) {
return postStmt
}
}
return result
}
これで実装完了です。
REPLで試してみる
>> for (var i = 0; i < 10; i = i + 1) { puts(i); }
0
1
2
3
4
5
6
7
8
9
ちゃんと動いてそうですね
さいごに
この記事では一部のコードしか記載しなかったので、全体の差分が見たい方は こちらのPRをご覧ください。