このプログラムは、Ruby 0.49(1994年リリース)からRuby 3.2.1(今月リリース)まで、現在確認されているすべてのCRubyで動作するQuineです。
eval($s=("t='eval($s=('+d=34.chr;s=3 2.chr+$s*i=8;v=$VERSION||eval('begin;v=V ERSION;rescue;v||RUBY_VERSION;end');f=('?'*8 +'A|'+'?'*20+'G?c'+'?'*15+'A@CXx@~@_`OpGxCxp@~pO xS|O~G?c?q?xC`AP|q?x_|C_xC_xO@H@cG?G?qA|_|_`GCpOxC|H NFccqq@`_|OF@`?q?x_@x_x_`GB`O``O~G?C@qCxCxP@D@|G~C?pO|C? pO|C?AP|A~HNN`ccxC|Q@L@B"+"GpGpc@p?x_`GB`???_@FO|OB@ xC|P`@?c?q?HPx@~@_`G@`????@L^`?q?x?xq@|_|O~GC` xA~@_@GBD').unpack('c*');w=4+v.length*u= 15;r=10.chr;j=0;while-24+w*u>i=1+i ;x=i%w;x>0||t=t+d+'+'+r+d;k= i/w%12>2&&x%u>3&&x%u+i /w*11-34+('-._'. index(c=v[ x/u,1] )||c.hex +3)*99| |0; k=f [k/6 ][k% 6]; t=t +s[ k*j =k+ j,1 ]end;pr int (t+ d+' ).s pli t.j oin [0, 609 ])# Y.E. '+r) ").split .join)#
プログラミング言語Ruby 30周年記念イベントでLT発表したものです。コードはGitHubに置きました。
動作方法
全バージョンで実行するには、rubylang/all-rubyというdocker imageを使ってください。
$ wget https://raw.githubusercontent.com/mame/all-ruby-quine/main/all-ruby-quine.rb $ docker run --rm -ti -v `pwd`:/src rubylang/all-ruby
./bin/ruby-0.49 /src/all-ruby-quine.rb
とすると、ruby 0.49で実行できます。
# ./bin/ruby-0.49 /src/all-ruby-quine.rb eval($s=("t='eval($s=('+d=34.chr;s=32.chr+$s*i=8;v=$VERSION||eval"+ "('begin;v=VERSION;rescue;v||RUBY_VERSION;end');f=('?'*8+'A|'+'?'"+ "*20+'G?c'+'?'*15+'A@CXx@~@_`OpGxCxp@~pOxS|O~G?c?q?xC`AP|q?x_|C_x"+ "C_xO@H @cG?G?qA|_|_`GCpOxC|HNFcc qq@`_|O F@`?q"+ "?x_@ x_x _`GB`O``O~G?C@qCxCxP@D @|G~C? pO| C?pO"+ "|C?A P|A~H NN`ccxC|Q@L@BGpGpc@p? x _`GB`? ??_@F O|OB"+ "@xC| P`@?c ?q?HPx@~@_`G@`????@L ^` ?q?x?x q@|_ |O~G"+ "C`xA ~@_@G BD').unpack('c*');w =4+ v.lengt h*u="+ "15;r =10.c hr;j=0;while-24+w*u >i= 1+i;x=i%w;x>0| |t=t"+ "+d+' +'+r+ d;k=i/w%12>2&&x%u>3 &&x%u+i/w*11 -34+"+ "('-. _'. index(c= v[x/u,1])||c.h ex+3)* 99|| 0;k="+ "f[k/6] [k%6];t=t+ s[k*j=k+j,1]en d;print (t+d+"+ "').split.join[0,609])#Y.E.'+r)t='eval($s=('+d=34.chr;s=32.chr+$s"+ "*i=8;v=$VERSION||eval('begin;v=VERSION;rescue;v||RUBY_VERSION;en"+ "d');f=('?'*8+'A|'+'?'*20+'G?c'+'?'*15+'A").split.join[0,609])#Y.E.
同様に、./bin/ruby-3.2.1 /src/all-ruby-quine.rb
とすればruby 3.2.1で実行できます。
# ./bin/ruby-3.2.1 /src/all-ruby-quine.rb eval($s=("t='eval($s=('+d=34.chr;s=32.chr+$s*i=8;v=$VERSION||eval('begin;v=VERSI"+ "ON;rescue;v||RUBY_VERSION;end');f=('?'*8+'A|'+'?'*20+'G?c'+'?'*15+'A@CXx@~@_`Op"+ "GxCxp@~pOxS|O~G?c?q?xC`AP|q?x_|C_xC_xO@H@cG?G?qA|_|_`GCpOxC|HNFccqq@`_|OF@`?q?x"+ "_@x_x _`GB`O``O~G?C@qCxCxP@D @|G~C?pO|C?pO|C?AP|A~HN N`ccxC|Q"+ "@L@B GpGp c@p?x_`GB`???_@FO|OB @xC|P `@?c?q?HPx@~@_`G@`??? ?@L^`?q?"+ "x?xq@|_|O~GC `xA~@_@GBD').unpack('c*');w =4+v.length*u=15;r=1 0 .chr;j=0"+ ";while-24+w *u>i=1+i;x=i%w;x>0||t=t+d+'+ '+r+d;k=i/w%12>2&&x%u>3 &&x%u+i/"+ "w*11-3 4+('-._'.index(c=v[x/u,1])| |c.hex+3)*99||0;k=f[k/6] [k%6];t="+ "t+s[k*j=k+j ,1]end;print(t+d+').spli t.join[0,609])#Y.E.'+r)t=' eval($s="+ "('+d=34.chr; s=32.chr+$s*i=8;v=$VE RSION||eval('begin;v=VERSION ;rescue;"+ "v||R UBY_ VERSION;e nd');f=( '?'*8+'A|'+'?'* 20+'G?c'+'?' *15+'A@C"+ "Xx@~@ _`OpGxCxp@ ~pOxS|O~ G?c?q?xC `AP|q?x_| C_xC_"+ "xO@H@cG?G?qA|_|_`GCpOxC|HNFccqq@`_|OF@`?q?x_@x_x_`GB`O``O~G?C@qCxCxP@D@|G~C?pO|"+ "C?pO|C?AP|A~HNN`ccxC|Q@L@BGpGpc@p?x_`GB`???_@FO|OB@xC|P`@?c?q?HPx@~@_`G@`????@L"+ "^`?q?x?xq@|_|O~GC`xA~@_@GBD').unpack('c*');w=4+v.length").split.join[0,609])#Y.E.
./bin/ruby-3.2.0 /src/all-ruby-quine.rb | ./bin/ruby-0.49
で、ruby 3.2.0の出力をruby 0.49で動かせます。逆も可。
全バージョンで動かしたかったら ./all-ruby all-ruby-quine.rb
としてください。
Rubyの全バージョンで動くプログラムの作り方
たぶん誰の役にも立ちませんが、このQuineを書くのに得られた知見をまとめておきます。
ブロックは使用不可
ruby 0.49のブロックは、do ary.each using x ... end
という記法だったようです。現代で見る ary.each {|x| ... }
や ary.each do |x| ... end
のようなブロックはありません。旧式の文法は現代のRubyではsyntax errorになるので、全バージョンで動かすにはブロックは使えません。while
文などでがんばりましょう。
x += 1
は使用不可
ruby 0.49には x += 1
や x ||= 1
のような構文がありません。x = x + 1
などと書き下します。
三項演算子は使用不可
ruby 0.49には cond ? a : b
がまだありません。if
文はありますが、ちょっと長いので cond && a || b
などとするとおしゃれで しょう。
大きい文字列リテラルは使用不可
ruby 0.49で大きめ(700バイトくらい)の文字列リテラルを作るとSEGVするようでした。文字列の連結を使って回避します。ちなみに文字列の連結で動くということは、全バージョンでGCがそこそこ安定して動いているってことなので、結構すごいことです。
%-記法のリテラルや式展開は使用不可
この手のQuineで非常に便利な %(...)
という文字列リテラルは、ruby 0.49では未実装です。String#split
やArray#join
があるので、コードのアスキーアート化は eval(s="...メインのコード...".split.join)
とすれば可能です。
あと、"#{expr}"
もないので注意。がんばって文字列連結しましょう。
evalの中でメソッド定義は使用不可
ruby 1.3.1-990324 では eval("def foo; end")
などとすると nested method definition
という例外になります。おそらくバグなんですが、いずれにせよ全バージョンで動かすためには使えません。
evalの中でコメントは使用不可
ruby 1.1d0 で eval("1#")
などとするとSEGVします。間違いなくバグですが、コメントを使うのは避けましょう。
evalでローカル変数を参照するのは不可
再現条件がやや微妙なのですが、ruby 0.51など初期バージョンでは次のコードでSEGVします。
s = "hello"; eval("print(t = s)")
eval
で外のローカル変数を読み出すところにバグがあるようでした。グローバル変数を使うと安定して動きました。
$s = "hello"; eval("print(t = $s)")
str[idx]に注意
Ruby 1.8まで、str[idx]
はidx番目のバイトを整数で返していましたが、1.9からは1文字の文字列に変わっています。なのでstr[idx]
は使わないのが無難です。どうしても使いたかったら、たとえば次のように書けば良いでしょう。
ch="ABC"[1]; "@"[0] == 64 && ch=[ch].pack("C*")
これで全バージョンで ch == "B"
になります。ポイントは、"@"[0]
が整数かどうかを見て分岐するところです(?@ == 64
でもよい)。型がなくてよかったですね。
利用可能な組み込みメソッドに注意
当然ですが、昔のRubyは今ほど組み込みメソッドが充実していないので、何が利用可能かを慎重に試す必要があります。とはいえ、ruby 0.49の時点で意外と多いです。現代の組み込みメソッドの半分以上はすでにあるんじゃないかな。
どんなメソッドがあるかは、ruby 0.49に同梱されているspecというファイルが便利です。かつて「Rubyにはドキュメントがない」と言われていたのはなんだったのか。
Rubyのバージョン番号を出力するプログラム
all-ruby-quineの肝はインタプリタのバージョン番号を取得するところなのですが、これが結構 hacky でした。その部分だけ取り出した次のコードが、各Rubyバージョンでどのように解釈されるか説明しておきます。
print($VERSION||eval('begin;v=VERSION;rescue;v||RUBY_VERSION;end'))
このコードが各バージョンでどのように解釈されるかを説明しておきます。
0.49 .. 0.65
これらのバージョンでは $VERSION
が定義されています。よって、$VERSION || ...
の後半は評価されず、そのまま $VERSION
が返ります。後半でeval
を使っているのは、これらのバージョンでは begin; ...; end
の構文がまだ存在せず、そのまま書いたら syntax error になるからです。
0.69 .. 0.76
ここが一番おもしろいです。これらのバージョンでは $VERSION
が定義されておらず、定数のVERSION
が定義されています。後述する1.9.0からはRUBY_VERSION
にリネームされるのですが、RUBY_VERSION
を参照すると例外になってしまうので、少し工夫が必要です。次のようにしました。
begin v = VERSION rescue v || RUBY_VERSION end
rescue
のインデントがおかしいのは意図的です。というのも、これらのバージョンでは例外捕捉キーワードは resque
であり、rescue
はただのメソッド呼び出しと解釈されます。「なら undefined method エラーになるのでは?」と思うかもしれませんが、幸いなことにこれらのバージョンでは「未定義メソッド呼び出しは黙って nil
を返す」というパワーのある仕様です。よって、変数 v
に VERSION
の中身が代入されたあと、v || RUBY_VERSION
の後半は評価がショートカットされるので、無事 VERSION
が返されます。
0.95 .. 1.8.7-p374
これらのバージョンでは VERSION
が定義されています。0.95で resque
が rescue
に変わったので、素直に v = VERSION
だけ評価されて値を返します。
1.9.0 ..
VERSION
定数が削除されたので、v = VERSION
は NameError
を投げます。しかし rescue
によって補足されるので、RUBY_VERSION
が評価されて値を返します。
感想
なにより驚いたのは、ruby 0.49の時点でかなりRubyになっていることです。現代のRubyの機能の半分以上はすでにありそう。
そして、そこからほとんど変わってないのもすごい。このQuine程度に非自明なプログラムが書けるくらいに、本質的な非互換がない。やりはじめたときは、ここまでできるとは思ってませんでした。
「Rubyは未完成だったので開発協力者がたくさん現れて、それがOSSとしての成功につながった」のようにmatzが語っているのはわりと有名ですが、そうはいっても「最初の時点でかなり完成していること、そしてそこからブレないこと」も成功するOSSの秘訣なのではないかという気がします。
それから、初期も含めて各リリースの品質が非常に高い。バグ回避のテクニックをたくさん書いたので言ってることと違うと思うかもしれませんが、言語処理系って多くの人が実戦で使ってはじめてまともになるものなんですよね。信じられない人はquine-relayを作ってみるといいです。世のマイナー言語処理系たちがいかにふつうのコードでもすぐSEGVするとか、致命的に機能が足りないとか、そもそも起動もしないとかがわかります。それを考えると、ユーザがほとんどいなかった初期でもRubyの各リリースの品質がここまで高いのは驚異的です。
ということで、matzはすごい!ということを体感できる遊びでした。だれかPythonとか他の言語でもやるといいと思います。
蛇足
ruby 0.49以前のコードが発掘されませんように。