MinRubyを改造する 2024(REPLと宇宙線演算子の追加)
これは Ruby Advent Calendar 2024 の5日目の記事です。一つ前の記事は Rubyの多重代入における*variable記法、次の記事は RubyのHash、Struct、OpenStruct、Data、Class、ActiveModel、ActiveRecordどうやって使いわけてる? - DIGGLE開発者ブログ です。
2年前の Ruby Advent Calendar でこういうのを書きました。今回はこの続きです。
今年は Ruby Advent Calendar に書くネタ思いつかないな、見送りかな、と思っていましたが、思いついてしまったので半日くらいでババッとやりました。5日目が空いていたので後埋めで入れさせてもらいます。
RubyでつくるRuby ゼロから学びなおすプログラミング言語入門
リンク貼っておきますね。みなさん『RubyでつくるRuby』をやりましょう!
できたもの
repl
ブランチと cosmic-ray-op
ブランチを生やしました。網羅的な動作確認はできていないのでたぶん変なところがあります。気づいたら後で直します。
REPL の追加
「REPL がないと辛い……」というわけではなくて必要性は薄いのですが、思いついたのでやってみました。以前書いた ↓これを使って MinRuby に REPL を追加してみたらどうかな? という素朴な思いつきです。
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 のしおいさんの発表でしょう。
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 です。
そういえば今年はデータサイエンス関連の活動もできなかったなあ。何かできたらよかったのですが。
dxopal-demos にマーブル模様を描くデモを追加しました。
ベクトルをちょちょいとやるだけなので実装は簡単、でもおもしろい絵が描けておもしろい。
先日の Ruby World Conference では小芝さんによるクリエイティブコーディングの紹介がありましたが、 DXOpal でもいろいろできますよー。
そういえば今年は DXOpal が 1.6.0 にバージョンアップしましたね。
同じく dxopal-demos になんちゃって物理演算のデモを追加しました。
かなり簡易なものですが、意外とそれっぽく動いてくれますね。
おまけで去年の Advent Calendar 向けに書いたものも貼ってみます。よろしければこちらもどうぞ。
最後とりとめのない感じになりましたが以上です。よいお年を〜。
Discussion