patat-0.15.2.0: lib/Patat/Eval.hs
--------------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Patat.Eval
( parseEvalBlocks
, evalVar
, evalActiveVars
, evalAllVars
) where
--------------------------------------------------------------------------------
import qualified Control.Concurrent.Async as Async
import Control.Exception (IOException, catch, finally)
import Control.Monad (foldM, when)
import Control.Monad.State (StateT, runStateT, state)
import Control.Monad.Writer (Writer, runWriter, tell)
import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI
import Data.Foldable (for_)
import qualified Data.HashMap.Strict as HMS
import qualified Data.IORef as IORef
import Data.List (foldl')
import Data.Maybe (maybeToList)
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Patat.Eval.Internal
import Patat.Presentation.Internal
import Patat.Presentation.Syntax
import Patat.Unique
import System.Exit (ExitCode (..))
import qualified System.IO as IO
import qualified System.Process as Process
--------------------------------------------------------------------------------
parseEvalBlocks :: Presentation -> Presentation
parseEvalBlocks presentation =
let ((pres, varGen), evalBlocks) = runWriter $
runStateT work (pUniqueGen presentation) in
pres {pEvalBlocks = evalBlocks, pUniqueGen = varGen}
where
work = case psEval (pSettings presentation) of
Nothing -> pure presentation
Just settings -> do
slides <- traverse (evalSlide settings) (pSlides presentation)
pure presentation {pSlides = slides}
--------------------------------------------------------------------------------
lookupSettings :: [CI T.Text] -> EvalSettingsMap -> [EvalSettings]
lookupSettings classes (EvalSettingsMap settings) = do
c <- classes
maybeToList $ HMS.lookup c settings
--------------------------------------------------------------------------------
-- | Monad used for identifying and extracting the evaluation blocks from a
-- presentation.
type ExtractEvalM a = StateT UniqueGen (Writer (HMS.HashMap Var EvalBlock)) a
--------------------------------------------------------------------------------
evalSlide :: EvalSettingsMap -> Slide -> ExtractEvalM Slide
evalSlide settings slide = case slideContent slide of
TitleSlide _ _ -> pure slide
ContentSlide blocks -> do
blocks1 <- dftBlocks (evalBlock settings) (pure . pure) blocks
pure slide {slideContent = ContentSlide blocks1}
--------------------------------------------------------------------------------
evalBlock
:: EvalSettingsMap -> Block
-> ExtractEvalM [Block]
evalBlock settings orig@(CodeBlock classes txt)
| [s@EvalSettings {..}] <- lookupSettings classes settings = do
var <- Var <$> state freshUnique
tell $ HMS.singleton var $ EvalBlock s classes txt Nothing
case (evalReveal, evalReplace) of
(False, True) -> pure [VarBlock var]
(False, False) -> pure [orig, VarBlock var]
(True, True) -> do
revealID <- RevealID <$> state freshUnique
pure $ pure $ Reveal ConcatWrapper $ RevealSequence
revealID
[revealID]
[ (S.singleton 0, [orig])
, (S.singleton 1, [VarBlock var])
]
(True, False) -> do
revealID <- RevealID <$> state freshUnique
pure $ pure $ Reveal ConcatWrapper $ RevealSequence
revealID
[revealID]
[ (S.fromList [0, 1], [orig])
, (S.fromList [1], [VarBlock var])
]
| _ : _ : _ <- lookupSettings classes settings =
let msg = "patat eval matched multiple settings for " <>
T.intercalate "," (map CI.original classes) in
pure [CodeBlock classes msg]
evalBlock _ block =
pure [block]
--------------------------------------------------------------------------------
newAccum :: Monoid m => (m -> IO ()) -> IO (m -> IO ())
newAccum f = do
ref <- IORef.newIORef mempty
pure $ \x ->
IORef.atomicModifyIORef' ref (\y -> let z = y <> x in (z, z)) >>= f
--------------------------------------------------------------------------------
evalVar :: Var -> ([Block] -> IO ()) -> Presentation -> IO Presentation
evalVar var writeOutput presentation = case HMS.lookup var evalBlocks of
Nothing -> pure presentation
Just EvalBlock {..} | Just _ <- ebAsync -> pure presentation
Just eb@EvalBlock {..} -> do
let EvalSettings {..} = ebSettings
writeChunk <- newAccum (writeOutput . renderEvalBlock eb)
let drainLines copy h = do
c <- catch (T.hGetChunk h) ((\_ -> pure "") :: IOException -> IO T.Text)
when (c /= "") $ do
when copy $ writeChunk c
drainLines copy h
let proc = (Process.shell $ T.unpack evalCommand)
{ Process.std_in = Process.CreatePipe
, Process.std_out = Process.CreatePipe
, Process.std_err = Process.CreatePipe
}
(Just hIn, Just hOut, Just hErr, hProc) <- Process.createProcess proc
async <- Async.async $
Async.withAsync (T.hPutStr hIn ebInput `finally` IO.hClose hIn) $ \_ ->
Async.withAsync (drainLines True hOut) $ \outAsync ->
Async.withAsync (drainLines evalStderr hErr) $ \errAsync ->
Async.withAsync (Process.waitForProcess hProc) $ \exitCodeAsync -> do
erExitCode <- Async.wait exitCodeAsync
_ <- Async.wait outAsync
_ <- Async.wait errAsync
case erExitCode of
ExitSuccess -> pure ()
ExitFailure i -> writeChunk $
evalCommand <> ": exit code " <> T.pack (show i) <> "\n"
pure presentation
{ pEvalBlocks = HMS.insert var eb {ebAsync = Just async} evalBlocks
}
where
evalBlocks = pEvalBlocks presentation
--------------------------------------------------------------------------------
evalActiveVars
:: (Var -> [Block] -> IO ()) -> Presentation -> IO Presentation
evalActiveVars update presentation = foldM
(\p var -> evalVar var (update var) p)
presentation
(activeVars presentation)
--------------------------------------------------------------------------------
evalAllVars :: Presentation -> IO Presentation
evalAllVars pres = do
updates <- IORef.newIORef []
let forceEvalVar pres0 var = do
pres1 <- evalVar
var
(\u -> IORef.atomicModifyIORef' updates (\l -> (l ++ [u], ())))
pres0
case HMS.lookup var (pEvalBlocks pres1) of
Nothing -> pure pres1
Just eb -> do
for_ (ebAsync eb) Async.wait
IORef.atomicModifyIORef' updates $ \l ->
([], foldl' (\p u -> updateVar var u p) pres1 l)
foldM forceEvalVar pres (HMS.keys (pEvalBlocks pres))