numeric.cを書き換えてFizzBuzzを1行で
Rubyってオープンクラスだから実行時にコアクラスを書き換えていいんですね!というネタとして、以下のように書いてみた。
require 'fileutils' File.open("./numeric.c", "r") do |klass| File.open("./numeric.tmp", "w") do |tmpfile| while line = klass.gets tmpfile.write line if line =~ /fix_to_s\(int argc, VALUE \*argv, VALUE x\)/ 2.times {tmpfile.write klass.readline} tmpfile.write <<PATCH int val; val = FIX2INT(x); if (val % 15 == 0) return rb_str_new2("FizzBuzz"); if (val % 3 == 0) return rb_str_new2("Fizz"); if (val % 5 == 0) return rb_str_new2("Buzz"); PATCH end end end end FileUtils.mv("numeric.tmp", "numeric.c") system('make') system('./ruby -ve "(1..100).each{|i| puts i}"')
書き換えた箇所は、
/* * call-seq: * fix.to_s(base=10) -> string * * Returns a string containing the representation of <i>fix</i> radix * <i>base</i> (between 2 and 36). * * 12345.to_s #=> "12345" * 12345.to_s(2) #=> "11000000111001" * 12345.to_s(8) #=> "30071" * 12345.to_s(10) #=> "12345" * 12345.to_s(16) #=> "3039" * 12345.to_s(36) #=> "9ix" * */ static VALUE fix_to_s(int argc, VALUE *argv, VALUE x) { int base; int val; val = FIX2INT(x); if (val % 15 == 0) return rb_str_new2("FizzBuzz"); if (val % 3 == 0) return rb_str_new2("Fizz"); if (val % 5 == 0) return rb_str_new2("Buzz"); if (argc == 0) base = 10; else { VALUE b; rb_scan_args(argc, argv, "01", &b); base = NUM2INT(b); } return rb_fix2str(x, base); }
とかなる。
自明でくだらないと思ったけど、やってみるといろいろ分かるもの。
- あるファイルの途中に何かを書き加えるという処理は、File.open("file.txt", "r+")では実は面倒なので別ファイルを用意して後からファイルごと入れ替えるほうがイマドキっぽい
- IO#readlineはEOFで例外を上げるけど、IO#getsはfalseを返す
- CRuby内部で使われるStringのコンストラクタには何種類かあって、rb_str_new2はC文字列を渡すと、そのstrlenを取って、本物の(?)rb_str_newに渡してくれる。USASCIIの場合はrb_usascii_str_newというのもある。rb_str_new2はrb_str_cstrの別名としてdefineされていて、これは歴史的経緯っぽい
- Rubyって本体を壊して遊ぶのも楽しい