Whitespace

Haskellhttp://compsoc.dur.ac.uk/whitespace/ 0.3のインタープリタを作った。

本家サイトにはあまり仕様が書いてないので、http://compsoc.dur.ac.uk/whitespace/examples.phphttp://compsoc.dur.ac.uk/whitespace/contrib.phpに置いてあるプログラムが動くように作った。

2ヵ所あるdebugというコメントの箇所を有効にすると、それぞれソースコード中の命令の並びと、実行される命令の並び(およびスタックの状態)を標準出力に表示する。

ソースはこちら(ツッコミ希望)。

import Data.Array
import qualified Data.Map as M
import Char (chr, ord)
import System
import IO

-- data type definitions
type Prog    = Array Addr Command
data Command = Push Number | Dup | Copy Number | Swap | Pop | Slide Number |
               Add | Sub | Mul | Div | Mod |
               Store | Load |
               Mark Addr | Call Addr | Jump Addr | Jeqz Addr | Jltz Addr |  -- with address
               Markl {label::Label} | Calll Label | Jumpl Label | Jeqzl Label | Jltzl Label |  -- with labels
               Return | Term |
               Putc | Puti | Getc | Geti
               deriving (Show)
type Number  = Bits
type Label   = Bits

data State   = State { stack::[Bits],
                       heap::M.Map Bits Bits,
                       ip::Addr,                    -- instruction pointer
                       calls::[Addr] }              -- subroutine call stack
               | End
               deriving (Show)

type Bits = [Bit]
data Bit = T | S deriving (Eq, Ord, Show)
type Addr = Int -- Instruction Address

-- main routine
main::IO()
main = do   [path] <- getArgs
            source <- readFile path
            execute source

execute :: String -> IO ()
execute src = do  let p = compile src
                      s = State [] M.empty 1 []
                  --mapM_ (\(l,c) -> putStrLn $ show l ++ " " ++ show c) $ assocs p -- debug
                  run p s
                  return ()

