{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {- | Support functions to call common live coding functionalities like launching and reloading from a @ghci@ or @cabal repl@ session. You typically don't need to import this module in your code, but you should load it in your interactive session, ideally by copying the file `essence-of-live-coding/.ghci` to your project, adjusting it to your needs and launching @cabal repl@. -} module LiveCoding.GHCi where -- base import Control.Concurrent import Control.Exception (Exception (displayException, toException), SomeException, try) import Control.Monad (join, void, (>=>)) import Data.Data import Data.Function ((&)) -- transformers import Control.Monad.Trans.State.Strict -- foreign-store import Foreign.Store -- essence-of-live-coding import LiveCoding.LiveProgram import LiveCoding.RuntimeIO.Launch proxyFromLiveProgram :: LiveProgram m -> Proxy m proxyFromLiveProgram _ = Proxy -- | An exception type marking the absence of a foreign store of the correct type. data NoStore = NoStore deriving (Show) instance Exception NoStore -- * Retrieving launched programs from the foreign store {- | Try to retrieve a 'LiveProgram' of a given type from the 'Store', handling all 'IO' exceptions. Returns 'Right Nothing' if the store didn't exist. -} possiblyLaunchedProgram :: Launchable m => Proxy m -> IO (Either SomeException (LaunchedProgram m)) possiblyLaunchedProgram _ = do storeMaybe <- lookupStore 0 fmap join $ try $ traverse readStore $ maybe (Left $ toException NoStore) Right storeMaybe {- | Try to load a 'LiveProgram' of a given type from the 'Store'. If the store doesn't contain a program, it is (re)started. -} sync :: Launchable m => LiveProgram m -> IO () sync program = do launchedProgramPossibly <- possiblyLaunchedProgram $ proxyFromLiveProgram program case launchedProgramPossibly of -- Looking up the store failed in some way, restart Left (e :: SomeException) -> do putStrLn $ displayException e launchAndSave program -- A program is running, update it Right launchedProgram -> do putStrLn "update" update launchedProgram program -- | Launch a 'LiveProgram' and save it in the 'Store'. launchAndSave :: Launchable m => LiveProgram m -> IO () launchAndSave = launch >=> save -- | Save a 'LiveProgram' to the store. save :: Launchable m => LaunchedProgram m -> IO () save = writeStore $ Store 0 {- | Try to retrieve a 'LaunchedProgram' from the 'Store', and if successful, stop it. -} stopStored :: Launchable m => Proxy m -> IO () stopStored proxy = do launchedProgramPossibly <- possiblyLaunchedProgram proxy either (putStrLn . displayException) stop launchedProgramPossibly -- * GHCi commands -- ** Debugging -- TODO Could also parametrise this and all other commands by the 'liveProgram' {- | Initialise a launched program in the store, but don't start it. -} liveinit _ = return $ unlines [ "programVar <- newMVar liveProgram" , "threadId <- myThreadId" , "save LaunchedProgram { .. }" ] -- | Run one program step, assuming you have a launched program in a variable @launchedProgram@. livestep _ = return "stepLaunchedProgram launchedProgram" -- ** Running -- | Launch or restart a program and save its reference in the store. livelaunch _ = return "sync liveProgram" -- | Reload the code and do hot code swap and migration. livereload _ = return $ unlines [ ":reload" , "sync liveProgram" ] -- | Stop the program. livestop _ = return "stopStored $ proxyFromLiveProgram liveProgram"