前作: Python 1.0 をビルドする - Qiita

再度、ちょっとした考古学的興味により初期のRubyを調べてみたくなったので、Ruby 0.49 をビルドして動かしてみた。

ソースコード入手

Rubyが初めてインターネットに公表されたのは1995年12月21日、バージョンは0.95でのことである (fj.comp.oops)。またfjへ投稿される前にもいくつかのクローズドアルファが公開されており、遡って Ruby 0.65 などのバージョンが公開されていた (Rubyist Magazine)

それ以前のソースコードは長らくmatzが個人的に保管していたが、2006年にmatzがバージョン0.49以降のソースコードを公開して以来 (rubi-talk)、0.49が現存する最も古いRubyのソースコードとして知られている。私家版Ruby史では1994年6〜7月頃のコードと推定されており、Rubyが命名されてから1年と少しのころのコードの模様である。

公開されたバージョン0.49のソースコード (ruby-0.49.tar.gz) は以下から入手できる。

ftp://ftp.ruby-lang.org/pub/ruby/1.0/

しかしこのコード、現在の環境ではエラーが多発してまるでコンパイルできない。時期的には前回試した Python 1.0.1 とほぼ同時期のはずだが、依存するライブラリが入手困難なものが多く、簡単にはビルドまで辿り着けない。

が、世の中には酔狂な人頼もしい同士がいるもので、現代の環境でRubyを動かすためのhistorical-rubiesというパッチが、Charlie Somerville さんによって公開されている。

https://github.com/charliesome/historical-rubies

というわけで、さっそくビルドしてみる。

ビルド

Ubuntu 64bit 環境を想定。

historical-rubiesのパッチでは32bitバイナリをターゲットにしているので、クロスコンパイル用の環境をセットアップする。

sudo dpkg --add-architecture i386
sudo apt update
sudo apt install gcc-multilib

依存ライブラリをいくつかインストールする。

sudo apt install libgdbm-dev:i386 libssl-dev:i386 bison

続いてソースコードを入手し、パッチを当てる。

curl http://ftp.ruby-lang.org/pub/ruby/1.0/ruby-0.49.tar.gz -LO
tar xzf ruby-0.49.tar.gz
cd ruby
curl https://raw.githubusercontent.com/hakatashi/historical-rubies/master/ruby-0.49.patch -LO
patch -p1 < ruby-0.49.patch

で、あとは頑張ってビルドするだけ。

./configure
sed -i 's/@//' Makefile
make CFLAGS="-g -m32" LDFLAGS="-g -m32" LIBS="-L/usr/lib32 -lm -lgdbm -lgdbm_compat -lcrypt"

なお、テスト用に作成したDockerfileがあるので参考にしてもらいたい。

動かしてみる

出てきたバイナリファイルのrubyを使って、少し遊んでみる。

ではさっそくおなじみの hello world から⋯⋯。

$ ./ruby <<<"puts 'Hello, World'"
-:1: syntax error

文法エラーとなる。どうやらカッコを省略できないようだ。

そこでカッコを書いて色々と試してみるが⋯⋯。

$ ./ruby <<<"puts('Hello, World')"
Segmentation fault
$ ./ruby <<<"p('Hello, World')"
Segmentation fault
$ ./ruby <<<"STDOUT.write('Hello, World')"
Segmentation fault

うまくいかない。これは本当にRubyなのか?

正しい文字出力関数はprintである。

$ ./ruby <<<"print('Hello, World')"
Hello, World

現代では「多様性は善」と言われるRubyも、さすがにこの頃は一つしかやり方がなかった模様。

sampleを眺めてみる

tarballに含まれているsamplesディレクトリの中には、この頃動いていたサンプルコードが多数含まれている。

いくつか興味深いものを拾ってみよう。

fib.rb
def fib (n)
  if n<2
    n
  else
    fib(n-2)+fib(n-1)
  end
end
print(fib(20), "\n");

暗黙の返り値やif文など、「すべてがオブジェクト」というRubyの思想がこの頃からしっかり設計されていたことがわかる秀逸なコード。

また、インデントはこの頃からスペース2つである。

math.rb
#load("lib/math.o")
include Math
sqrt(4)
print(Math.sqrt(257), "\n")

トップレベルにMathモジュールをincludeして実行している。現代でも動くコードだが、Ruby 0.49 で動かすとなぜかsqrtではなくlog10した結果が帰ってくる。それもそのはずで、該当部分のコードには、

math.c
static VALUE
Fmath_sqrt(obj, x)
    VALUE obj;
    struct RFloat *x;
{
    Need_Float(x);
    return float_new(log10(x->value));

    if (x->value < 0.0) Fail("square root for negative number");
    return float_new(sqrt(x->value));
}

と書かれている。matzがデバッグを行っていた痕跡だろうか。

attr.rb
class Foo
  attr("test", %TRUE)
end

foo = Foo.new
foo.test = 10
print(foo.test, "\n")
foo._inspect.print
print("\n")

%TRUEという謎の識別子が登場する。ソースコードを読む限りTRUEを表す定数だった模様。

tt.rb
module Print
  print("in Print\n")
  def println(*args)
    for a in args
      print(a)
    end
    print("\n")
  end def

  def println2(*args)
    print(*args)
    print("\n")
  end def
end module

end defend module など、今からだと考えられない文法が用いられており震える。matzはRubyのendをいたく気に入っているようだが、先発のVerilog (1984年) などの影響を受けてのものだったのだろうか。(なお、Rubyに影響を与えた言語の中で、構文にendを用いるのはPASCALのみである)

おまけ: Ruby 1.0 の場合

wget http://ftp.ruby-lang.org/pub/ruby/1.0/ruby-1.0-971225.tar.gz
tar xzf ruby-1.0-971225.tar.gz
wget https://github.com/charliesome/historical-rubies/raw/master/ruby-1.0-971225.patch
cd ruby-1.0-971225
patch -p0 < ../ruby-1.0-971225.patch
./configure
make DLDFLAGS="-shared -melf_i386"