Zenn
🌌

MinRubyを改造する 2024(REPLと宇宙線演算子の追加)

2024/12/14に公開

これは Ruby Advent Calendar 2024 の5日目の記事です。一つ前の記事は Rubyの多重代入における*variable記法、次の記事は RubyのHash、Struct、OpenStruct、Data、Class、ActiveModel、ActiveRecordどうやって使いわけてる? - DIGGLE開発者ブログ です。


2年前の Ruby Advent Calendar でこういうのを書きました。今回はこの続きです。


https://qiita.com/sonota88/items/84e4c16e5602b9771e92

今年は Ruby Advent Calendar に書くネタ思いつかないな、見送りかな、と思っていましたが、思いついてしまったので半日くらいでババッとやりました。5日目が空いていたので後埋めで入れさせてもらいます。

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

リンク貼っておきますね。みなさん『RubyでつくるRuby』をやりましょう!


https://www.lambdanote.com/products/ruby-ruby

できたもの

https://github.com/sonota88/cookpad-hackarade-minruby

repl ブランチと cosmic-ray-op ブランチを生やしました。網羅的な動作確認はできていないのでたぶん変なところがあります。気づいたら後で直します。

REPL の追加

「REPL がないと辛い……」というわけではなくて必要性は薄いのですが、思いついたのでやってみました。以前書いた ↓これを使って MinRuby に REPL を追加してみたらどうかな? という素朴な思いつきです。


https://memo88.hatenablog.com/entry/20210417_reline_readmultiline

3年前に書いたメモですが、特に変更点はなくてそのまま使えました。

使い方

repl ブランチに切り替えて以下で実行できます。

bundle exec ruby repl.rb

入力の区切りの判定

複数行の入力を受け付ける場合、「ひとまとまりの入力」の区切りをどうするか決める必要があります。今回は「それまでに入力された内容がパースできるか」で判断することにしました。

たとえば

>>> def add(a, b)

と入力してエンターキーを押すとパースに失敗します。パースに失敗すると「まだ入力し終わっていない、途中の状態である」とみなされ、続けて次の行が入力できる状態になります。ここからさらに

>>> def add(a, b)
  |   a + b
  | end

まで入力してからエンターキーを押すと、今度はパースに成功し、関数を定義する処理が実行されます。

簡易な方式の割にはそれっぽく動いているようですね。

複数行コピーしてクリップボードからペーストしても大丈夫。

>>> if 7 > 5
  |   p(1)
  | else
  |   p(2)
  | end
  | 
1

こういうのをサッと作れるのは嬉しいですね。 Reline すごい。

環境を引き継ぐ

genv と(トップレベルの)lenv を使いまわすようにしました。主な部分だけ抜き出すとこんな感じ。

genv = {
  "p" => ["builtin", "p"]
}
lenv = {}

loop do
  # read
  text = ... 入力を受け取る ...
  tree = MyMinRubyParser.minruby_parse(text)
  # eval
  result = evaluate(tree, genv, lenv)
  # print
  puts result
end

たとえば

>>> x = 42

と入力してエンターキーを押すと代入式が評価され、トップレベルの lenv が { "x" => 42 } に更新されます。それが以降の実行にも引き継がれ、

>>> p(x)

と入力すると変数 x の値 42 が式の評価結果として表示されます。関数も同様で、定義した関数は genv に登録され何度でも呼び出すことができます。

宇宙線演算子の追加

演算子の追加と聞いて思い出すのは RubyKaigi 2023 のしおいさんの発表でしょう。


https://rubykaigi.org/2023/presentations/coe401_.html#day2

CRuby をいじるのは時間と知識が足りなくてすぐやるのは無理そうです。しかし私たちには MinRuby があります。MinRuby ならすぐできる。

ところで宇宙線演算子とは何でしょうか? これは少し前に宇宙演算子 <=> のダジャレでなんとなく思いついたものです。オペランドのランダムなビットを低確率で反転します。実用性はないと思います。

! や単項の - のように前置するタイプの単項演算子で、~~123 のように書きます。記号は適当に ~~ にしました。なんとなく波(電磁波)っぽく見えませんか?
(……と書いておいて後から気づきましたが電磁波は関係ないかもしれません(よく分かってない)。とりあえず ~~ のままでいいかな……)

たとえば ~~123 という式をパースすると ["~~", ["lit", 123]] という構文木が得られます。あとはインタプリタで評価する時にビットを反転する処理に渡せばOK。

def emit_cosmic_ray(n)
  # 低確率でランダムなビットを反転した結果を返す
end

def evaluate(tree, genv, lenv)
  case tree[0]
  # ...
  when "~~"
    emit_cosmic_ray(evaluate(tree[1], genv, lenv))
  # ...

REPL を使って試してみましょう。

>>> ~~123
123
>>> ~~123
123
>>> ~~123
123
>>> ~~123
123
>>> ~~123
123
>>> ~~123
107

ヨシ!(ほんとに低確率にしてしまうと確認しにくいのでとりあえず 1/10 の確率にしました)

format("%b", 123) #=> "1111011"
format("%b", 107) #=> "1101011"
                         ^
                         このビットが反転した

ビット演算、普段なかなか縁がないのですが、今回ちょっと使えてよかったです。 Integer#bit_length とか Integer#[] とか今回初めて使いました。

バージョンなど

せっかくの Advent Calendar なので今年書いたものとかいろいろ

今年はあまりいろいろできませんでしたが、Ruby 関連だと diff についてのメモを書きました。ごく普通の diff です。


https://zenn.dev/sonota88/articles/6efd83d606cc2f

そういえば今年はデータサイエンス関連の活動もできなかったなあ。何かできたらよかったのですが。


dxopal-demos にマーブル模様を描くデモを追加しました。
https://sonota88.github.io/dxopal-demos/marbling/index.html

ベクトルをちょちょいとやるだけなので実装は簡単、でもおもしろい絵が描けておもしろい。

先日の Ruby World Conference では小芝さんによるクリエイティブコーディングの紹介がありましたが、 DXOpal でもいろいろできますよー。

そういえば今年は DXOpal が 1.6.0 にバージョンアップしましたね。


同じく dxopal-demos になんちゃって物理演算のデモを追加しました。

https://sonota88.github.io/dxopal-demos/physics001/index.html

かなり簡易なものですが、意外とそれっぽく動いてくれますね。


おまけで去年の Advent Calendar 向けに書いたものも貼ってみます。よろしければこちらもどうぞ。

https://qiita.com/sonota88/items/105e141d18372918e675


最後とりとめのない感じになりましたが以上です。よいお年を〜。

Discussion

ログインするとコメントできます