- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
ゴール設定が「ライフゲームが動けばOK」 だったので、前回で終わり、としてもよかったんですが、 せっかくなのでもうちょっとだけいじります。 すでにライフゲームが動いており目的は達成されているので今回のはおまけです。
※ 第24回 で触れた入れ子の式の問題については別記事で書く予定
ダンプ表示の改良(コメントに色を付ける)
これ v1 のときはやってたんですが、 v2 では単に忘れてた……だった気がします。 忘れていたことを忘れていた? 時間が経つといろんなことを忘れますね。
--- a/vgvm.rb +++ b/vgvm.rb @@ -7,6 +7,7 @@ require './common' module TermColor RESET = "\e[m" RED = "\e[0;31m" + BLUE = "\e[0;34m" end class Memory @@ -55,6 +56,8 @@ class Memory case operator when "exit", "call", "ret", "jump", "jump_eq" TermColor::RED + when "_cmt" + TermColor::BLUE else "" end
こんな見た目になります。
3行だけの修正で見やすくなるので (31) 生存カウント (2) / _cmt の時に入れておくべきでしたね……。
ダンプ表示を間引きして高速化
今の状態だと、グライダーが1周する (右下に移動して左上にワープして、元の位置に戻る) のに4分くらいかかります。
こういう、ターミナルにドバドバ出力するプログラムは ターミナルでの画面表示がボトルネックになる場合が多いので、 単純にダンプ表示を間引きしてやれば速くなる気がします。
動作確認やデバッグのときは1ステップずつ進める必要がありますが、 ライフゲームが動いているのを眺めて愛でる段階では 律儀に毎ステップ表示せずに適当にサボらせればよいでしょう。 とりあえず 「10ステップごとに1回だけダンプ表示を行う」 ようにしてみます。
--- a/vgvm.rb +++ b/vgvm.rb @@ -318,7 +318,7 @@ class Vm raise "Unknown operator (#{op})" end - dump_v2() + dump_v2() if @step % 10 == 0 # $stdin.gets if @step >= 600 # sleep 0.01 end
やはりこれだけでかなり速くなりますね。 20秒くらいで元の位置に戻るようになりました。
適当に10ステップ毎としましたが、 1周するのに 40万ステップ弱(!)かかるようになっているので、 表示を眺める分にはもっと間引いても問題ありません。
VM: インラインで書いていた命令ごとの処理をメソッドに抽出
Vm#start()
が長い。
行数を数えてみると 175行ありました。
case式の中に直接書いている処理をメソッドに抽出しましょう。例として push
。
--- a/vgvm.rb +++ b/vgvm.rb @@ -218,29 +218,7 @@ class Vm @pc = ret_addr # 戻る set_sp(@sp + 1) # スタックポインタを戻す when "push" - arg = @mem.main[@pc + 1] - val_to_push = - case arg - when Integer - arg - when String - case arg - when "bp" - @bp - when /^\[bp\-(\d+)\]$/ - stack_addr = @bp - $1.to_i - @mem.stack[stack_addr] - when /^\[bp\+(\d+)\]$/ - stack_addr = @bp + $1.to_i - @mem.stack[stack_addr] - else - raise not_yet_impl("push", arg) - end - else - raise not_yet_impl("push", arg) - end - set_sp(@sp - 1) - @mem.stack[@sp] = val_to_push + push() @pc += pc_delta when "pop" arg = @mem.main[@pc + 1] @@ -464,6 +442,34 @@ class Vm @pc += 2 end end + + def push + arg = @mem.main[@pc + 1] + + val_to_push = + case arg + when Integer + arg + when String + case arg + when "bp" + @bp + when /^\[bp\-(\d+)\]$/ + stack_addr = @bp - $1.to_i + @mem.stack[stack_addr] + when /^\[bp\+(\d+)\]$/ + stack_addr = @bp + $1.to_i + @mem.stack[stack_addr] + else + raise not_yet_impl("push", arg) + end + else + raise not_yet_impl("push", arg) + end + + set_sp(@sp - 1) + @mem.stack[@sp] = val_to_push + end end exe_file = ARGV[0]
同様に、 pop
, set_vram
, get_vram
もメソッドに抽出しました(diff は省略)。
ライフゲームが動くようになっていますので、 「ライフゲームの動きが(目で見て)おかしくなっていないこと」 をテスト代わりにして、壊れていないことを確認します。 適当だなー。
適当ですが、テストがまだない状態で無理してリファクタリングするよりは、 ちょっと我慢してリファクタリングを後回しにして、 雑でもテストで保護された状態になってから修正する方が安心感がありますね。
codegen_stmts() と codegen_func_def() の共通化
微妙に気にはなっていたところですが。
うーん、これはちょっと考えたんですが、 無理して共通化しなくてもいいかなと。やってもいいけど。 v1 のときはやってたんですけどね。
気が向いたらやるかも。
(2020-08-22 追記) 第46回で修正しました。
使わなくなったメソッドの削除
そういえばそんなのあったな的な。 一番最初に使ったやつみたいですね(忘れてる)。 消しても大丈夫。
--- a/vgvm.rb +++ b/vgvm.rb @@ -310,22 +310,6 @@ class Vm EOB end - def set_mem(addr, n) - @mem.main[addr] = n - end - - def copy_mem_to_reg_a(addr) - @reg_a = @mem.main[addr] - end - - def copy_mem_to_reg_b(addr) - @reg_b = @mem.main[addr] - end - - def copy_reg_c_to_mem(addr) - @mem.main[addr] = @reg_c - end - def add_ab @reg_a = @reg_a + @reg_b end
その他メモ
- なんとなく残したままにしてますが、
reg_c
は 第5〜7回で使ったあと使わなくなってしまったので、関連箇所を削除してもライフゲームは動きます。
他にもリファクタリングしたり改良したいところはいろいろあるのですが、 だらだらやっているとキリがないのでいったんここでやめておきます。 (気になるところが出てきたらまた追記するかもしれません)
2019-12-14 追記
gol.vgt.json
: テスト用のコードとコメントアウトしていた部分を一部削除しました