{-# OPTIONS -fglasgow-exts #-} module EmacsMonad ( EmacsM -- , runInSHM , Messages , SomeMessage(..) , runEmacs , sendMsg, cd , load, fuzzyCompleteId, simpleCompleteId , findDefinition, lookupType , Test(..), runTest, acceptTest ) where import Control.Monad.Trans import Data.Monoid import Directory import System.FilePath import Text.Printf import Shim.Sexp import Shim.Messages (simpleCompleteIdentifierMsg, fuzzyCompleteIdentifierMsg ,simpleCompleteModuleMsg, fuzzyCompleteModuleMsg ,loadFileMsg, findDefinitionMsg) import qualified Shim.Messages as Messages import Shim.MessagesTH import Shim.Shim import Shim.SHM import Shim.Hsinfo hiding ( load, runTest, findDefinition ) ghcProg = "ghc" buildMsg :: ConvSexp args => Message args resp -> args -> Sexp buildMsg (Message name _) args = toS(S name) `mappend` toS args type Output = [String] type ErrMsg = String type Messages = [SomeMessage] data SomeMessage where Msg :: EmacsM a -> SomeMessage data EmacsM a where SendMsg :: (Show args, ConvSexp args, ConvSexp resp) => Message args resp -> args -> EmacsM resp Bind :: EmacsM a -> (a -> EmacsM b) -> EmacsM b Return :: a -> EmacsM a Fail :: ErrMsg -> EmacsM a SetWD :: FilePath -> EmacsM () Canonicalize :: FilePath -> EmacsM FilePath LiftIO :: IO a -> EmacsM a instance Show (EmacsM a) where show (Fail err) = "Fail with " ++ err show (Return _) = [] show (Bind m m2) = ";" -- Should never show up in practice show (SendMsg msg args) = "Send " ++ showMsg msg args showMsg (Message name _) args = name ++ show args instance Show SomeMessage where show (Msg m) = show m ----------------- -- Monadic API ----------------- sendMsg :: (Show args, ConvSexp args, ConvSexp resp) => Message args resp -> args -> EmacsM resp sendMsg = SendMsg cd :: FilePath -> EmacsM () cd = SetWD canonicalize = Canonicalize --------------------- -- The actual monad --------------------- -- What we have here is a kind of convertor, runInSHM, -- that translates EmacsM computations to 'plain' SHM -- computations. Not really so plain, they are actually -- running in an extended SHM monad with explicit failure, -- output logging (writer-like) -- and message counting&logging (state-like) type RunningInSHM a = FilePath -> Messages -> SHM (Maybe a, Output, Messages) instance Monad EmacsM where (>>=) = Bind return = Return fail = Fail instance MonadIO EmacsM where liftIO = LiftIO runEmacs :: EmacsM a -> IO (Either (Output, Messages, ErrMsg) Output) runEmacs m = do -- setLogAction (\_ _ -> return ()) pwd <- getCurrentDirectory sess <- ghcInit ghcProg res <- runSHM sess ghcProg (runInSHM m pwd []) setCurrentDirectory pwd case res of (Just x, out, hist) -> return $ Right out (Nothing, (err:out), hist) -> return $ Left (out, hist, err) runInSHM :: forall a. EmacsM a -> RunningInSHM a runInSHM cmd@(Fail err) _ hist = return (Nothing, [err], hist) runInSHM (Return x) _ hist = return (Just x, [], hist) runInSHM (Bind m f) root hist = do it@(mb_x, out1, hist') <- runInSHM m root hist case mb_x of Nothing -> return (Nothing, out1, hist') Just x -> do (x', out2, hist'') <- let cmd = f x in runInSHM cmd root hist' return (x', out1 ++ out2, hist'') runInSHM(SetWD path) _ hist = liftSHM hist $ liftIO $setCurrentDirectory path runInSHM cmd@(SendMsg msg args) root hist = do let msgsexp = buildMsg msg args out <- handleCall msgsexp -- (S ":emacs-rex", length [msg|Msg(SendMsg{}) <- hist], msgsexp) case out of Error err -> runInSHM (Fail err) root (Msg cmd : hist) Response se -> let sexp = toS se Just resp = fromS sexp in return (Just resp, [show sexp], Msg cmd : hist) runInSHM (LiftIO io) _ hist = liftSHM hist (liftIO io) runInSHM (Canonicalize fp) rootPath hist = return (Just (rootPath fp), [], hist) liftSHM hist m = m >>= \x -> return (Just x, [], hist) ------------------- -- Messages ------------------- load path = canonicalize path >>= \p -> sendMsg loadFileMsg (p, Nothing) fuzzyCompleteId file id = canonicalize file >>= \f -> sendMsg fuzzyCompleteIdentifierMsg (f,id) simpleCompleteId file id = canonicalize file >>= \f -> sendMsg simpleCompleteIdentifierMsg (f,id) lookupType file l c = canonicalize file >>= \f -> sendMsg Messages.lookupTypeMsg (f, l, c, Nothing) findDefinition file l c = canonicalize file >>= \f -> sendMsg Messages.findDefinitionMsg (f, l, c, Nothing) ----------------------- -- Testing combinators ----------------------- type Name = String data Test = forall a. Test {testName::Name, testAction::EmacsM a} acceptTest (Test name m) = acceptOutput (name ++ ".out") m runTest :: Test -> IO Bool runTest (Test name m) = do expected <- readFile (name ++ ".out") mb_out <- runEmacs m case mb_out of Right out -> writeFile (name ++ ".run.out") (concat out) >> return (concat out == expected) Left (out,msgs,err) -> do writeFile (name ++ ".run.out") (concat (err : out)) putStrLn $ "Failed with error: " ++ err putStrLn $ "BackTrace of messages: \n" ++ unlines (map show msgs) -- Could also interleave outputs and the backtrace of messages, -- in order to see the answer to each message return False acceptOutput :: FilePath -> EmacsM a -> IO Bool acceptOutput path m = do mb_out <- runEmacs m case mb_out of Left _ -> return False Right out -> writeFile path (concat out) >> return True