- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
さて、機械語コードをいくつか書いてきて、 ジャンプ先のアドレスを調べて修正するのに慣れてきた一方で、だんだん面倒にもなってきました。 プログラムを修正するたびに調べるのも面倒だし、書きなおすのも面倒。 こんなの人間のやる仕事じゃねえ!
で、なんか自動化できそうなのでやってみます。
そのためにラベルという命令? 命令というか目印みたいなのを導入します。
ラベルを使って、プログラムをこんな感じで書けないでしょうか?
[ "set_reg_a", 0, "set_reg_b", 0, "compare", "jump_eq", "then", # ラベルを指定してジャンプ "set_reg_c", 3, "jump", "endif", # ラベルを指定してジャンプ "label", "then", # ラベル "set_reg_c", 2, "label", "endif", # ラベル "set_reg_a", 4, "exit" ]
これを変換して、 "then"
とか "endif"
の部分を数字(アドレス)にすれば、
これまで手で書いてきた機械語コードと同じ位置付けのものになる
(VM に渡せるようになる)、という寸法です。
この「変換前のプログラム」のフォーマットも紆余曲折(JSON にしたり)があり、 vm2gol-v1 のときはこんな感じの YAML ファイルに落ち着きました。
- set_reg_a 0 - set_reg_b 0 - compare - jump_eq then - set_reg_c 3 - jump endif - label then - set_reg_c 2 - label endif - set_reg_a 4 - exit
ですが、ラベルがインデントされているとやはり見やすいということで、 ちょっと工夫して、たとえばこんなフォーマットを考えてみます。
- set_reg_a 0 - set_reg_b 0 - compare - jump_eq then - set_reg_c 3 - jump endif - label then - set_reg_c 2 - label endif - set_reg_a 4 - exit
これを YAML としてパースすると、先頭の余分な空白は無視されるので、 内容的には上のインデントなしと同じになります。
あ、でもこれだと、 このファイルをプログラムで自動生成するときに困りそうかな……。
というわけで、 vm2gol-v2 では YAML はやめてオレオレフォーマットにします。
set_reg_a 0 set_reg_b 0 # ここを書き換えて動作確認する compare jump_eq then set_reg_c 3 jump endif label then set_reg_c 2 label endif set_reg_a 4 exit
これを機械語コードに変換するプログラムを作りました。これです。
# vgasm.rb # coding: utf-8 require 'pp' require 'yaml' def parse(src) alines = [] src.each_line do |line| words = line.sub(/#.*/, "").strip.split(/ +/) unless words.empty? alines << words end end alines end def create_label_addr_map(alines) map = {} addr = 0 alines.each do |aline| head, *rest = aline case head when "label" name = rest[0] map[name] = addr addr += 2 else addr += 1 addr += rest.size end end map end src = File.read(ARGV[0]) alines = parse(src) # key: ラベル名、 value: アドレス のマッピングを作る label_addr_map = create_label_addr_map(alines) # pp label_addr_map words = [] alines.each do |aline| head, *rest = aline words << head case head when "label" words << rest[0] when "jump", "jump_eq" label_name = rest[0] words << label_addr_map[label_name] else words += rest.map{ |arg| (/^\-?\d+$/ =~ arg) ? arg.to_i : arg } end end puts YAML.dump(words)
2パス(2段階)の処理に分かれていて、
まず最初のパスでラベル名とアドレスのマッピングを作り、
2パス目で jump
と jump_eq
で指定されているラベルをアドレスに置き換えます。
あれっなんかアセンブラができてしまいましたね(わざとらしく)。 そしてこの「変換前のプログラム」はアセンブリのコードですね(わざとらしく)?
拡張子は .vga.txt
にします。アセンブリ(Assembly)の a です。
さっきの「変換前のコード」を 07_if.vga.txt
に保存して、
実行します!
$ cat 07_if.vga.txt set_reg_a 0 set_reg_b 0 # ここを書き換えて動作確認する compare jump_eq then set_reg_c 3 jump endif label then set_reg_c 2 label endif set_reg_a 4 exit $ ruby vgasm.rb 07_if.vga.txt > 07_if.vge.yaml $ cat 07_if.vge.yaml --- - set_reg_a - 0 - set_reg_b - 0 - compare - jump_eq - 11 - set_reg_c - 3 - jump - 15 - label - then - set_reg_c - 2 - label - endif - set_reg_a - 4 - exit
jump
, jump_eq
の引数がそれぞれ 11, 15 となっていて、
うまく変換できてますね。
これを VM に与えると…
$ ruby vgvm.rb 07_if.vge.yaml pc( 0) | reg_a(0) b(0) c(0) | zf(0) pc( 2) | reg_a(0) b(0) c(0) | zf(0) pc( 4) | reg_a(0) b(0) c(0) | zf(0) pc( 5) | reg_a(0) b(0) c(0) | zf(1) pc(11) | reg_a(0) b(0) c(0) | zf(1) vgvm.rb:63:in `block in start': Unknown operator (label) (RuntimeError) from vm.rb:28:in `loop' from vm.rb:28:in `start' from vm.rb:135:in `<main>'
おっと VM の修正を忘れていました。
label
の場合は何もする必要がなくて、単に次に進むだけとします。
--- a/vgvm.rb +++ b/vgvm.rb @@ -53,6 +53,8 @@ class Vm when "compare" compare() @pc += 1 + when "label" + @pc += 2 when "jump" addr = @mem[@pc + 1] @pc = addr
では再度実行。
$ ruby vgvm.rb 07_if.vge.yaml pc( 0) | reg_a(0) b(0) c(0) | zf(0) pc( 2) | reg_a(0) b(0) c(0) | zf(0) pc( 4) | reg_a(0) b(0) c(0) | zf(0) pc( 5) | reg_a(0) b(0) c(0) | zf(1) … compare を実行した pc(11) | reg_a(0) b(0) c(0) | zf(1) … ラベル "then" の位置にジャンプした pc(13) | reg_a(0) b(0) c(0) | zf(1) … label なので何もせず進んだ pc(15) | reg_a(0) b(0) c(2) | zf(1) … then句を実行した pc(17) | reg_a(0) b(0) c(2) | zf(1) … endif にジャンプした pc(19) | reg_a(4) b(0) c(2) | zf(1) exit
良さそうですね。
……いや、ちょっと待ってください。
正しい動きではあるんですが、
ラベルそのものがあるアドレスにジャンプしても
label
の時は何もせず進むだけなので、無駄な感じがしますね。
なので、ジャンプするときはラベルの次の位置にジャンプさせると良いのでは?
やってみましょうか。
@@ -41,7 +41,7 @@ alines.each{ |line| words << rest[0] when "jump", "jump_eq" label_name = rest[0] - words << label_addr_map[label_name] + words << label_addr_map[label_name] + 2 else words.concat( rest.map{ |arg|
$ ruby vgasm.rb 07_if.vga.txt > 07_if.vge.yaml $ cat 07_if.vge.yaml --- - set_reg_a - 0 - set_reg_b - 0 - compare - jump_eq - 13 - set_reg_c - 3 - jump - 17 - label - then - set_reg_c - 2 - label - endif - set_reg_a - 4 - exit $ ruby vgvm.rb 07_if.vge.yaml pc( 0) | reg_a(0) b(0) c(0) | zf(0) pc( 2) | reg_a(0) b(0) c(0) | zf(0) pc( 4) | reg_a(0) b(0) c(0) | zf(0) pc( 5) | reg_a(0) b(0) c(0) | zf(1) pc(13) | reg_a(0) b(0) c(0) | zf(1) … ラベルの次の位置にジャンプした pc(15) | reg_a(0) b(0) c(2) | zf(1) pc(17) | reg_a(0) b(0) c(2) | zf(1) pc(19) | reg_a(4) b(0) c(2) | zf(1) exit
特に問題なさそう。
(こうすると VM に追加した label
は用済みな気が……
まあ残したままにしておきましょうか)
なんかあっさりとアセンブラができてしまいました。
といっても命令も値も機械語と同じで、 仕事らしい仕事といえばラベルの変換くらいなので、 アセンブラと言ってしまってよいものかという感じはしますが。
※ ちなみに、この後もアセンブラ部分はほとんど変わりません。これでほぼ完成形。
あ、そうだ、今回のでアセンブルと VM での実行の 2段階に分かれて、 実行するのがちょっと面倒になってきたので、 いっぺんに実行させるための簡単な Bash スクリプトを用意しておきます。
#!/bin/bash set -o errexit file="$1" bname=$(basename $file .vga.txt) exefile=tmp/${bname}.vge.yaml ruby vgasm.rb $file > $exefile ruby vgvm.rb $exefile
アセンブラによって生成された .vge.yaml
ファイルは tmp/
というディレクトリに入るようにしました。
vm2gol-v1 のときは生成されたファイルも全部 git のリポジトリに入れていましたが、
今回は tmp/
ディレクトリごと無視します。
--- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp/
run.sh
という名前で保存して chmod で実行権限を付けて、
こいつにアセンブリのファイルを渡すと、
VM での実行までやってくれます。
$ chmod u+x run.sh $ ./run.sh 07_if.vga.txt pc( 0) | reg_a(0) b(0) c(0) | zf(0) pc( 2) | reg_a(0) b(0) c(0) | zf(0) pc( 4) | reg_a(0) b(0) c(0) | zf(0) (略)