Befungeは通常のプログラミング言語とは異なり二次元のコードグリッド上を命令ポインタ(IP: Instruction Pointer)が移動しながら実行されます。いまはあんま安定してないが、最終的には複数のIPが同時に自由に自己書き換えしながら動いてもよいようにしたい。あとサイズが1024x1024固定になってますが、ここループ構造にするか可変にするか悩んでます。
動画は命令を覚えるために自分で書いたシンメトリックHello Wolrd
一日強で実装しました。ここでの一日というのはもともと2日くらいかけてC++で書いてたがbit幅とか文字コードについて考えるのがつらくなってきたのでRustに移植した、という流れでの換算ですね。どうでもいいですが
:
や\
の謎
:
はスタックのトップを複製し、\
はスタックのトップ2つを入れ替える。これらの操作はシンプルだが、スタックが空や1つしかない状態でこれらを実行した場合の挙動がBefungeの仕様には明確に定義されていない。
これが結構つらく、ネットで出てくるインタプリタの動きを全部模倣することでどうにかしています。テストを手軽に書きやすいRustの構造がこういうとき嬉しい。というかrust-analyzerが偉い。clangdももうちょっと楽にならないかな…
謎仕様の互換性に関しては、色々ちょうどいいので、このサイトの例が動くことをとりあえずの目安にしました。
esolangs Befunge
&>:1-:v v *_$.@
^ _$>\:^
これが対応が案外大変な階乗です。とりあえず勝てました。もっと込み入ったやつに対応してるかは……もう朝5時なので今度見ます……
Rustのテストのうれしみ
今回Rustを使っていて本当に助かったのが、テストを書きやすいこと。
→
(独自拡張として導入しようとしている、初期化時の並列命令) とかの対応は流石に厳しいが、既存の命令ならばChatGPTに雑に投げればいい感じにテストを書いてくれる。 stackの順序とかたまに変になるので注意はいるが。
#[test]
fn test_subtract_command() {
let registry = CommandRegistry::new();
let subtract_command = registry.get_command('-').unwrap();
// Mock objects
let ip = Arc::new(Mutex::new(IPState::new(0, 0, Direction::Right)));
let mock_command_grid = MockCommandGrid::new();
let mock_io_handler = Arc::new(MockIOHandler);
// Push values to the stack for subtraction
{
let mut ip_locked = ip.lock().unwrap();
ip_locked.stk.push(5);
ip_locked.stk.push(3);
}
// Execute the command
subtract_command
.execute(ip.clone(), &mock_command_grid, mock_io_handler.clone())
.unwrap();
// Verify the result
let mut ip_locked = ip.lock().unwrap();
assert_eq!(ip_locked.stk.pop().unwrap(), 2, "5 - 3 should equal 2");
}
感想
この言語、インタプリタの実装めちゃ楽です。難しいのは静的コンパイラなんでしょうが、CとかRustとかで実装したら逐次評価でも十分速いんで気にならない。そもそもこれ静的にコンパイルできるんでしょうか。ある程度はasmに落とすビジョンは浮かぶのでやってみてもいいのかも。評価も案外z3とか使ったらいけるか?
展望
BeFungible(仮)は、二次元プログラミング言語基盤となる何らかを目標としてます。何になるのかは知らないです。
一日でReactでフロントまで作ってポートフォリオ代わりにならんかなとか思ってたけど、思ったより仕様の整合性で時間がかかったためいずれ…
皆さんも自分だけのカスタムFungeで謎の魔法陣を作成しましょう。