compile :: String -> Prog
compile cs = let cs' = filter (flip elem [' ', '\t', '\n']) cs
                 cmds = unlabels $ parse cs'
                 in listArray (1, length cmds) cmds
                 where parse :: String -> [Command]
                       parse []                  = []
                       parse (' ':cs)            = parseStack cs
                       parse ('\n':cs)           = parseFlow cs
                       parse ('\t':' ':cs)       = parseArith cs
                       parse ('\t':'\t':cs)      = parseHeap cs
                       parse ('\t':'\n':cs)      = parseIo cs
                       parse cs                  = parseError "Invalid IMP" cs
                       parseStack (' ':cs)       = let (cs', n) = parseBits cs in (Push n) : parse cs'
                       parseStack ('\t':' ':cs)  = let (cs', n) = parseBits cs in (Copy n) : parse cs'
                       parseStack ('\t':'\n':cs) = let (cs', n) = parseBits cs in (Slide n) : parse cs'
                       parseStack ('\n':' ':cs)  = Dup : parse cs
                       parseStack ('\n':'\t':cs) = Swap : parse cs
                       parseStack ('\n':'\n':cs) = Pop : parse cs
                       parseStack cs             = parseError "Invalid stack command" cs
                       parseFlow (' ':' ':cs)    = let (cs', l) = parseBits cs in (Markl l) : parse cs'
                       parseFlow (' ':'\t':cs)   = let (cs', l) = parseBits cs in (Calll l) : parse cs'
                       parseFlow (' ':'\n':cs)   = let (cs', l) = parseBits cs in (Jumpl l) : parse cs'
                       parseFlow ('\t':' ':cs)   = let (cs', l) = parseBits cs in (Jeqzl l) : parse cs'
                       parseFlow ('\t':'\t':cs)  = let (cs', l) = parseBits cs in (Jltzl l) : parse cs'
                       parseFlow ('\t':'\n':cs)  = Return : parse cs
                       parseFlow ('\n':'\n':cs)  = Term : parse cs
                       parseFlow cs              = parseError "Invalid flow control command" cs
                       parseArith (' ':' ':cs)   = Add : parse cs
                       parseArith (' ':'\t':cs)  = Sub : parse cs
                       parseArith (' ':'\n':cs)  = Mul : parse cs
                       parseArith ('\t':' ':cs)  = Div : parse cs
                       parseArith ('\t':'\t':cs) = Mod : parse cs
                       parseArith cs             = parseError "Invalid arithmetic command" cs
                       parseHeap (' ':cs)        = Store : parse cs
                       parseHeap ('\t':cs)       = Load : parse cs
                       parseHeap cs              = parseError "Invalid heap access command" cs
                       parseIo (' ':' ':cs)      = Putc : parse cs
                       parseIo (' ':'\t':cs)     = Puti : parse cs
                       parseIo ('\t':' ':cs)     = Getc : parse cs
                       parseIo ('\t':'\t':cs)    = Geti : parse cs
                       parseIo cs                = parseError "Invalid IO command" cs
                       parseError msg cs         = fail $ msg ++ " : " ++ (show $ parseBits $ take 10 cs) ++ ".."
                       parseBits :: String -> (String, Bits)
                       parseBits cs              = parseBit cs []
                       parseBit ('\n':cs) bs     = (cs, reverse bs)
                       parseBit (' ':cs) bs      = parseBit cs (S:bs)
                       parseBit ('\t':cs) bs     = parseBit cs (T:bs)
                       unlabels :: [Command] -> [Command]
                       unlabels cmds             = let marks = filter (\(n, c) -> isMarkl c) $ zip [1..] cmds
                                                       lamap = M.fromList $ map (\(n, c) -> (label c, n)) marks
                                                       in map (\c -> unlabel c lamap) cmds
                       isMarkl (Markl _)         = True
                       isMarkl _                 = False
                       unlabel (Markl l) m       = Mark (m M.! l)
                       unlabel (Calll l) m       = Call (m M.! l)
                       unlabel (Jumpl l) m       = Jump (m M.! l)
                       unlabel (Jeqzl l) m       = Jeqz (m M.! l)
                       unlabel (Jltzl l) m       = Jltz (m M.! l)
                       unlabel c         _       = c

run :: Prog -> State -> IO State
run p s = do  let c = fetch p s
              --putStrLn $ concat [(show $ ip s), " ", (show c), " : ", (show $ stack s)] -- debug
              s' <- dispatch c s
              case s' of
                   End -> return s'
                   _   -> run p s'

fetch :: Prog -> State -> Command
fetch p s = p ! (ip s)

dispatch :: Command -> State -> IO State
dispatch (Push b) s@(State st _ _ _) = returni $ s {stack = b:st}
dispatch Dup s@(State st@(x:_) _ _ _) = returni $ s {stack = x:st}
dispatch (Copy b) s@(State st _ _ _) = let n = toInt b - 1
                                           in returni $ s {stack = (st !! n) : st}
dispatch Swap s@(State (x:y:zs) _ _ _) = returni $ s {stack = y:x:zs}
dispatch Pop s@(State (_:xs) _ _ _) = returni $ s {stack = xs}
dispatch (Slide b) s@(State (x:xs) _ _ _) = let n = toInt b
                                                in returni $ s {stack = x : drop n xs}
dispatch Add s = returni $ arithmetic (+) s
dispatch Sub s = returni $ arithmetic (-) s
dispatch Mul s = returni $ arithmetic (*) s
dispatch Div s = returni $ arithmetic div s
dispatch Mod s = returni $ arithmetic mod s
dispatch Store s@(State (v:a:xs) h _ _) = returni $ s {stack = xs, heap = M.insert a v h}
dispatch Load s@(State (a:xs) h _ _) = case M.lookup a h of
                                            Just x -> returni $ s {stack = x:xs}
                                            _      -> fail $ "Load error : " ++ show a
dispatch (Mark _) s = returni s
dispatch (Call a) s@(State _ _ i cs) = return $ s {ip = a, calls = (i + 1):cs}
dispatch (Jump a) s = return $ s {ip = a}
dispatch (Jeqz a) s@(State (x:xs) _ _ _) = if isZero x
                                              then return $ s {stack = xs, ip = a}
                                              else returni $ s {stack = xs}
dispatch (Jltz a) s@(State (x:xs) _ _ _) = if isMinus x
                                              then return $ s {stack = xs, ip = a}
                                              else returni $ s {stack = xs}
dispatch Return s@(State _ _ _ (a:as))   = return $ s {ip = a, calls = as}
dispatch Term _     = return End
dispatch Putc s@(State (b:xs) _ _ _) = do putChar $ toChar b
                                          returni $ s {stack = xs}
dispatch Puti s@(State (b:xs) _ _ _) = do putStr $ show $ toInt b
                                          returni $ s {stack = xs}
dispatch Getc s@(State (a:xs) h _ _) = do c <- getChar
                                          let v = fromChar c
                                          returni $ s {stack = xs, heap = M.insert a v h}
dispatch Geti s@(State (a:xs) h _ _) = do i <- getInt
                                          let v = fromInt i
                                          returni $ s {stack = xs, heap = M.insert a v h}
dispatch c _                     = fail $ "Invalid command : " ++ show c

arithmetic :: (Int -> Int -> Int) -> State -> State
arithmetic f s@(State (x:y:zs) _ _ _) = let z = fromInt $ f (toInt y) (toInt x)
                                            in s {stack = z:zs}

returni :: State -> IO State
returni s@(State _ _ i _) = return (s {ip = i + 1})

getInt :: IO Int
getInt = do cs <- getLine
            return $ (read::String -> Int) cs

-- Bits data manipulation
toInt :: Bits -> Int
toInt (b:bs) | b == T     = negate $ toUint bs
             | otherwise  = toUint bs
             where toUint = foldl (\n b -> n * 2 + if b == S then 0 else 1) 0
toInt []                  = 0

toChar :: Bits -> Char
toChar = chr . toInt

fromInt :: Int -> Bits
fromInt i | i >= 0    = S:(reverse $ fromUint i)
          | otherwise = T:(reverse $ fromUint $ negate i)
          where fromUint 0 = []
                fromUint i = (if even i then S else T):(fromUint $ i `div` 2)

fromChar :: Char -> Bits
fromChar = fromInt . ord

isZero :: Bits -> Bool
isZero (_:bs) = all (== S) bs
isZero []     = True

isMinus :: Bits -> Bool
isMinus (T:bs) = any (== T) bs
isMinus _      = False