Writing An Interpreter In Go その7
その6はこちら
前回までの内容でREPLが電卓として使えるようになったので、if文とreturn文、それに変数の宣言と利用を追加して電卓からプログラミング言語にレベルアップさせる。
3.6: if文の評価
3.6: Evaluate if statements · nibral/monkey_interpreter@9645d49 · GitHub
ASTに Condition, Consequence, Alternative が格納されているので、Conditionの式を評価してどちらを実行するか決定する。
if <Condition> { <Consequence> } else { <Alternative> }
Parserにbooleanを追加したときと同様に、式がNULLまたはFALSEの時に偽、それ以外はすべて真として評価する。つまり数値の5は真。
>> if (5) { return true; } else { return false; } true
ここの挙動は言語仕様の問題で、
などの選択肢がある。個人的には型エラーにするのが親切でいいかなと思う。
3.7: return文の評価
3.7: Evaluate return statements · nibral/monkey_interpreter@2f9e42d · GitHub
Monkeyでは最後に評価した値が戻り値になるが、return文が出現したときはそこで処理を打ち切って以降の文を実行しないようにする。
単にreturnが出た時に打ち切るだけだとブロックが入れ子になった時に正しく動作しない(下の例で1を返してしまう)ので、return文による戻り値であることを示す ReturnValue というオブジェクトを追加して処理を打ち切ったことをブロックの外側にも伝搬させる必要がある。
if (10 > 1) { if (10 > 1) { return 10; } return 1; }
3.8: エラー処理を追加
3.8: Implement error handling · nibral/monkey_interpreter@f9ce1dc · GitHub
入力したコードに問題があって評価できないときに、単にnilを返すのではなく適切なエラーメッセージを返すようにした。「そんな演算子は知らん」とか「整数と真偽値を足してるぞ」とか。
return文の時と同様にエラーが発生したらそれ以降の処理はすべて打ち切る必要があるので、Errorというオブジェクトを追加した。2項演算子を評価する場合は右辺と左辺がそれぞれエラーになっていないかチェックする必要があるので、修正箇所は多め。
3.9: 変数の宣言と利用
3.9: Implement variable · nibral/monkey_interpreter@0548f28 · GitHub
値に識別子を付けてあとで使う、つまり変数の仕組みを追加する。
仕組みとしては非常に簡単で、文字列をキーとするオブジェクトのmapを持っておく。letで識別子が定義されたらmapに追加、別の場所で識別子が現れたらmapから取得。識別子がmapに存在しなければエラーにすればよい。
もちろんこのmapはプログラムの実行中ずっと同じものを保持していなければならないので、Environment という構造体を追加してRPELまたはテストコードから渡すようにする。
Evaluator 側でEnvironmentのインスタンスを作らずに、呼び出し側から渡してもらうあたりは DI (Dependency-Injection) っぽいと思ってるけどこの理解であってるのかな?
ここまで来るとREPLがかなりプログラミング言語っぽくなってくる。一丁前にエラーも出してくるが、自分のtypoを自分が作ったEvaluatorが見つけていると思うと「よしよしちゃんと動いとるな」という感じでニヤニヤしてしまった。
> go run .\main.go Hello nibral! This is the Monkey programming language! Feel free to type commands >> let apple = 120; >> let quantity = 3; >> apple * quontity; ERROR: identifier not found: quontity >> apple * quantity; 360 >>