■
data Reg = Zero | At | V0 | V1 | A0 | A1 | A2 | A3 | T0 | T1 | T2 | T3 | T4 | T5 | T6 | T7 | S0 | S1 | S2 | S3 | S4 | S5 | S6 | S7 | T8 | T9 | GP | SP | FP | RQ | PC deriving (Eq, Ord, Show, Enum, Bounded) data Insn = Add Reg Reg Reg | Sub Reg Reg Reg | J Word deriving (Eq, Ord, Show) data PseudoInsn = Pseudo [Insn] data Cell = W Word | I Insn deriving (Eq, Ord, Show) data VM = VM (Map.Map Reg Cell) (Map.Map Word Cell) deriving (Eq, Show) readReg r = gets $ \ (VM rs cs) -> fromMaybe err (Map.lookup r rs) where err = error $ "Invalid Register: " ++ show r readMem a = gets $ \ (VM rs cs) -> fromMaybe err (Map.lookup a cs) where err = error $ "Invalid Address: " ++ show a writeReg r x = modify $ \ (VM rs cs) -> VM (Map.insert r x rs) cs writeMem a x = modify $ \ (VM rs cs) -> VM rs (Map.insert a x cs) incr r = do W x <- readReg r writeReg r (W (x + 1)) fetch :: MonadState VM m => m Cell fetch = do W pc <- readReg PC readMem pc decode :: Cell -> Insn decode (I x) = x decode _ = error "Invalid Instruction" exec :: MonadState VM m => m () exec = do op <- fetch case decode op of Add rs rt rd -> do W s <- readReg rs W t <- readReg rt writeReg rd (W (s + t)) incr PC exec
Haskell で仮想マシンを書く fetch
仮想マシンなので命令はメモリから読もう。レジスタを2つ増やして、プログラムカウンタと計算結果を保存するレジスタを持つようにしてみる。命令は停止命令とACCレジスタをインクリメントする二つだけ用意しよう。
data Reg = PC | FP | ACC deriving (Eq, Ord, Show, Enum) data Insn = INCR | STOP deriving (Eq, Ord, Show, Enum)
命令を PC から読み出す fetch と、それを decode する関数は、メモリアドレスとメモリの持つ値が両方とも同じ Int にしておくと簡単になる。
-- word = ptr 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 としてアセンブラを書いている(ように見える)。同じことを目指してみよう。
たぶん続く。
Haskell で仮想マシンを書く
モナドの練習として仮想マシンを書く。レジスタとメモリを、キーをレジスタまたはアドレスとし値を保持できる Map で表すことにしよう。
type Regs r x = Map.Map r x type Memory p x = Map.Map p x data VM r p x = VM (Regs r x) (Memory p x) deriving (Eq, Show)
この仮想マシンを状態モナドにして、メモリとレジスタを更新/参照する関数 store, load, storeReg, loadReg を用意し、runStateT や evalStateT で実行することを考える。
利用しているメモリアドレスの上限をレジスタで管理することにすると、単純化したメモリアロケーションを行う関数 alloc が書ける。
alloc r = do fp <- loadReg r modifyReg (Map.insert r (succ fp)) return $ succ fp
具体的にレジスタ FP をもった仮想マシン vm0 を考えることにする。
data Reg = FP deriving (Eq, Ord, Show, Enum) vm0 :: VM Reg Int Int vm0 = VM (Map.fromList $ zip (enumFrom FP) [0 .. ]) Map.empty
メモリをアロケートしてそこに値を書き込む処理 assign は以下のように書ける。
assign x = do p <- alloc FP store x p return p
これを使うと、以下のような手続き型言語のような処理が書ける。ここで var はアドレス値から値を取得する関数で、全体を計算の連鎖として書くために必要だった。
また plus は liftM2 (+) で定義されるモナドで、 m r -> m r -> m r の型を持つ。
test00 = do x <- assign 10 y <- assign 3 (plus (var x) (var y)) Main> run0 test00 (13,VM (fromList [(FP,2)]) (fromList [(1,10),(2,3)]))
続く。
以下、全コード。
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-} import qualified Data.Map as Map import Control.Monad.State import Data.Maybe type Regs r x = Map.Map r x type Memory p x = Map.Map p x data VM r p x = VM (Regs r x) (Memory p x) deriving (Eq, Show) -- Register の更新 modifyReg :: MonadState (VM r p x) m => (Regs r x -> Regs r x) -> m () modifyReg f = modify $ \(VM rs ms) -> VM (f rs) ms -- Memory の更新 modifyMem :: MonadState (VM r p x) m => (Memory p x -> Memory p x) -> m () modifyMem f = modify $ \(VM rs ms) -> VM rs (f ms) -- load from Register loadReg :: (Ord r, MonadState (VM r t x) m) => r -> m x loadReg r = do VM rs _ <- get return $ fromMaybe (error "un-initialized reg") (Map.lookup r rs) -- store to Register storeReg :: (Ord r, MonadState (VM r p x) m) => r -> x -> m () storeReg r x = modifyReg (Map.insert r x) -- load from memory load :: (Ord p, MonadState (VM r p x) m) => p -> m x load p = do VM _ ms <- get return $ fromMaybe (error "un-initialized memory") (Map.lookup p ms) -- store to memory store :: (Ord p, MonadState (VM r p x) m) => x -> p -> m () store x p = modifyMem (Map.insert p x) -- memory allocate use register r as free pointer. alloc :: (Enum x, Ord r, MonadState (VM r p x) m) => r -> m x alloc r = do fp <- loadReg r modifyReg (Map.insert r (succ fp)) return $ succ fp -- test machine with one register data Reg = FP deriving (Eq, Ord, Show, Enum) vm0 :: VM Reg Int Int vm0 = VM (Map.fromList $ zip (enumFrom FP) [0 .. ]) Map.empty -- 新規の領域に x を保存 assign x = do p <- alloc FP store x p return p assign' p = do x <- p assign x -- 変数 p を参照 var :: (Ord p, MonadState (VM r p x) m) => p -> m x var = load plus :: (Monad m, Num r) => m r -> m r -> m r plus = liftM2 (+) minus :: (Monad m, Num r) => m r -> m r -> m r minus = liftM2 (-) run0 f = runStateT f vm0 eval0 f = evalStateT f vm0 test00 :: Monad m => StateT (VM Reg Int Int) m Int test00 = do x <- assign 10 y <- assign 3 (plus (var x) (var y)) test01 :: Monad m => StateT (VM Reg Int Int) m Int test01 = do x <- assign 10 -- x = 10 y <- assign 3 -- y = 3 z <- assign' (plus (var x) (var x)) -- z = x + x (minus (var z) (var y)) -- return z - y
Template Haskell (TH) でデータ定義
列挙型とその文字表現を、データで定義できる TH を書いた。
-- モジュールA defDataConstsType "T_Digit" (map (\x -> "T_" ++ [intToDigit x]) [0..10]) defShowType "T_Digit" (map (\x -> ("T_" ++ [intToDigit x], "'" ++ [intToDigit x] ++ "'")) [0..10])
このように書くと、下と同じ意味になる(はず)。
data T_Digit = T_0 | T_1 ... instance Show T_Digit where show T_0 = "'0'" show T_1 = "'1'" ...
TH のコード本体は利用する側と別モジュールに書かなくてはいけない(制限)。
-- モジュールB defDataConstsType :: String -> [String] -> Q [Dec] defDataConstsType name ns = return d where d = [DataD [] (mkName name) [] (map (\n -> NormalC (mkName n) []) ns) [mkName "Eq", mkName "Enum"]] defShowType :: String -> [(String, String)] -> Q [Dec] defShowType name ht = return d where d = [InstanceD [] (AppT (ConT (mkName "Show")) (ConT (mkName name))) [FunD (mkName "show") [Clause [VarP x] (NormalB (CaseE (VarE x) -- (map (\(n,s) -> Match (ConP (mkName n) []) (NormalB (LitE (StringL s))) []) ht))) []]]] where x = mkName "x"
PEG
PEG モドキを Haskell で。データ構造を定義した。Show のインスタンスにする部分は省略。
data PExp nt = Eps -- epsilon | AtomT String -- Terminal symbol | AtomNT nt -- Non-Terminal symbol | PExp nt :. PExp nt -- e1 e2 | PExp nt :/ PExp nt -- e1 / e2 | Opt (PExp nt) -- e? | ZeroOrMore (PExp nt) -- e* | OneOrMore (PExp nt) -- e+ | And (PExp nt) -- &e | Not (PExp nt) -- !e deriving Eq data Rule nt = nt :<- (PExp nt) deriving Eq data Grammer nt = Grammer [Rule nt] deriving Show
そして以下のようにサンプルのグラマーを定義してやる。
pp (Grammer rs) = mapM_ (putStrLn . show) rs data NT01 = Underscore | Digit | LowerCase | UpperCase | Identifier deriving (Eq, Show) g01 :: Grammer NT01 g01 = Grammer [ underScore, digit, lowerCase, upperCase, identifier ] where underScore = Underscore :<- AtomT "_" digit = Digit :<- e where f n = [intToDigit n] e = foldl1 (:/) $ map (AtomT . f) [0..9] lowerCase = LowerCase :<- foldl1 (:/) [AtomT [x] | x <- ['a'..'z']] upperCase = UpperCase :<- foldl1 (:/) [AtomT [x] | x <- ['A'..'Z']] identifier = Identifier :<- ((AtomNT LowerCase :/ AtomNT UpperCase :/ AtomNT Underscore) :. (Opt (AtomNT LowerCase :/ AtomNT UpperCase :/ AtomNT Underscore :/ AtomNT Digit)))
そうすると以下のように出力される。
Main> pp g01 Underscore <- _ Digit <- 0 / 1 / 2 / 3 / 4 / 5 / 6 / 7 / 8 / 9 LowerCase <- a / b / c / d / e / f / g / h / i / j / k / l / m / n / o / p / q / r / s / t / u / v / w / x / y / z UpperCase <- A / B / C / D / E / F / G / H / I / J / K / L / M / N / O / P / Q / R / S / T / U / V / W / X / Y / Z Identifier <- ( LowerCase / UpperCase / Underscore ) ( ( LowerCase / UpperCase / Underscore / Digit )? ) Main>
Java コード生成へ向けて
lisp/scheme でS式からコード生成を行いたい。最終的なイメージは以下のような Java の S 式表現から Java コードを生成することだ。
gosh> (print (jise->java '((System.out.println "Hello, Java!") (declare x 0) (assign x 3) (when (= x 3) (System.out.println "oops"))))) System.out.println("Hello, Java!"); int x = 0; x = 3; if (x == 3) { System.out.println("oops"); }
Java の構文要素を調べれて、S 式表現を検討すれば、あとは util.match あたりを使って単なる静的な式変形をすればよい。
sxml の簡易シリアライズ
SXML (http://ja.wikipedia.org/wiki/SXML) の名前空間部分を無視して文字列化する、簡単なシリアライザーを gauche で書いた。
以下使用例。生成された文字列と、その前段階のリストを同時に返している。
gosh> (simple-sxml->xml '(tag)) "<tag/>" ("<" "tag" () "/>") gosh> (simple-sxml->xml '(html (@ (lang "ja")) (body))) "<html lang=\"ja\"><body/></html>" ("<" #0="html" ((" " "lang" "=\"" "ja" "\"")) ">" (("<body/>")) "</" #0# ">") gosh> (simple-sxml->xml '(tag (@ (attr1 "v1") (attr2 "v2")) (nested "Text Node") (empty))) "<tag attr1=\"v1\" attr2=\"v2\"><nested>Text Node</nested><empty/></tag>" ("<" #0="tag" ((#1=" " "attr1" #2="=\"" "v1" #3="\"") (#1# "attr2" #2# "v2" #3#)) ">" (("<nested>Text Node</nested>" "<empty/>")) "</" #0# ">")
パターンマッチには util.match を使う。string-append を使って文字列を逐次生成するのは冗長に感じられたので、一旦文字列のリストを生成した後、 text.tree で文字列にすることにした。
(use util.match) (use text.tree) (define (simple-sxml->xml x) (define (rep1 x attrs) `("<" ,(symbol->string x) ,attrs "/>")) (define (rep x attrs . body) (let1 s (symbol->string x) `("<" ,s ,attrs ">" ,body "</" ,s ">"))) (define (iter tree) (match x ((tag) (rep1 tag '())) ((tag ('@ (attr value) ...)) (rep1 tag (map (lambda (a v) (list " " (symbol->string a) "=\"" v "\"")) attr value))) ((tag ('@ (attr value) ...) rest ...) (rep tag (map (lambda (a v) (list " " (symbol->string a) "=\"" v "\"")) attr value) (map simple-sxml->xml rest))) ((tag rest ...) (rep tag '() (map simple-sxml->xml rest))) (() "") ((? string? s) s))) (let1 r (iter x) (values (tree->string r) r)))