{-# LANGUAGE TypeFamilies #-} module Hsm.Repl ( Repl , repl , runRepl ) where import Control.Monad (forM_) import Data.Typeable (Proxy (Proxy), Typeable, typeRep) import Effectful (Dispatch (Static), DispatchOf, Eff, IOE, liftIO, (:>)) import Effectful.Dispatch.Static (SideEffects (WithSideEffects), StaticRep, evalStaticRep, getStaticRep, unsafeEff_) import Effectful.Exception (bracket) import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) import Generic.Data.Function.Common.Generic.Meta (KnownSymbols, symbolVals) import Hsm.Log (Log, Severity (Attention, Info, Trace), logMsg) import Language.Haskell.Interpreter ( GhcError (errMsg) , InterpreterError (WontCompile) , as , interpret , runInterpreter , setImports ) import String.ANSI (blue) import System.Console.Haskeline (defaultSettings, getInputLine, handleInterrupt, withInterrupt) import System.Console.Haskeline.IO (InputState, cancelInput, initializeInput, queryInput) data Repl (p :: Symbol) (ms :: [Symbol]) (t :: *) (a :: * -> *) (b :: *) type instance DispatchOf (Repl p ms t) = Static WithSideEffects newtype instance StaticRep (Repl p ms t) = Repl InputState repl :: forall p ms t es . (KnownSymbol p, KnownSymbols ms, Log "repl" :> es, Repl p ms t :> es, Show t, Typeable t) => Eff es (Maybe t) repl = query >>= maybe (return Nothing) parse where query = do Repl inputState <- getStaticRep logMsg Trace $ "Expecting a value of type: " <> show (typeRep $ Proxy @t) unsafeEff_ . queryInput inputState . handleInterrupt (return Nothing) . withInterrupt . getInputLine . blue $ symbolVal (Proxy @p) parse string = do logMsg Trace $ "Parsing string: " <> string eitherValue <- unsafeEff_ . runInterpreter $ do setImports $ symbolVals @ms interpret string as case eitherValue of Right value -> do logMsg Trace $ "Parsed value: " <> show value return $ Just value Left (WontCompile errors) -> do forM_ errors $ logMsg Attention . errMsg repl Left err -> do logMsg Attention $ show err repl runRepl :: forall p ms t es a . (IOE :> es, Log "repl" :> es) => Eff (Repl p ms t : es) a -> Eff es a runRepl action = bracket inputStateAlloc inputStateDealloc $ \inputState -> evalStaticRep (Repl inputState) action where inputStateAlloc = do logMsg Info "Initializing input" liftIO $ initializeInput defaultSettings inputStateDealloc inputState = do logMsg Info "Cancelling input" liftIO $ cancelInput inputState