任意の Ruby プログラムをアルファベットだけにするプログラム

数字もまぁなんか消せると思う。

任意の Ruby プログラムをアルファベットと数字だけにするプログラム

とサラッと言われているのだけれど、思いつくのにかなり苦労しました。なんとか 2 つの方法を見つけた。
出題から 1 年半以上経っているので、以下にぼくの回答を書きます。みたくない人は見ないでください。




















ポイントは、任意の文字列を構成する各 ASCII コードをどうやってアルファベットだけで作るか、というところです。

Hello, world

class                                        String
def                                         inspect
concat   begin   dup  ensure  replace  String   nil
concat      concat      concat     concat      size
concat                  concat                 size
concat                  concat                 size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat  concat  concat  concat concat  concat  size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat      concat      concat     concat      size
concat          concat         concat          size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat          concat         concat          size
concat                  concat                 size
concat          concat         concat          size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat                  concat                 size
concat      concat      concat     concat      size
concat                  concat                 size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat          concat         concat          size
concat                  concat                 size
concat                  concat                 size
concat                  concat                 size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat                  concat                 size
concat          concat         concat          size
concat                  concat                 size
concat                  concat                 size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat  concat  concat  concat concat  concat  size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat          concat         concat          size
concat                  concat                 size
concat          concat         concat          size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat          concat         concat          size
concat                  concat                 size
concat                  concat                 size
concat                  concat                 size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat          concat         concat          size
concat                  concat                 size
concat          concat         concat          size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat          concat         concat          size
concat                  concat                 size
concat          concat         concat          size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat      concat      concat     concat      size
concat          concat         concat          size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat      concat      concat     concat      size
concat      concat      concat     concat      size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat      concat      concat     concat      size
concat                  concat                 size
concat                  concat                 size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat  concat  concat  concat concat  concat  size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat                  concat                 size
concat      concat      concat     concat      size
concat                  concat                 size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat                  concat                 size
concat          concat         concat          size
concat          concat         concat          size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat                  concat                 size
concat          concat         concat          size
concat          concat         concat          size
concat                                         size
concat begin size ensure replace String nil end end
concat   begin   dup  ensure  replace  String   nil
concat                  concat                 size
concat                  concat                 size
concat    concat   concat   concat   concat    size
concat begin size ensure replace String nil end end
eval                                           self
exit                     end                    end
copyright MMX Yusuke Endoh             p String nil

caller と for を使う方法

最初に思いついたのはこの方法。caller を使って所望の長さの配列を作り、その size を呼び出すことで任意の数値を得ます。

def foo1; caller; end
def foo2; foo1; end
def foo3; foo2; end
def foo4; foo3; end

foo4.size  #=> 4 (呼び出したコンテキストによって多少ずれる)

しかし size がそのままでは呼び出せない (ドットを使ってしまう) ので、for 文を使います。

for i in ary do ... end

ary.each {|i| ... }

は (多分) 等価というのは知っていたんですが、返り値まで一緒というのは初めて知りました。つまり for 文を使うと each を呼び出し、その返り値を得ることができます。
以上をまとめると、以下のようになります。

require "erb"

code = "puts 'Hello, world!'"

ERB.new(DATA.read, nil, "%").run
__END__
class Array
  def each
    size
  end
end
class String
  def aaaaabb
    caller
  end
% (4..127).each do |x|
  def <%= ("%07b" % x).tr("01", "ab") %>
    <%= ("%07b" % (x - 1)).tr("01", "ab") %>
  end
% end
  def inspect
% code.each_byte do |x|
    concat for i in <%= ("%07b" % x).tr("01", "ab") %> do end
% end
    eval self
    exit
  end
end
p String nil

ただ、この方法は caller のために 127 個くらいのメソッドを定義するので、そこがかっこ悪いです。

ensure 節を使う方法

もうひとつは、String#inspect の中で所望の長さの文字列を作り、その size を呼び出すことで任意の数値を得る方法。上の Hello, world はこっちの方法を整形したものです。

class String
  def inspect
    replace ""
    concat size
    concat size
    concat size
    ...
    size
  end
end

concat size の連続の部分は、concat self をあわせて使えば長さを 2 倍にできることを使えば短くできます。
ただ、これと同じ方法 (String#inspect の中で concat) でコード本体も作るので、単純に replace "" してしまうと今まで作ってきたコード本体が消えてしまいます。そこで ensure 節を利用します。

p begin; 1; ensure; 2; end

というプログラムが 1 を返す (2 は捨てられる) というのを活用して、以下のようにします。

concat begin
  dup
ensure
  replace ""
  concat size
  concat size
  concat size
  ...
  concat begin
    size
  ensure
    replace ""
  end
end

口では説明しかねますが、評価順序がうまい感じになっていて、文字列の先頭に任意の数値を追加するコードになっています。
以上をまとめると、以下のようになります。

require "erb"

code = "puts 'Hello, world!'"

ERB.new(DATA.read, nil, "%").run
__END__
class String
  def inspect
% code.each_byte.to_a.reverse_each do |x|
    concat begin
      dup
    ensure
      replace String nil
%   ("%b" % x)[0..-2].scan(/10*/) do |b|
      <%= "concat " * b.size %>concat size
%   end
%   if x % 2 == 1
      concat size
%   end
      concat begin
        size
      ensure
        replace String nil
      end
    end
% end
    eval self
    exit
  end
end
p String nil

open problem

小文字アルファベットだけ、は無理かなあ。