「Go言語でつくるインタプリタ」のつづき ~再代入可能な変数を実装してみる~

[Go言語でつくるインタプリタ](https://www.oreilly.co.jp/books/9784873118222/)という本を読みました。この本を読み進めながらコードを書いていくと、数値、加減乗除、文字列、配列、ハッシュ、if文、関数呼び出し等々、を実現するインタプリタがあっという間に実装できます。今年発売されたGo言語関連の書籍の中でも1、2を争う名著ではないでしょうか。
とはいえ、個人的には物足りないなと思う点もあり、その中の一つが再代入できる変数が存在しないことでした。

```swift

let x = 0;
x = 1; // error!!
```
ということで、自分で実装してみることにしました。
以下の説明は、 [Go言語でつくるインタプリタ](https://www.oreilly.co.jp/books/9784873118222/)を読んでない方にとってはピンと来ないかもしれませんが、あしからず。

# 仕様
`var`をつけて変数を定義すると、再代入可能な変数を定義できます。
また、代入式は式として評価され、代入された値が返ります。

```swift

var x = 1;
x = 2; // 再代入できる
var y = x = 3;  // 代入式(x = 3)は3を返すので、y = 3となる
```

# 字句解析

新たに追加する`var`を特別なTokenとして読み込むようにする必要があるので、`var`をキーワードに追加します。

```golang:token.go
var keywords = map[string]TokenType{
    ...
    "var":    VAR,
}

```

# 構文解析
`var`による変数定義文を表現する`VarStatement`をastのnodeとして新たに追加します。

```golang:ast.go
type VarStatement struct {
	Token token.Token
	Name  *Identifier // 左辺の変数(識別子)
	Value Expression  // 右辺の式
}
```
そして、以下のように構文解析を行います。

```golang:parser.go
// `var`のTokenを読み込むと呼ばれる関数
func (p *Parser) parseVarStatement() *ast.VarStatement {
	stmt := &ast.VarStatement{Token: p.curToken}
	
	// `var`の次は`Identfier`
	if !p.expectPeek(token.IDENT) {
		return nil
	}

	stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}

	// `Identfier`の次は`=`(ASSIGN)
	if !p.expectPeek(token.ASSIGN) {
		return nil
	}

	p.nextToken()

	// 右辺の式を解析する
	stmt.Value = p.parseExpression(LOWEST)

	if p.peekTokenIs(token.SEMICOLON) {
		p.nextToken()
	}

	return stmt
}
```

また、代入式は`AssignExpression`というastのnodeを定義します。

```golang:ast.go
type AssignExpression struct {
	Token token.Token
	Name  *Identifier
	Value Expression
}
```

代入式の構文解析を行う関数は、Pratt構文解析におけるInfixのパターンとして登録します。

```golang:parse.go
func New(l *lexer.Lexer) *Parser {
	p := &Parser{l: l, errors: []string{},}

	...	

	// Infixとして、代入式の構文解析を行う関数(parseAssignExpression)を登録する
	p.registerInfix(token.ASSIGN, p.parseAssignExpression)

	return p
}

// `=`を読み込むと呼ばれる代入式の構文解析を行う関数。引数として、左辺のnodeが渡される。
func (p *Parser) parseAssignExpression(left ast.Expression) ast.Expression {
	// 左辺はIdentifierであること
	ident, ok := left.(*ast.Identifier)
	if !ok {
		return nil
	}

	assign := &ast.AssignExpression{Token: p.curToken, Name: ident}

	p.nextToken()

	// 右辺の式を解析する
	assign.Value = p.parseExpression(LOWEST)

	return assign
}
```

# 評価

変数の構造体に、「再代入可能か」を示す`IsMutable`というフィールドを追加します。

```golang:environment.go
type Value struct {
	Obj Object
	IsMutable bool
}
```

そして、再代入不可な`LetStatement`の場合は`IsMutable=false`に、再代入可能な`VarStatement`の場合は`IsMutable=true`として、変数をセットします。
代入式`AssignExpression`は、左辺の変数が`IsMutable=true`であることを確認してから、右辺の値を変数に代入します。

```golang:evaluator.go
func Eval(node ast.Node, env *object.Environment) object.Object {
	switch node := node.(type) {
	...
	case *ast.LetStatement: // 再代入不可のLetStatement
		// 右辺を評価
		val := Eval(node.Value, env)
		if isError(val) {
			return val
		}
		env.Set(node.Name.Value, val, false) // isMutable = false として変数を登録
	case *ast.VarStatement: // 再代入可のVarStatement
		// 右辺を評価
		val := Eval(node.Value, env)
		if isError(val) {
			return val
		}
		env.Set(node.Name.Value, val, true) // isMutable = true として変数を登録
	case *ast.AssignExpression: // 代入式
		identVal, ok := env.Get(node.Name.Value)
		if !ok {
			// 未定義の変数には代入できない
			return newError("identifier not found: " + node.Name.Value)
		}

		if !identVal.IsMutable {
			// 再代入不可の変数には代入できない
			return newError("can't assign value to immutable identifier: " + node.Name.Value)
		}

		// 右辺を評価
		val := Eval(node.Value, env)
		if isError(val) {
			return val
		}
		env.Set(node.Name.Value, val, true) // isMutable = true として登録する
		return val  // 代入した値を返す
	}
	...
	}
	return nil
}
```

これで、実装完了です！

# REPLで試してみる
```
>> var i = 0; i = 1;
1
>> var x = 1; var y = 2; var z = x = y = 3;   
>> x
3
>> y
3
>> z
3
>> j = 0
ERROR: identifier not found: j
>> let k = 0
>> k = 1
ERROR: can't assign value to immutable identifier: k
```
ちゃんと動いてそうですね :tada: 


# まとめ
「インタプリタを作る」というと、かなりハードルの高いものだと思っていましたが、このように簡単なものであれば、案外あっさりと実装することができました。この記事では、一部のコードしか記載しなかったので、全体の差分が見たい方は [こちらのPR](https://github.com/tatsumack/monkey/pull/1)をご覧ください。

# 参考
https://qiita.com/Kashiwara/items/0015a591fec4fd608b0a