Ruby処理系自作入門

###はじめに

この記事は[Ruby Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ruby) 22日目の記事です。遅刻して申し訳ありません。

Rustという言語でrurubyというRuby処理系の独自実装を作っており、今回はその紹介をします。RustでRubyということでrurubyという安直なネーミングにしましたが、発音しずらい上に本家Rubyと紛らわしく恐れ多いので後悔しております。もう少し技術的なポエムを[言語実装Advent Calendar 2020](https://qiita.com/sisshiki1969/items/4d76e69545ca1c26ed48)に書きましたので、興味のある方はそちらもご参照頂ければ。

###Rustについて

[Rust](https://www.rust-lang.org/ja)は比較的新しいプログラミング言語で、C言語と同様の静的型付け言語です。最新の研究成果を取り入れた柔軟で堅固な型システムと型推論機構を持ち、高速かつメモリ安全・スレッド安全な言語となっています。また、高度な抽象化機能を実行速度を犠牲にしないやり方でうまく取り入れていて、言語処理系のようなシステムプログラミングには最適です。

逆に、所有権システムという独特の仕組みがあってプログラマに一定の制約が課されます。コンパイルが通れば安全ですが、慣れるまではコンパイルを通すまでが大変、と巷では言われています。プログラムの書きやすさは犠牲にして高速性と安全性を追求するという、ある意味Rubyとは正反対の思想のプログラミング言語ですね。（どちらの言語が良いか、という不毛な議論はするつもりはありません。場面に応じて自分が使いやすいものを使えばよいと思います。念のため。）

###実装の現状について

基本的には現在のRubyと同様にプログラムを中間表現（バイトコード）にコンパイルして仮想マシンが解釈実行するタイプの処理系です。パーサは独自に古典的な再帰下降パーサを心を込めて手書きで書いています（つらい）。不要になったオブジェクトを回収するガーベジコレクタも、単純なものですが自前で実装しています。
現在の進捗状況ですが、ほとんどの制御構文やリテラルは動きます。組み込みクラスは基本的なものは実装していますが、まだまだクラスもメソッドも足りていないのと、特殊変数など、まだ適当な実装になっている部分が多数あります。

Rubyベンチマーク界のゴールデンスタンダードである[Optcarrot](https://mametter.hatenablog.com/entry/20160401/p1)も動きます。実行速度の方も頑張ってはいますが、残念ながらRuby3.0.0-previewの2.7倍、--optという自己書き換えで高速化するオプションをつけた場合は2.1倍遅いです。[いろいろなベンチマークの結果はこちら](https://github.com/sisshiki1969/ruruby/wiki/Benchmarks)
![opt_bench.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332404/474282a2-cefb-668a-f41c-4d7012fa22ab.png)

今年の夏に話題になった大塚製薬のカロリーメイト・リキッドの[プログラマ向けCUIサイト](https://www.otsuka.co.jp/cmt/to_programmer/cui/)に隠されている~~人間にはとても読めないひどい~~超絶技巧を駆使したプログラムである[Quine](https://mametter.hatenablog.com/entry/2020/08/05/150835)も動かしました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">動きカクカク問題、<a href="https://twitter.com/Linda_pp?ref_src=twsrc%5Etfw">@Linda_pp</a> さんの助言により完璧に動作するようになりました！（嬉しかったのでしつこく投稿） <a href="https://t.co/tvPV1K4a3i">pic.twitter.com/tvPV1K4a3i</a></p>&mdash; monochrome (@s_isshiki1969) <a href="https://twitter.com/s_isshiki1969/status/1295744313444282368?ref_src=twsrc%5Etfw">August 18, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Rubyの標準ライブラリもrequireで読めるようになっています。ただCバインディングを使って書かれたものは動きませんし、一部文法がサポートされていないのと、メソッドも足りないので全然ダメです。実はこの原稿を書くにあたっていくつか試しましたが、ほとんど動かなかったのでへこんでいます。一つ一つ動かせるようにしていくことが当面の課題です。

###使い方

レポジトリはこちらです。
https://github.com/sisshiki1969/ruruby

```shell-session
❯ git clone https://github.com/sisshiki1969/ruruby.git
❯ cd ruruby
```

実行環境としてはLinuxを想定しています。Rustが動く環境であればWindowsでもOKなはずですが検証していません。WSL2やdocker上での実行を推奨します。

実行にはまずRustのインストールが必要です。少し手数がかかりますが、基本的にはコマンド一発で終わりますので、Rubyのビルドに比べると飛躍的に簡単です。
https://www.rust-lang.org/ja/learn/get-started

Rustは`stable`/`beta`/`nightly`という３つの[リリースチャンネル](https://doc.rust-jp.rs/book-ja/appendix-07-nightly-rust.html)（バージョンみたいなものだと思ってください）があり、rurubyの実行には`nightly`チャンネル（Rubyでいうとpreview的なもの）が必要です。 
インストールの際に`nightly`を選択するか、もしくはとりあえずデフォルトのインストールを行い、その後で下記のように`nightly`をデフォルトのチャンネルとしてください。

```sh
❯ rustup install nightly
❯ rustup default nightly
```

レポジトリのルートで`cargo run`を実行すると自動的にコンパイルされ、できた実行ファイルが実行されます。`--release`オプションを付けるとコンパイルに時間がかかりますが、最適化され、高速化された実行ファイルが生成・実行されます。

実行ファイルは`target/debug/ruruby`(--release なしの場合)か`target/release/ruruby`(--release ありの場合)に配置されますが、cargo runを使えば適当に実行されますのであまり気にしなくて大丈夫です。
`tests/`以下にいくつかベンチマーク・プログラム（[多くはRubyのリポジトリ内にあるもの](https://github.com/ruby/ruby/tree/master/benchmark)）がありますので試してみてください。  

まずは定番、[フィボナッチ数列の素朴な計算](https://github.com/ruby/ruby/blob/master/benchmark/app_fib.rb)です。

```shell-session
❯ cargo run --release tests/app_fibo.rb
   Compiling ruruby v0.2.0 (/mnt/c/Users/sissh/Documents/GitHub/ruruby)
    Finished release [optimized] target(s) in 30.04s
     Running `target/release/ruruby tests/app_fibo.rb`
9227465
```

[aobench](https://github.com/ruby/ruby/blob/master/benchmark/app_aobench.rb)はシンプルなレイトレーシングです。標準出力に`.ppm`形式のデータを吐き出しますので、`convert`コマンド（[ImageMagick](https://higuma.github.io/2016/08/31/imagemagick-1/)）などで適当なフォーマットに変換する必要があります。

```shell-session

❯ cargo run --release tests/app_aobench.rb > ao.ppm
    Finished release [optimized] target(s) in 1.15s
     Running `target/release/ruruby tests/app_aobench.rb`![aobench.PNG]
❯ convert ao.ppm ao.jpg

```

![ao.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332404/6b1045f6-4afb-ae65-5cc4-4afb2b484b78.png "ao.jpg")

ファイル名を省略するとREPL（Rubyでいう`irb`）が起動します。

```sh-session
❯ cargo run
   Compiling ruruby v0.2.0 (/mnt/c/Users/sissh/Documents/GitHub/ruruby)
    Finished dev [unoptimized + debuginfo] target(s) in 1m 24s
     Running `target/debug/ruruby`
irb:0> a = "Ruby"
=> "Ruby"
irb:0> puts "Hello #{a} world!"
Hello Ruby world!
=> nil
```

エラー処理もエラーの発生場所と対処方法が分かりやすいように頑張ってスタックトレースを表示してくれます。

```sh-session
❯ cargo run --release tests/so_nbody.rb
    Finished release [optimized] target(s) in 1m 26s
     Running `target/release/ruruby tests/so_nbody.rb`
-0.169075164
NoMethodError(no method `distances' for #<Planet:0x7f7509584a00>:Planet)
0:/mnt/c/Users/sissh/Documents/GitHub/ruruby/tests/so_nbody.rb:31
      mag = dt / (distances * distance * distance)
                  ^^^^^^^^^
1:/mnt/c/Users/sissh/Documents/GitHub/ruruby/tests/so_nbody.rb:143
    b.move_from_i(BODIES, nbodies, dt, i + 1)
      ^^^^^^^^^^^
2:/mnt/c/Users/sissh/Documents/GitHub/ruruby/tests/so_nbody.rb:139
n.times do
  ^^^^^
```

###まとめ

「Ruby処理系自作入門」と題していますが「オレの作ったRuby自作処理系への入門」になってしまいました。ご容赦ください。
言語処理系製作、大変楽しく勉強になりますので（Rustの勉強にももちろんなりますし、Rubyの実行モデルの理解も深まりますので一挙両得です）、皆さんもぜひオレオレ言語処理系製作にチャレンジしてみてください。