hfd-0.0.2: src/App.hs
-- | This module defines App monad
module App
(
App,
runApp,
AppState(..),
FileEntry(..),
StackFrame,
Breakpoint(..),
setStack,
addFileEntry,
getFileEntry,
setLastCmd,
setBreakpoints,
removeBreakpoint,
newBreakId
)
where
import Data.ByteString (ByteString)
import Data.Iteratee (Iteratee, run)
import Data.Iteratee.IO (enumHandle)
import System.IO (Handle)
import System.Environment (getEnv)
import System.Console.Haskeline (InputT, runInputT, Settings(..), CompletionFunc, simpleCompletion)
import Control.Monad (liftM)
import Control.Monad.Trans.State (StateT, evalStateT, get, put)
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class(MonadIO)
import UCmd (UCmd(..), suggestBaseCmd)
import Inst () -- 'MonadCatchIO' instance for 'InputT'
-- | App monad
type App m = Iteratee ByteString (InputT (StateT AppState m))
-- | Run App monad
runApp :: Handle -- ^ Input/Output stream to be used
-- to communicate with player
-> App IO a -- ^ Application
-> IO a
runApp h app = do
home <- getEnv "HOME"
let history = home ++ "/.hfd_history"
flip evalStateT (defaultState h) $
runInputT (hlSettings history) (enumHandle 1 h app >>= run)
-- | Make haskeline settings
hlSettings :: MonadIO m => String -> Settings (StateT AppState m)
hlSettings history = Settings {historyFile = Just history, complete = completeFunc, autoAddHistory = True}
-- | Complete base commands
completeFunc :: MonadIO m => CompletionFunc (StateT AppState m)
completeFunc (s, _) = do
let s' = reverse s
let sgs = suggestBaseCmd s'
return (s, map (simpleCompletion . drop (length s')) sgs)
-- | Application state
data AppState = AppState {
asFiles :: [(Int, FileEntry)], -- ^ map of file entries
asStack :: [StackFrame], -- ^ current stack if any
asBreaks :: [(Int, Breakpoint)], -- ^ list of breakpoints
asLastBreakId :: Int, -- ^ last used breakpoint id
asLastCmd :: Maybe UCmd, -- ^ last command entered by user
asHandle :: Handle -- ^ handle to player
} deriving Show
-- | Represents single breakpoint
data Breakpoint = Breakpoint {
bpFileId :: Int,
bpLine :: Int
} deriving Show
-- | Set breakpoints
setBreakpoints :: Monad m => [(Int, Breakpoint)] -> App m ()
setBreakpoints bs = do
state <- lift . lift $ get
lift . lift $ put state {asBreaks = bs}
-- | Remove breakpoint by id
removeBreakpoint :: Monad m => Int -> App m ()
removeBreakpoint iD = do
state <- lift . lift $ get
let bs = filter ((/= iD) . fst) $ asBreaks state
setBreakpoints bs
-- | Allocate id for new breakpoint
newBreakId :: Monad m => App m Int
newBreakId = do
state <- lift . lift $ get
let newId = asLastBreakId state + 1
lift . lift $ put state {asLastBreakId = newId}
return newId
-- | Default application state contains nothing
defaultState :: Handle -> AppState
defaultState = AppState [] [] [] 0 Nothing
-- | File entry represents one source file
data FileEntry = FileEntry {
fePath :: String, -- ^ Path to file as recieved from player,
-- e.g. @\/home\/user\/proj;com\/example;Main.as@
feContent :: [String] -- ^ File content
} deriving Show
-- | Represents stack frame
-- file id, line and function name
type StackFrame = (Int, Int, String)
-- | Returns `FileEntry` by id
getFileEntry :: Monad m => Int -> App m (Maybe FileEntry)
getFileEntry iD = do
fs <- lift . lift $ liftM asFiles get
return $ lookup iD fs
-- | Set current stack
setStack :: Monad m => [StackFrame] -> App m ()
setStack st = do
state <- lift $ lift get
lift . lift $ put state {asStack = st}
-- | Add new file entry to app state
--
-- XXX: check that ids are unique
addFileEntry :: Monad m => (Int, FileEntry) -> App m ()
addFileEntry fe = do
state <- lift $ lift get
let fes = asFiles state
lift . lift $ put state {asFiles = fe : fes}
-- | Set last cmd entered by user
setLastCmd :: Monad m => Maybe UCmd -> App m ()
setLastCmd (Just UCmdEmpty) = return ()
setLastCmd cmd = lift . lift $ do
state <- get
put $ state {asLastCmd = cmd}