packages feed

tttool-1.3: src/GMERun.hs

{-# LANGUAGE FlexibleContexts #-}
module GMERun (playTipToi) where

import Text.Printf
import Data.Bits
import Data.List
import qualified Data.Map as M
import Control.Monad.State

import Types
import PrettyPrint
import Utils

type PlayState r = M.Map r Word16

type GMEM r = StateT (PlayState r) IO

formatState :: PlayState ResReg -> String
formatState s = spaces $
    map (\(k,v) -> printf "$%d=%d" k v) $
    filter (\(k,v) -> k == 0 || v /= 0) $
    M.toAscList s

playTipToi :: Transscript -> TipToiFile -> IO ()
playTipToi t tt = do
    let initialState = M.fromList $ zip [0..] (ttInitialRegs tt)
    printf "Initial state (not showing zero registers): %s\n" (formatState initialState)
    flip evalStateT initialState $ forEachNumber $ untilNothing $ \i -> do
        case lookup (fromIntegral i) (ttScripts tt) of
            Nothing -> do
                lift $ printf "OID %d not in main table\n" i
                return Nothing
            Just Nothing -> do
                lift $ printf "OID %d deactivated\n" i
                return Nothing
            Just (Just lines) -> do
                code <- gets $ \s -> find (enabledLine s) lines
                case code of
                    Nothing -> lift $ do
                        printf "None of these lines matched!\n"
                        mapM_ (putStrLn . ppLine t) lines
                        return Nothing
                    Just l -> do
                        lift $ printf "Executing:  %s\n" (ppLine t l)
                        next <- applyLine l
                        get >>= lift . printf "State now: %s\n" . formatState
                        return next

enabledLine :: Ord r => PlayState r -> Line r -> Bool
enabledLine s (Line _ cond _ _) = all (condTrue s) cond

condTrue :: Ord r => PlayState r -> Conditional r -> Bool
condTrue s (Cond v1 o v2) = value s v1 =?= value s v2
  where
    (=?=) = case o of
        Eq  -> (==)
        NEq -> (/=)
        Lt  -> (<)
        GEq -> (>=)
        _   -> \_ _ -> False

value :: Ord r => PlayState r -> TVal r -> Word16
value m (Reg r) = M.findWithDefault 0 r m
value m (Const n) = n

getVal :: (Ord r, MonadState (PlayState r) m) => TVal r -> m Word16
getVal v = gets $ \s ->  value s v

modReg :: (Ord r, MonadState (PlayState r) m) => r -> (Word16 -> Word16) -> m ()
modReg r f = do
    x <- getVal (Reg r)
    modify $ M.insert r (f x)

applyLine :: (Ord r, MonadState (PlayState r) m) => Line r -> m (Maybe Word16)
applyLine (Line _ _ acts _) = go acts
  where
    go (Neg r : acts) = do
        modReg r neg
        go acts
    go (ArithOp o r v : acts) = do
        arg <- getVal v
        let (*) = applyOp o
        modReg r (* arg)
        go acts
    go (Jump v: _ ) = do
        n <- getVal v
        return (Just n)
    go (_: acts) = do
        go acts
    go [] = return Nothing

    neg 0 = 1
    neg _ = 0

    applyOp Inc = (+)
    applyOp Dec = (-)
    applyOp Mult = (*)
    applyOp Div = div
    applyOp Mod = mod
    applyOp And = (.&.)
    applyOp Or  = (.|.)
    applyOp XOr = xor
    applyOp Set = \_ v -> v


untilNothing :: Monad m => (a -> m (Maybe a)) -> a -> m ()
untilNothing f i = do
    r <- f i
    case r of Just i' -> untilNothing f i'
              Nothing -> return ()

forEachNumber :: (Word16 -> StateT s IO ()) -> StateT s IO ()
forEachNumber action = forever $ do
    lift $ putStrLn "Next OID touched? "
    str <- lift $ getLine
    case readMaybe str of
        Just i ->  action i
        Nothing -> lift $ putStrLn "Not a number, please try again"