JavaScriptでLispインタープリターを作ろう(5) ~変数の導入~
前回の記事では、Lispインタープリターに順次処理「do」を導入した。ここまでの説明で、「順次・反復・分岐」のうち、「順次」と「分岐」は既に導入できたので、残すは「反復」のみとなる。
しかし、反復を説明する前に説明しておくべき事がある。それは「変数」である。このインタープリターにはまだ変数を扱う方法が無い。このため、たとえば反復で必要となるループの脱出条件を記述することが出来ない。そこで今回は、「反復」の説明をする前に「変数」を導入する。
変数の定義を行うdef
変数の定義を可能にするには、インタープリターのevaluate
関数に以下の分岐を追加する事で実現できる。
var evaluate = function(x, env) { if (Array.isArray(x)) { if (x[0] === 'def') { // 変数定義 ← 追加行 return env[x[1]] = evaluate(x[2], env); // ← 追加行 } else if (x[0] === 'if') { // 条件分岐 // ・・・以下略・・・
if
の時と同じように、def
というキーワードを特別扱いし、第二引数の計算結果を第一引数の変数名に代入するのである。また、変数に代入した値は、そのままdef
の戻り値としている。値を保持する入れ物としては、今まで関数を入れてきたglobalEnv
をそのまま利用する。
ソースコード全体は次のようになる。
このdef
の利用例を以下に示す。
evaluate(['console.log', ['do', // 変数「a」作成し、2で初期化する ['def', 'a', 2], // 変数「b」作成し、1 + 2つまり3で初期化する ['def', 'b', ['+', 1, 2]], // 変数「a」と変数「b」を足し合わせる ['+', 'a', 'b']]], globalEnv);
この例ではまず、['def', 'a', 2]
という部分で変数a
を作成し、この変数a
を2
で初期化している。同様に、['def', 'b', ['+', 1, 2]]
という部分で変数b
を作成し、1 + 2
つまり3
で初期化している。最後に、この変数a
とb
を足しあわせ、結果をブラウザーのJavaScriptコンソールへ出力している。
なお、ここで定義した変数a
と変数b
のスコープであるが、現在のインタープリターの実装ではglobalEnv
に直接値を保持しているため、同一のglobalEnv
を使っている間はずっと保持された状態になる。これは一般的なインタープリターで例えると、グローバル変数領域に保持されている状態と同一である。
変数への代入を行うset!
さて、変数への定義が可能になったので、変数への代入を行う操作も追加してみよう。変数への代入操作の名前はset!
とする。set!
を導入するためには、以下のようにインタープリターのevaluate
関数を変更するだけだ。
なお、変数への再代入のような破壊的操作に感嘆符!
を付ける文化はSchemeやClojureやRubyに見られる。このインタープリターでも、このような文化を踏襲した。
var evaluate = function(x, env) { if (Array.isArray(x)) { if (x[0] === 'def') { // 変数定義 return env[x[1]] = evaluate(x[2], env); } else if (x[0] === 'set!') { // 代入 ← 追加行 return env[x[1]] = evaluate(x[2], env); // ← 追加行 } else if (x[0] === 'if') { // 条件分岐 // ・・・以下略・・・
set!
の処理の内容はdef
と全く同じだ。def
とset!
違いは「変数定義」と「代入」という役割の違いでしかない。事実、このインタープリターではset!
を用いても変数定義は可能であるし、def
を用いても代入が可能である。この役割の違いをより明確にし、利便性を向上させるため、本来はset!
の処理時に変数がまだ定義されていなかった場合はエラーにした方が好ましいが、このインタープリターではこのようなエラー処理を省いた。
以下にset!
導入後のソースコード全体を掲載する。
このdef
の利用例を以下に示す。
evaluate(['console.log', ['do', // 変数「a」作成し、2で初期化する ['def', 'a', 2], // 変数「b」作成し、3で初期化する ['def', 'b', 3], // 変数「a」に1を代入する ['set!', 'a', 1], // 変数「a」と変数「b」を足し合わせる ['+', 'a', 'b']]], globalEnv);
このコードを実行すると、a
の初期値は2
だが、あとで代入されて1
となり、b
に代入されている3
と足しあわされて、ブラウザーのJavaScriptコンソールへ4
が出力される。
さて、これでグローバル変数のみではあるが、変数の作成や代入ができるようになった。次回は反復を導入する。
[近棟 稔]