{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RankNTypes #-} {-# 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 (SomeException, try) import Control.Monad (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 -- * 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 (Maybe (LaunchedProgram m))) possiblyLaunchedProgram _ = do storeMaybe <- lookupStore 0 try $ traverse readStore 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) -> putStrLn "exc" >> launchAndSave program -- The store was empty, restart Right Nothing -> putStrLn "empty" >> launchAndSave program -- A program is running, update it Right (Just launchedProgram) -> 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 = void $ (fmap $ fmap $ fmap stop) $ possiblyLaunchedProgram proxy -- * 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"