🦀

Rustで作るBEAMライクなLanguage VM - 4 ~ 8

2022/02/08に公開

引き続き、BEAM-like VM in Rustをしていく。

前回の記事

今日は Part 04からPart 08

Part 04 ~ Part 05 : 比較演算・ジャンプ


今回はジャンプ、比較のオペコードを追加して実装もしていきます。
書いて見せられると単純ですぐ理解できるものの、ジャンプってこういう仕組みなんだなぁとひとつ賢くなった気がします。(こなみ)
現在位置からの相対的なジャンプというのもあるんですねぇ。

これでifとかloopができるようになりました。

やったね。

https://github.com/ymdarake/iridium/pull/4
https://github.com/ymdarake/iridium/pull/5

instructions.rs
pub enum Opcode {
    LOAD,
    ADD,
    SUB,
    MUL,
    DIV,
    HLT,
     // ここから 
    JMP,
    JMPF,
    JMPB,
    EQ,
    NEQ,
    GTE,
    LTE,
    LT,
    GT,
    JMPE,
     // ここまで追加した
    IGL,
}

Part 06 ~ Part 07 : REPL


実装してきたものを色々お手軽に試すためにも(?)REPLを実装していきます。
これまで実装してきたVMに標準入力経由でプログラムをread_lineしてきて実行するだけ。

https://github.com/ymdarake/iridium/pull/6
https://github.com/ymdarake/iridium/pull/7

repl/mod.rs
// TODO: 昔のモジュールの書き方らしいから後で変えたい

match buffer {
".quit" => {
    writeln!(&mut writer, "Farewell! Have a great day!")
        .expect("Unable to execute .quit");
    writer.flush().unwrap();
    true
}
// (中略)
".registers" => {
    writeln!(&mut writer, "Listing registers and all contents:")
        .expect("Unable to execute .registers");
    writeln!(&mut writer, "{:#?}", self.vm.registers)
        .expect("Unable to write registers");
    writeln!(&mut writer, "End of Program Listing")
        .expect("Unable to write ending message of .registers");
    writer.flush().unwrap();
    false
}
_ => {
    let results = self.parse_hex(buffer);
    match results {
        Ok(bytes) => {
            for byte in bytes {
                self.vm.add_byte(byte)
            }
        }
        Err(_e) => {
            writeln!(&mut writer, "Unable to decode hex string. Please enter 4 groups of 2 hex charracters.").expect("Unable to write parse_hex error message");
            writer.flush().unwrap();
        }
    };
    self.vm.run_once();
    false
}

いい感じ(?)に自作言語感がでてきましたね。

余談1

ブログ記事の写経でプルリクをマージしたは良いものの、せっかく集計してるカバレッジが80%台まで落ちてしまったので、stdin, stdoutたちをリファクタしてテスタブルにしました。
https://github.com/ymdarake/iridium/pull/8
ジェネリクスもいい感じに書きやすいですね。
whereという言葉のチョイスにHaskellみを感じました。
(別にHaskellが書けるわけではないです。ざんねん。えらいひとにおそわりたい。)

余談2

せっかく設定していたGitHub Actionsによるカバレッジ集計が動かなくなってました。

適当にRustのアクション(って呼ぶのか?)で人気そうなものを拝借していたんですが、
ビルドキャッシュが良くなかったらしく、カバレッジのリポートファイル生成がこけてました。

なんでかは調べてないですが、まぁキャッシュなので、そういうこともあるんでしょう。
ありがたいことに無料なのでNoキャッシュ戦略で通してますが、
地球に優しい開発者になりたいのでそのうち直そうと思います。自分も電気も省エネだいじ。

ちなみにGitHub Actionsも初めて触ってます。
いい機会だと思って使ってみている状況。何事もチャレンジ!(?)

Part 08 : アセンブラ


自前VM用に自前アセンブラを書いていきます。

LOAD $1 #10

00 01 00 0A

に変換してあげる感じです。

さすがに自前パーサーまで書いていると切りがないので、素直にライブラリを使います。
これで

  • オペコード: LOAD
  • レジスター: $1
  • 数字: #10

assembler::Token に変換できるようになりました。

https://github.com/ymdarake/iridium/pull/11

自前でお勉強コードを書いていると、あらためてライブラリの偉大さを実感しますね。(ただし偉大なものに限る)

おまけ

Rustの入門も兼ねて写経で進めているんですが、今後あらためて調べる点がありそうです。

  1. 執筆当初からRustがバージョンアップしたことで、モジュールの宣言の仕方が変わっている。
    mod.rsはいまはdeprecatedとのこと。
    useする方法もバラバラなので統一したい。

  2. マクロシステム。
    Part 08で使った nomというパーサーコンビネーターのライブラリがマクロマクロしていた。
    マクロを多用するのは主にメタいことを達成したいときだろうし、それほど多用するタイミングもない気はしつつ、使えるようにはなっておきたい。
    エディタ上で通常のコードと同様に補完が効く書き方(rust-analyzerも頑張ってると思う)で、目に優しい系マクロシステムだなと思った。

ここまで、写経だけだとエラーになったりもしたけど、まずまず順調で内容も楽しめてます。

次回に続く。

Discussion