SICP 読書ノート#49 - RubyでSchemeインタプリタをつくろう(8) - 環境に対する操作 (pp.213-228)
環境および束縛、代入まわりを実装しました。やってみると意外と簡単だった。
> (define a 1) => nil > a => 1 > (define y 1) => nil > (set! y 2) => nil > y => 2 > (set! z 3) "set_variable_value!: unbound variable; z" > (define foo (lambda (x) "foo")) => nil > (foo 1) => "foo"
環境に対する操作
SICPを参考にEnvironmentクラスを実装。変数と値のペアはHashで持たせる。SICPのコードよりはずっとシンプルにできたと思う。
extend_envronment()はローカル環境を生成するために新しいインスタンスを返す必要があたりに気づけずにしばらくバグっていました。。
class Environment def initialize(frames) @frames = frames end def lookup_variable_value(var) @frames.each do |frame| return frame[var] if frame[var] != nil end raise "lookup_variable_value : unbound variable " + var.to_s end def extend_environment(vars, values) begin return Environment.new([make_frame(vars, values)] + @frames) rescue raise "extend_envronment: arguments error; " + vars.to_s + " " + values.to_s end end def define_variable!(var, value) @frames[0][var] = value end def set_variable_value!(var, value) @frames.each do |frame| return frame[var] = value if frame[var] != nil end raise "set_variable_value! : unbound variable " + var.to_s end private def make_frame(vars, values) vars.zip(values).to_h end end
変数の評価
Variableオブジェクトを評価する際は環境から変数を探す。
class Variable attr_reader :name def initialize(name) @name = name end def eval(env) env.lookup_variable_value(@name) end end
束縛と代入
Assignment、Definitionのevalをそれぞれ実装。
class Assignment < Base def eval(env) env.set_variable_value!(@variable.name, @value.eval(env)) nil end end class Definition < Base def eval(env) env.define_variable!(@variable.name, @value.eval(env)) nil end end
関数適用とローカル環境
関数適用の際に引数と対応する値からローカルな環境を生成する。
class Procedure < Base def apply(arguments) env = @env.extend_environment(@params.map { |param| param.name }, arguments) # ローカル環境を生成 self.eval_sequence(@body, env) end end
処理系の基盤部分は結構できてきたと思う。次はprimitive proceduresをやります。