SICP 読書ノート#47 - RubyでSchemeインタプリタをつくろう(6) - 実行オブジェクトへのマッピング (pp.213-228)
前回作った構文木について、そのまま評価器に放り込んで評価させても良いのですが、SICPのテキストのeval()
のようにだらだらと長くなるのがいまいちかなと思います。
問題4.3でも取り上げられているように「データ主導」で実装する方がスマートかと思いますので、そのために構文木のノードを実行可能なオブジェクトへ変換します。
具体的には、number,stringのような組み込みオブジェクト、quote,assignment,lambdaのような特殊形式、関数適用であるapplicationといった構文単位でクラスを用意し、構文木のノードをそのクラスの何れかに属するオブジェクトへとマッピングします。
class Generator def self.generate(node) begin case node.key when :NUMBER return Builtin::Number.new(node.value) when :STRING return Builtin::String.new(node.value) when :SYMBOL return Builtin::Variable.new(node.value) else raise "generate: unknown builtin type; " + node.to_s end rescue operator = node[0] operands = node[1..-1] case operator.value when "quote" list = operands[0].map { |item| self.generate(item) } return SpecialForm::Quote.new(list) when "set!" variable = self.generate(operands[0]) value = self.generate(operands[1]) return SpecialForm::Assignment.new(variable, value) when "define" variable = self.generate(operands[0]) value = self.generate(operands[1]) return SpecialForm::Definition.new(variable, value) when "if" predicate = self.generate(operands[0]) consequent = self.generate(operands[1]) alternative = self.generate(operands[2]) return SpecialForm::If.new(predicate, consequent, alternative) when "lambda" p operands[0] p operands[1..-1] params = operands[0].map { |param| self.generate(param) } body = operands[1..-1].map { |exp| self.generate(exp) } return SpecialForm::Lambda.new(params, body) when "begin" exps = operands[0].map { |operand| self.generate(operand) } return SpecialForm::Begin.new(exps) else procedure = Builtin::Variable.new(operator.value) arguments = operands.map { |operand| self.generate(operand) } return SpecialForm::Application.new(procedure, arguments) end end end end
Typeモジュールとそれに属するクラスを用意。とりあえずinitializeだけ実装。
module Type class Base end class Number < Base def initialize(value) @value = value end end class String < Base def initialize(value) @value = value end end class Variable < Base def initialize(name) @name = name end end class Quote < Base def initialize(list) @list = list end end class Assignment < Base def initialize(var, value) @variable = var @value = value end end class Definition < Base def initialize(var, value) @variable = var @value = value end end class If < Base def initialize(predicate, consequent, alternative) @predicate = predicate @consequent = consequent @alternative = alternative end end class Lambda < Base def initialize(params, body) @params = params @body = body end end class Begin < Base def initialize(exps) @exps = exps end end class Application < Base def initialize(procedure, arguments) @procedure = procedure @arguments = arguments end end end
pryで実行してみると、こんな感じになりました。
[1] pry(main)> exp = "(define x 1)" => "(define x 1)" [2] pry(main)> Generator.generate(Parser.parse(Parser.tokenize(exp))) => #<Type::Definition:0x007f9902141070 @value=#<Type::Number:0x007f9902140cd8 @value=1>, @variable=#<Type::Variable:0x007f9902140e90 @name="x">> [3] pry(main)> exp = "((lambda (x) \"foo\") 1)" => "((lambda (x) \"foo\") 1)" [4] pry(main)> Generator.generate(Parser.parse(Parser.tokenize(exp))) => #<Type::Application:0x007f9a728e5cb0 @operands=[#<Type::Number:0x007f9a728e5b98 @value=1>], @operator=#<Type::Lambda:0x007f9a728e5fd0 @body=[#<Type::String:0x007f9a728e5d50 @value="foo">], @params=[#<Type::Variable:0x007f9a728e5f30 @name="x">]>>
あとは各クラスのevalメソッドを実装すれば、REPLループでトップのオブジェクトを順々に評価されるはず。
tokens = Parser.tokenize(input) nodes = Parser.parse(tokens) object = Generator.generate(nodes) @evaluator.eval(object) # この中でobjects.eval(@environment)を実行