仮想マシンなので命令はメモリから読もう。レジスタを２つ増やして、プログラムカウンタと計算結果を保存するレジスタを持つようにしてみる。命令は停止命令とACCレジスタをインクリメントする二つだけ用意しよう。

data Reg = PC | FP | ACC deriving (Eq, Ord, Show, Enum) data Insn = INCR | STOP deriving (Eq, Ord, Show, Enum)

命令を PC から読み出す fetch と、それを decode する関数は、メモリアドレスとメモリの持つ値が両方とも同じ Int にしておくと簡単になる。

fetch :: (Ord x, Show x, MonadState (VM Reg x x) m) => m x fetch = do pc <- loadReg PC load pc decode :: Int -> Insn decode = toEnum

実行するのも簡単だ。fetch - decode と命令による分岐を書けば良い。とりあえずは ACC レジスタの値を返しておくことにする。ハンドアセンブルして命令列を作るほうが面倒なくらい。

exec :: (MonadState (VM Reg Int Int) m) => m Int exec = do op <- fetch case decode op of STOP -> do { loadReg ACC } INCR -> do { acc <- loadReg ACC; storeReg ACC (acc + 1 ); pc <- loadReg PC; storeReg PC (pc + 1 ); exec }

適切なメモリとレジスタの状態を作って実行する。

* Main > evalStateT exec (makeVm [STOP]) 0 * Main > evalStateT exec (makeVm [INCR, STOP]) 1 * Main > evalStateT exec (makeVm [INCR, INCR, INCR, STOP]) 3

これではハンドアセンブルがあまりに辛い。TMR-Issue6 (https://wiki.haskell.org/wikiupload/1/14/TMR-Issue6.pdf) を斜め読みすると、DSL としてアセンブラを書いている（ように見える）。同じことを目指してみよう。

たぶん続く。