rrencodeを理解した

perlの予約語について検索したら、ppencoderrencodeに行き着きました。こんなに愛を感じるプログラムは見た事無かったです。で、ppencodeは解説もあって割とすっと理解できたんですが、rrencodeは理解するのに時間がかかりました。一応メモ書きをば。

出来るだけ簡潔に済ませる為に’a’と1文字だけ表示させるrrencodeプログラムを見てみます。

$ ./rrencode.rb a
#!/usr/bin/env ruby
($,&$,||@_=$>);$>< <($,&$,||(%!%!<<(?!+?!+?!))%(?{-?;+(?]-?=+?"-?!)));($,|$$&&@@_=$_);$>< <$/

どこから手をつければ良いか途方に暮れそうになりますが、まず手始めに ?c という構文から崩してみます。これは文字からアスキーコードを取得する構文で、この部分はすべて数字に置き換えられます。?cの部分をすべて数字に直してみましょう。

#!/usr/bin/env ruby
($,&$,||@_=$>);$>< <($,&$,||(%!%!<<(33+33+33))%(123-59+(93-61+34-33)));($,|$$&&@@_=$_);$>< <$/

まだまだ何の事か全く解りませんね。次に計算できる部分は計算して、セミコロンで改行を入れてみます。

#!/usr/bin/env ruby
($,&$,||@_=$>);
$>< <($,&$,||(%!%!<<(99))%(97));
($,|$$&&@@_=$_);
$>< <$/

何となく希望が湧いて来ました。次に特殊変数を処理します。ここではデフォルトの場合を仮定して以下の置き換えを行っています。リファレンスを見ながらちまちま置き換えましょう。

  • $,はnil
  • $/は”\n”
  • $$はProcess.pid
  • $>はSTDOUT

また、ついでに適宜空白を入れています。

#!/usr/bin/env ruby
(nil & nil || @_=STDOUT);
STDOUT< <(nil & nil || (%!%!<<(99))%(97));
(nil | Process.pid && @@_=$_);
STDOUT<<"\n"

ここまで来ると何となくプログラムの意味を想像できますね。さて、ここで2行目と4行目は単に値を表しているだけで、出力に影響を与えていないので削除します。

#!/usr/bin/env ruby
STDOUT< <(nil & nil || (%!%!<<(99))%(97));
STDOUT<<"\n"

2行目をもう少し整理してみましょう。nil & nilはfalseと、false || expressionはexpressionと等価である事を考えて整理します。明らかに冗長な括弧も削除しました。

#!/usr/bin/env ruby
STDOUT< <((%!%! << 99) % 97);
STDOUT<<"\n"

後一息です。%!文字列!はダブルクォート文字列を表しているので%!%!は”%”と等しいです。

#!/usr/bin/env ruby
STDOUT< <(("%" << 99) % 97);
STDOUT<<"\n"

さらに、文字列に対する<<演算子は、Stringクラスのconcatメソッドのエイリアスなので、concatメソッドで置き換えられます。

#!/usr/bin/env ruby
STDOUT< <(("%".concat(99)) % 97);
STDOUT<<"\n"

concatメソッドは0 ~ 255の値が渡された場合はその値をアスキーコードに持つ文字を文字列に付加します。なので”%”.concat(99)は99が’c’を表す事を考えると”%c”に等しくなります。

#!/usr/bin/env ruby
STDOUT< <("%c" % 97);
STDOUT<<"\n"

文字列 % argsという構文はStringクラスのsprintfメソッドのエイリアスなので、sprintfで置き換えます。

#!/usr/bin/env ruby
STDOUT< 

以上で、rrenodeされたプログラムがaと出力される仕組が解りました。複数文字を出力する場合は、sprintf文を文字数分だけ呼び出すようです。なるほど。何となくperlでも出来そうですけどどうなんでしょうか。

Leave a Reply

Your email address will not be published. Required fields are marked *