4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「Go言語でつくるインタプリタ」のつづき ~for文を実装してみる~

Last updated at Posted at 2019-01-07

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

ちゃんと動いてそうですね :tada:

さいごに

この記事では一部のコードしか記載しなかったので、全体の差分が見たい方は こちらのPRをご覧ください。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?