問題:http://nabetani.sakura.ne.jp/kanagawa.rb/evalex/
解法として、まじめに構文解析するか、正規表現を使うか( http://qiita.com/cielavenir/items/ad6338a8cc4fa9c99704 )、Rubyのevalを何とかして使うかということになると思う。
Rubyのevalを利用したい場合、(鍋谷さんの解法のように数値を全て別クラスに分岐することをしなければ)このように、どうしてもモンキーパッチが必要になる (Refinementで可能かどうかの確認はしていない)。
20230523: Refinementで書き直しました.
kanagawarb_evalex_eval.rb
#!/usr/bin/ruby
#http://nabetani.sakura.ne.jp/kanagawa.rb/evalex/
Fixnum=Integer if !defined?(Fixnum)
module FixnumHack
refine Fixnum do
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
end
class Impl
using FixnumHack
def solve(s)
eval(s.tr('*+&|','|&+*'))
end
end
while gets
p Impl.new.solve $_
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解法はネタです(これ以上邪悪な方法はないと思います)。実務ではやらないようにして下さい^^;;