再度、ちょっとした考古学的興味により初期のRubyを調べてみたくなったので、Ruby 0.49 をビルドして動かしてみた。
プログラミング考古学シリーズ
- 第1回: Python 1.0 をビルドする
- 第2回: Ruby 0.49 をビルドする
- 第3回: Perl 1.0 をビルドする
ソースコード入手
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 さんによって公開されている。
というわけで、さっそくビルドしてみる。
ビルド
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 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ディレクトリの中には、この頃動いていたサンプルコードが多数含まれている。
いくつか興味深いものを拾ってみよう。
def fib (n)
if n<2
n
else
fib(n-2)+fib(n-1)
end
end
print(fib(20), "\n");
暗黙の返り値やif文など、「すべてがオブジェクト」というRubyの思想がこの頃からしっかり設計されていたことがわかる秀逸なコード。
また、インデントはこの頃からスペース2つだったようだ。
#load("lib/math.o")
include Math
sqrt(4)
print(Math.sqrt(257), "\n")
トップレベルにMathモジュールをincludeして実行している。現代でも動くコードだが、Ruby 0.49 で動かすとなぜかsqrtではなくlog10した結果が帰ってくる。それもそのはずで、該当部分のコードには、
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がデバッグを行っていた痕跡だろうか。
class Foo
attr("test", %TRUE)
end
foo = Foo.new
foo.test = 10
print(foo.test, "\n")
foo._inspect.print
print("\n")
%TRUE
という謎の識別子が登場する。ソースコードを読む限りTRUEを表す定数だった模様。
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 def
や end 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"