{-# LANGUAGE MultiParamTypeClasses #-} -- | A module implementing the Piet interpreter as a monad. The monad -- encapsulates the interpreter's status, i. e. the side-effects of -- Piet programs. module Language.Piet.PietMonad ( -- * The Piet interpreter monad PietMonad , InterpreterStatus , LogLevel(..) -- * Status access -- ** Direction Pointer, Codel Chooser and position , getDP, setDP , getCC, setCC , getPosition, setPosition -- ** Stack primitives , stackPush, stackPop, stackRoll -- * I/O , printNumber, printChar , readNumber, readChar , logMessage -- * Termination , terminate -- * Execution , runPietMonad ) where import Control.Concurrent import Control.Monad.Error () import Control.Monad.State import Data.RollStack import Language.Piet.Types -- | The status of a Piet interpreter. data InterpreterStatus = InterpreterStatus { dp :: DirectionPointer -- ^ Direction Pointer , cc :: CodelChooser -- ^ Codel Chooser , position :: (Int, Int) -- ^ Position , stack :: RollStack Int -- ^ Stack } -- | A request a Piet interpreter may send to it's environment. data PietRequest = Read PietType -- ^ The interpreter wants to read from STDIN. | Print PietType Int -- ^ The interpreter wants to print something to STDOUT. | Log LogLevel String -- ^ A log message has been issued. | Terminate -- ^ The Program has terminated. deriving (Show, Eq, Ord) -- | Describes the importance of a log message. data LogLevel = Verbosed -- ^ Rather verbosed output. | Info -- ^ Usual log level. | Error -- ^ A recoverable error has occured. | Fatal -- ^ A fatal error has occured. deriving (Eq, Ord) instance Show LogLevel where show Verbosed = "VERBOSED" show Info = "INFO " show Error = "ERROR " show Fatal = "FATAL " -- | A monad encapsulating the status of a Piet interpreter. newtype PietMonad a = PietMonad (InterpreterStatus -> Chan PietRequest -> Chan Int -> IO (Either String (a, InterpreterStatus))) instance Monad PietMonad where return x = PietMonad (\status _ _ -> return (return (x, status))) (PietMonad m01) >>= f = PietMonad (\status0 requestChan inputChan -> do either1 <- m01 status0 requestChan inputChan case either1 of Left msg -> return (fail msg) Right (x1, status1) -> do let (PietMonad m12) = f x1 m12 status1 requestChan inputChan ) fail msg = do logMessage Fatal msg PietMonad (\_ _ _ -> return (fail msg)) instance MonadState InterpreterStatus PietMonad where get = PietMonad $ \status _ _ -> return (return (status, status)) put status = PietMonad $ \_ _ _ -> return (return ((), status)) -- | Returns the current Direction Pointer. getDP :: PietMonad DirectionPointer getDP = gets dp -- | Sets the Direction Pointer. setDP :: DirectionPointer -> PietMonad () setDP newDP = modify (\status -> status { dp = newDP }) -- | Returns the current Codel Chooser. getCC :: PietMonad CodelChooser getCC = gets cc -- | Sets the current Codel Chooser. setCC :: CodelChooser -> PietMonad () setCC newCC = modify (\status -> status { cc = newCC }) -- | Returns the current position. getPosition :: PietMonad (Int, Int) getPosition = gets position -- | Sets the current position. setPosition :: Int -> Int -> PietMonad () setPosition x y = modify (\status -> status { position = (x, y) }) -- | Pushes a given 'Int' value on the stack. stackPush :: Int -> PietMonad () stackPush n = modify (\status -> status { stack = push n (stack status) }) -- | Pops the top value from the stack. If the stack was empty, -- 'Nothing' is returned, 'Just' the top value otherise. stackPop :: PietMonad (Maybe Int) stackPop = do response <- gets (pop . stack) case response of Nothing -> return Nothing Just (x, xs) -> do modify (\status -> status { stack = xs }) return (Just x) -- | Performs the 'roll' operation on the stack. stackRoll :: Int -- ^ Roll number -> Int -- ^ Depth -> PietMonad () stackRoll rolls depth = modify (\status -> status { stack = roll rolls depth (stack status) }) -- | Reads the given 'PietType' from STDIN. readType :: PietType -> PietMonad Int readType pType = PietMonad $ \status requestChan inputChan -> do writeChan requestChan (Read pType) response <- readChan inputChan return $ return (response, status) -- | Reads a number from STDIN. readNumber :: PietMonad Int readNumber = readType PietNumber -- | Reads a character from STDIN. Note that it is returned as an 'Int'. readChar :: PietMonad Int readChar = readType PietChar -- | Issue log message with given priority. logMessage :: LogLevel -> String -> PietMonad () logMessage level msg = PietMonad $ \status requestChan _ -> do writeChan requestChan (Log level msg) return $ return ((), status) -- | Prints a representation of a given 'PietType' to STDOUT. printType :: PietType -> Int -> PietMonad () printType pType n = PietMonad $ \status requestChan _ -> do writeChan requestChan (Print pType n) return $ return ((), status) -- | Prints a number to STDOUT. printNumber :: Int -> PietMonad () printNumber = printType PietNumber -- | Converts a given number to a character and prints it to STDOUT. printChar :: Int -> PietMonad () printChar = printType PietChar -- | Quit a program. Any command following this one will be ignored. terminate :: PietMonad () terminate = PietMonad $ \status requestChan _ -> do writeChan requestChan Terminate return $ return ((), status) -- | Executes a program represented by a 'PietMonad'. I/O operations -- (reading and writing numbers or characters) is delegated to -- callback functions. runPietMonad :: (PietType -> IO Int) -- ^ Callback to read from STDIN -> (PietType -> Int -> IO ()) -- ^ Print callback -> (LogLevel -> String -> IO ()) -- ^ Logging callback -> PietMonad a -- ^ The program to be executed -> IO (Either String a) -- ^ Result of the 'PietMonad' or an error message runPietMonad readCallback printCallback logCallback program = do requestChannel <- newChan responseChannel <- newChan lock <- newEmptyMVar -- Fork the actual monadic calculation. forkIO $ do let PietMonad piet = do x <- program -- This guarantees, that the service routine (see -- below) terminates iff the Piet program terminates, -- no matter how broken the program text is terminate return x x <- piet InterpreterStatus { dp = DPRight , cc = CCLeft , position = (0, 0) , stack = empty } requestChannel responseChannel putMVar lock x -- Run the IO part in the current thread. Thus the caller does -- not have to worry about thread-safe callbacks as all the IO -- stays in the same thread, which is the invoking thread. let serviceRoutine = do request <- readChan requestChannel case request of Read pType -> do n <- readCallback pType writeChan responseChannel n serviceRoutine Print pType n -> do printCallback pType n serviceRoutine Log level msg -> do logCallback level msg serviceRoutine Terminate -> return () serviceRoutine -- Block until the monadic calculation is completed and -- return its result. (liftM fst) `liftM` takeMVar lock