Ruby

遠い世界の数式 (RubyVM::InstructionSequence.load)

More than 3 years have passed since last update.

問題:http://nabetani.sakura.ne.jp/kanagawa.rb/evalex/

解法として、まじめに構文解析するか、正規表現を使うか( http://qiita.com/cielavenir/items/ad6338a8cc4fa9c99704 )、Rubyのevalを何とかして使うかということになると思う。

Rubyのevalを利用したい場合、(鍋谷さんの解法のように数値を全て別クラスに分岐することをしなければ)このように、どうしてもモンキーパッチが必要になる(Refinementで可能かどうかの確認はしていない)。


kanagawarb_evalex_eval.rb

#!/usr/bin/ruby

#http://nabetani.sakura.ne.jp/kanagawa.rb/evalex/

class Fixnum
alias_method :or_real,:|
alias_method :and_real,:&
alias_method :plus_real,:+
alias_method :mult_real,:*
def |(other) self.mult_real(other) end
def &(other) self.plus_real(other) end
def +(other) self.and_real(other) end
def *(other) self.or_real(other) end
end

while gets
p eval($_.tr('*+&|','|&+*'))
end


既存メソッドの上書きを何とか回避できないものかと考えるうちに、「コンパイルされたバイトコードを上書きする」方法を考えついた。そのためには、librubyのrb_iseq_loadをRubyの世界から見えるようにすれば良い。


kanagawarb_evalex_iseq.rb

#!/usr/bin/ruby

#http://nabetani.sakura.ne.jp/kanagawa.rb/evalex/

begin
require 'iseq'
rescue LoadError
require 'fiddle'

class RubyVM
class InstructionSequence
addr = Fiddle.dlopen(nil)['rb_iseq_load']
fn = Fiddle::Function.new(addr, [Fiddle::TYPE_VOIDP] * 3, Fiddle::TYPE_VOIDP)
define_singleton_method(:load) do |dat, par=nil, opt=nil|
fn.call(Fiddle.dlwrap(dat), par, opt).to_value
end
end
end
end

while gets
bytecode=RubyVM::InstructionSequence.compile($_.tr('*+&|','|&+*')).to_a
bytecode[13].map!{|e|
if e[1].is_a?(Hash)&&e[1].has_key?(:mid)
e[1][:mid]=e[1][:mid].to_s.tr('*+&|','|&+*').to_sym
e[0]={:* => :opt_mult,:+ => :opt_plus,:& => :opt_send_simple,:| => :opt_send_simple}[e[1][:mid]]
end
e
}
p RubyVM::InstructionSequence.load(bytecode).eval
end



最後に

わかっているとは思いますが、RubyVM::InstructionSequence.load解法はネタです(これ以上邪悪な方法はないと思います)。実務ではやらないようにして下さい^^;;