{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeSynonymInstances #-} module Heist.Compiled.Internal where ------------------------------------------------------------------------------ import Blaze.ByteString.Builder import Blaze.ByteString.Builder.Char.Utf8 import Control.Arrow import Control.Exception import Control.Monad import Control.Monad.RWS.Strict import Control.Monad.State.Strict import qualified Data.Attoparsec.Text as AP import Data.ByteString (ByteString) import Data.DList (DList) import qualified Data.DList as DL import qualified Data.HashMap.Strict as H import qualified Data.HashSet as S import qualified Data.HeterogeneousEnvironment as HE import Data.Map.Syntax import Data.Maybe import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Vector as V import Text.Printf import qualified Text.XmlHtml as X import qualified Text.XmlHtml.HTML.Meta as X ------------------------------------------------------------------------------ #if !MIN_VERSION_base(4,8,0) import Data.Foldable (Foldable) #endif import qualified Data.Foldable as Foldable ------------------------------------------------------------------------------ import Heist.Common import Heist.Internal.Types.HeistState ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ -- | A compiled Splice is a HeistT computation that returns a @DList -- (Chunk m)@. -- -- The more interesting part of the type signature is what comes before the -- return value. The first type parameter in @'HeistT' n IO@ is the runtime -- monad. This reveals that the Chunks know about the runtime monad. The -- second type parameter in @HeistT n IO@ is @IO@. This tells us that the -- compiled splices themselves are run in the IO monad, which will usually -- mean at load time. Compiled splices run at load time, and they return -- computations that run at runtime. type Splice n = HeistT n IO (DList (Chunk n)) ------------------------------------------------------------------------------ -- | Runs the parameter node's children and returns the resulting compiled -- chunks. By itself this function is a simple passthrough splice that makes -- the spliced node disappear. In combination with locally bound splices, -- this function makes it easier to pass the desired view into your splices. runChildren :: Monad n => Splice n runChildren = runNodeList . X.childNodes =<< getParamNode {-# INLINE runChildren #-} renderFragment :: Markup -> [X.Node] -> Builder renderFragment markup ns = case markup of Html -> X.renderHtmlFragment X.UTF8 ns Xml -> X.renderXmlFragment X.UTF8 ns ------------------------------------------------------------------------------ -- | Yields pure text known at load time. pureTextChunk :: Text -> Chunk n pureTextChunk t = Pure $ T.encodeUtf8 t {-# INLINE pureTextChunk #-} ------------------------------------------------------------------------------ -- | Yields a pure Builder known at load time. You should use this and -- 'yieldPureText' as much as possible to maximize the parts of your page that -- can be compiled to static ByteStrings. yieldPure :: Builder -> DList (Chunk n) yieldPure = DL.singleton . Pure . toByteString {-# INLINE yieldPure #-} ------------------------------------------------------------------------------ -- | Yields a runtime action that returns a builder. yieldRuntime :: RuntimeSplice n Builder -> DList (Chunk n) yieldRuntime = DL.singleton . RuntimeHtml {-# INLINE yieldRuntime #-} ------------------------------------------------------------------------------ -- | Yields a runtime action that returns no value and is only needed for its -- side effect. yieldRuntimeEffect :: Monad n => RuntimeSplice n () -> DList (Chunk n) yieldRuntimeEffect = DL.singleton . RuntimeAction {-# INLINE yieldRuntimeEffect #-} ------------------------------------------------------------------------------ -- | A convenience wrapper around yieldPure for working with Text. Roughly -- equivalent to 'textSplice' from Heist.Interpreted. yieldPureText :: Text -> DList (Chunk n) yieldPureText = DL.singleton . pureTextChunk {-# INLINE yieldPureText #-} ------------------------------------------------------------------------------ -- | Convenience wrapper around yieldRuntime allowing you to work with Text. yieldRuntimeText :: Monad n => RuntimeSplice n Text -> DList (Chunk n) yieldRuntimeText = yieldRuntime . liftM fromText {-# INLINE yieldRuntimeText #-} ------------------------------------------------------------------------------ -- | Returns a computation that performs load-time splice processing on the -- supplied list of nodes. runNodeList :: Monad n => [X.Node] -> Splice n runNodeList = mapSplices runNode ------------------------------------------------------------------------------ -- | Runs a DocumentFile with the appropriate template context set. runDocumentFile :: Monad n => TPath -> DocumentFile -> Splice n runDocumentFile tpath df = do let markup = case dfDoc df of X.XmlDocument _ _ _ -> Xml X.HtmlDocument _ _ _ -> Html modifyHS (\hs -> hs { _curMarkup = markup }) let inDoctype = X.docType $ dfDoc df addDoctype $ maybeToList inDoctype modifyHS (setCurTemplateFile curPath . setCurContext tpath) res <- runNodeList nodes dt <- getsHS (listToMaybe . _doctypes) let enc = X.docEncoding $ dfDoc df return $! (yieldPure (X.renderDocType enc dt) `mappend` res) where curPath = dfFile df nodes = X.docContent $! dfDoc df ------------------------------------------------------------------------------ compileTemplate :: Monad n => TPath -> DocumentFile -> HeistT n IO [Chunk n] compileTemplate tpath df = do !chunks <- runDocumentFile tpath df return $! consolidate chunks ------------------------------------------------------------------------------ compileTemplates :: Monad n => (TPath -> Bool) -> HeistState n -> IO (Either [String] (HeistState n)) compileTemplates f hs = do (tmap, hs') <- runHeistT (compileTemplates' f) (X.TextNode "") hs let pre = _splicePrefix hs' let canError = _errorNotBound hs' let errs = _spliceErrors hs' let nsErr = if not (T.null pre) && (_numNamespacedTags hs' == 0) then Left [noNamespaceSplicesMsg $ T.unpack pre] else Right () return $ if canError then case errs of [] -> nsErr >> (Right $! hs { _compiledTemplateMap = tmap }) es -> Left $ either (++) (const id) nsErr $ map (T.unpack . spliceErrorText) es else nsErr >> (Right $! hs { _compiledTemplateMap = tmap , _spliceErrors = errs }) ------------------------------------------------------------------------------ noNamespaceSplicesMsg :: String -> String noNamespaceSplicesMsg pre = unwords [ printf "You are using a namespace of '%s', but you don't have any" ns , printf "tags starting with '%s'. If you have not defined any" pre , "splices, then change your namespace to the empty string to get rid" , "of this message." ] where ns = reverse $ drop 1 $ reverse pre ------------------------------------------------------------------------------ compileTemplates' :: Monad n => (TPath -> Bool) -> HeistT n IO (H.HashMap TPath ([Chunk n], MIMEType)) compileTemplates' f = do hs <- getHS let tpathDocfiles :: [(TPath, DocumentFile)] tpathDocfiles = filter (f . fst) (H.toList $ _templateMap hs) foldM runOne H.empty tpathDocfiles where runOne tmap (tpath, df) = do modifyHS (\hs -> hs { _doctypes = []}) !mHtml <- compileTemplate tpath df return $! H.insert tpath (mHtml, mimeType $! dfDoc df) tmap ------------------------------------------------------------------------------ -- | Consolidate consecutive Pure Chunks. consolidate :: (Monad n) => DList (Chunk n) -> [Chunk n] consolidate = consolidateL . DL.toList where consolidateL [] = [] consolidateL (y:ys) = boilDown [] $! go [] y ys where ---------------------------------------------------------------------- go soFar x [] = x : soFar go soFar (Pure a) ((Pure b) : xs) = go soFar (Pure $! a `mappend` b) xs go soFar (RuntimeHtml a) ((RuntimeHtml b) : xs) = go soFar (RuntimeHtml $! a `mappend` b) xs go soFar (RuntimeHtml a) ((RuntimeAction b) : xs) = go soFar (RuntimeHtml $! a >>= \x -> b >> return x) xs go soFar (RuntimeAction a) ((RuntimeHtml b) : xs) = go soFar (RuntimeHtml $! a >> b) xs go soFar (RuntimeAction a) ((RuntimeAction b) : xs) = go soFar (RuntimeAction $! a >> b) xs go soFar a (b : xs) = go (a : soFar) b xs ---------------------------------------------------------------------- boilDown soFar [] = soFar boilDown soFar ((Pure h) : xs) = boilDown ((Pure $! h) : soFar) xs boilDown soFar (x : xs) = boilDown (x : soFar) xs ------------------------------------------------------------------------------ -- | Given a list of output chunks, codeGen turns consecutive runs of -- @Pure Html@ values into maximally-efficient pre-rendered strict -- 'ByteString' chunks. codeGen :: Monad n => DList (Chunk n) -> RuntimeSplice n Builder codeGen l = V.foldr mappend mempty $! V.map toAct $! V.fromList $! consolidate l where toAct !(RuntimeHtml !m) = m toAct !(Pure !h) = return $! fromByteString h toAct !(RuntimeAction !m) = m >> return mempty {-# INLINE codeGen #-} ------------------------------------------------------------------------------ -- | Looks up a splice in the compiled splice map. lookupSplice :: Text -> HeistT n IO (Maybe (Splice n)) lookupSplice nm = do pre <- getsHS _splicePrefix res <- getsHS (H.lookup nm . _compiledSpliceMap) if isNothing res && T.isPrefixOf pre nm && not (T.null pre) then do tellSpliceError $ "No splice bound for " `mappend` nm return Nothing else return res ------------------------------------------------------------------------------ -- | Runs a single node. If there is no splice referenced anywhere in the -- subtree, then it is rendered as a pure chunk, otherwise it calls -- compileNode to generate the appropriate runtime computation. runNode :: Monad n => X.Node -> Splice n runNode node = localParamNode (const node) $ do hs <- getHS let pre = _splicePrefix hs let hasPrefix = (T.isPrefixOf pre `fmap` X.tagName node) == Just True when (not (T.null pre) && hasPrefix) incNamespacedTags hs' <- getHS -- Plain rethrows for CompileException to avoid multiple annotations. (res, hs'') <- liftIO $ catches (compileIO hs') [ Handler (\(ex :: CompileException) -> throwIO ex) , Handler (\(ex :: SomeException) -> handleError ex hs')] putHS hs'' return res where localSplicePath = localHS (\hs -> hs {_splicePath = (_curContext hs, _curTemplateFile hs, X.elementTag node): (_splicePath hs)}) compileIO hs = runHeistT compile node hs compile = do isStatic <- subtreeIsStatic node dl <- compile' isStatic liftIO $ evaluate $ DL.fromList $! consolidate dl compile' True = do markup <- getsHS _curMarkup return $! yieldPure $! renderFragment markup [parseAttrs node] compile' False = localSplicePath $ compileNode node handleError ex hs = do errs <- evalHeistT (do localSplicePath $ tellSpliceError $ T.pack $ "Exception in splice compile: " ++ show ex getsHS _spliceErrors) node hs throwIO $ CompileException ex errs parseAttrs :: X.Node -> X.Node parseAttrs (X.Element nm attrs ch) = newAttrs `seq` X.Element nm newAttrs ch where newAttrs = map parseAttr attrs parseAttrs !n = n parseAttr :: (Text, Text) -> (Text, Text) parseAttr (k,v) = (k, T.concat $! map cvt ast) where !ast = case AP.feed (AP.parse attParser v) "" of (AP.Done _ res) -> res (AP.Fail _ _ _) -> [] (AP.Partial _ ) -> [] cvt (Literal x) = x cvt (Ident i) = T.concat ["${", i, "}"] ------------------------------------------------------------------------------ -- | Checks whether a node's subtree is static and can be rendered up front at -- load time. subtreeIsStatic :: X.Node -> HeistT n IO Bool subtreeIsStatic (X.Element nm attrs ch) = do isNodeDynamic <- liftM isJust $ lookupSplice nm attrSplices <- getsHS _attrSpliceMap let hasSubstitutions (k,v) = hasAttributeSubstitutions v || H.member k attrSplices if isNodeDynamic then return False else do let hasDynamicAttrs = any hasSubstitutions attrs if hasDynamicAttrs then return False else do staticSubtrees <- mapM subtreeIsStatic ch return $ and staticSubtrees subtreeIsStatic _ = return True ------------------------------------------------------------------------------ -- | Checks whether a string has any attribute substitutions. hasAttributeSubstitutions :: Text -> Bool hasAttributeSubstitutions txt = any isIdent ast where ast = case AP.feed (AP.parse attParser txt) "" of (AP.Done _ res) -> res (AP.Fail _ _ _) -> [] (AP.Partial _ ) -> [] ------------------------------------------------------------------------------ -- | Given a 'X.Node' in the DOM tree, produces a \"runtime splice\" that will -- generate html at runtime. compileNode :: Monad n => X.Node -> Splice n compileNode (X.Element nm attrs ch) = do msplice <- lookupSplice nm fromMaybe compileStaticElement msplice where tag0 = T.append "<" nm end = T.concat [ ""] -- If the tag is not a splice, but it contains dynamic children compileStaticElement = do -- Parse the attributes: we have Left for static and Right for runtime compiledAttrs <- runAttributes attrs childHtml <- runNodeList ch return $! if null (DL.toList childHtml) && nm `S.member` X.voidTags then DL.concat [ DL.singleton $! pureTextChunk $! tag0 , DL.concat compiledAttrs , DL.singleton $! pureTextChunk " />" ] else DL.concat [ DL.singleton $! pureTextChunk $! tag0 , DL.concat compiledAttrs , DL.singleton $! pureTextChunk ">" , childHtml , DL.singleton $! pureTextChunk $! end ] compileNode _ = error "impossible" ------------------------------------------------------------------------------ -- | parseAtt :: Monad n => (Text, Text) -> HeistT n IO (DList (Chunk n)) parseAtt (k,v) = do mas <- getsHS (H.lookup k . _attrSpliceMap) maybe doInline (return . doAttrSplice) mas where cvt (Literal x) = return $ yieldPureText x cvt (Ident x) = localParamNode (const $ X.Element x [] []) $ getAttributeSplice x -- Handles inline parsing of $() splice syntax in attributes doInline = do let ast = case AP.feed (AP.parse attParser v) "" of (AP.Done _ res) -> res (AP.Fail _ _ _) -> [] (AP.Partial _ ) -> [] chunks <- mapM cvt ast let value = DL.concat chunks return $ attrToChunk k value -- Handles attribute splices doAttrSplice splice = DL.singleton $ RuntimeHtml $ do res <- splice v return $ mconcat $ map attrToBuilder res ------------------------------------------------------------------------------ -- | parseAtt2 :: Monad n => (Text, Text) -> HeistT n IO (RuntimeSplice n [(Text, Text)]) parseAtt2 (k,v) = do mas <- getsHS (H.lookup k . _attrSpliceMap) maybe doInline (return . doAttrSplice) mas where cvt (Literal x) = return $ return x cvt (Ident x) = localParamNode (const $ X.Element x [] []) $ getAttributeSplice2 x -- Handles inline parsing of $() splice syntax in attributes doInline = do let ast = case AP.feed (AP.parse attParser v) "" of (AP.Done _ res) -> res (AP.Fail _ _ _) -> [] (AP.Partial _ ) -> [] chunks <- mapM cvt ast return $ do list <- sequence chunks return [(k, T.concat list)] -- Handles attribute splices doAttrSplice splice = splice v ------------------------------------------------------------------------------ -- | Performs splice processing on a list of attributes. This is useful in -- situations where you need to stop recursion, but still run splice -- processing on the node's attributes. runAttributes :: Monad n => [(Text, Text)] -- ^ List of attributes -> HeistT n IO [DList (Chunk n)] runAttributes = mapM parseAtt ------------------------------------------------------------------------------ -- | Performs splice processing on a list of attributes. This is useful in -- situations where you need to stop recursion, but still run splice -- processing on the node's attributes. runAttributesRaw :: Monad n -- Note that this parameter should not be changed to Splices => [(Text, Text)] -- ^ List of attributes -> HeistT n IO (RuntimeSplice n [(Text, Text)]) runAttributesRaw attrs = do arrs <- mapM parseAtt2 attrs return $ liftM concat $ sequence arrs attrToChunk :: Text -> DList (Chunk n) -> DList (Chunk n) attrToChunk !k !v = do DL.concat [ DL.singleton $! pureTextChunk $! T.concat [" ", k, "=\""] , v, DL.singleton $! pureTextChunk "\"" ] attrToBuilder :: (Text, Text) -> Builder attrToBuilder (k,v) | T.null v = mconcat [ fromText " " , fromText k ] | otherwise = mconcat [ fromText " " , fromText k , fromText "=\"" , fromText v , fromText "\"" ] ------------------------------------------------------------------------------ getAttributeSplice :: Text -> HeistT n IO (DList (Chunk n)) getAttributeSplice name = lookupSplice name >>= fromMaybe (return $ DL.singleton $ Pure $ T.encodeUtf8 $ T.concat ["${", name, "}"]) {-# INLINE getAttributeSplice #-} getAttributeSplice2 :: Monad n => Text -> HeistT n IO (RuntimeSplice n Text) getAttributeSplice2 name = do mSplice <- lookupSplice name case mSplice of Nothing -> return $ return $ T.concat ["${", name, "}"] Just splice -> do res <- splice return $ liftM (T.decodeUtf8 . toByteString) $ codeGen res {-# INLINE getAttributeSplice2 #-} ------------------------------------------------------------------------------ -- | Promises are used for referencing the results of future runtime -- computations during load time splice processing. newtype Promise a = Promise (HE.Key a) ------------------------------------------------------------------------------ -- | Gets the result of a promised runtime computation. getPromise :: (Monad n) => Promise a -> RuntimeSplice n a getPromise (Promise k) = do mb <- gets (HE.lookup k) return $ fromMaybe e mb where e = error $ "getPromise: dereferenced empty key (id " ++ show (HE.getKeyId k) ++ ")" {-# INLINE getPromise #-} ------------------------------------------------------------------------------ -- | Adds a promise to the runtime splice context. putPromise :: (Monad n) => Promise a -> a -> RuntimeSplice n () putPromise (Promise k) x = modify (HE.insert k x) {-# INLINE putPromise #-} ------------------------------------------------------------------------------ -- | Modifies a promise. adjustPromise :: Monad n => Promise a -> (a -> a) -> RuntimeSplice n () adjustPromise (Promise k) f = modify (HE.adjust f k) {-# INLINE adjustPromise #-} ------------------------------------------------------------------------------ -- | Creates an empty promise. newEmptyPromise :: HeistT n IO (Promise a) newEmptyPromise = do keygen <- getsHS _keygen key <- liftIO $ HE.makeKey keygen return $! Promise key {-# INLINE newEmptyPromise #-} -- ------------------------------------------------------------------------------ -- -- | Creates an empty promise with some error checking to help with debugging. -- newEmptyPromiseWithError :: (Monad n) -- => String -> HeistT n IO (Promise a) -- newEmptyPromiseWithError from = do -- keygen <- getsHS _keygen -- prom <- liftM Promise $ liftIO $ HE.makeKey keygen -- yieldRuntimeEffect $ putPromise prom -- $ error -- $ "deferenced empty promise created at" ++ from -- return prom -- {-# INLINE newEmptyPromiseWithError #-} ------------------------------------------------------------------------------ -- | Binds a compiled splice. This function should not be exported. bindSplice :: Text -- ^ tag name -> Splice n -- ^ splice action -> HeistState n -- ^ source state -> HeistState n bindSplice n v ts = ts { _compiledSpliceMap = H.insert n' v (_compiledSpliceMap ts) } where n' = _splicePrefix ts `mappend` n ------------------------------------------------------------------------------ -- | Binds a list of compiled splices. This function should not be exported. bindSplices :: Splices (Splice n) -- ^ splices to bind -> HeistState n -- ^ source state -> HeistState n bindSplices ss hs = hs { _compiledSpliceMap = applySpliceMap hs _compiledSpliceMap ss } ------------------------------------------------------------------------------ -- | Adds a list of compiled splices to the splice map. This function is -- useful because it allows compiled splices to bind other compiled splices -- during load-time splice processing. withLocalSplices :: Splices (Splice n) -> Splices (AttrSplice n) -> HeistT n IO a -> HeistT n IO a withLocalSplices ss as = localHS (bindSplices ss . bindAttributeSplices as) ------------------------------------------------------------------------------ -- | Looks up a compiled template and returns a runtime monad computation that -- constructs a builder. -- -- Note that template names should not include the .tpl extension: -- -- @renderTemplate hs "index"@ renderTemplate :: Monad n => HeistState n -> ByteString -> Maybe (n Builder, MIMEType) renderTemplate hs nm = fmap (first (interpret . DL.fromList) . fst) $! lookupTemplate nm hs _compiledTemplateMap ------------------------------------------------------------------------------ -- | Looks up a compiled template and returns a compiled splice. callTemplate :: Monad n => ByteString -> Splice n callTemplate nm = do hs <- getHS maybe (error err) call $ lookupTemplate nm hs _templateMap where err = "callTemplate: "++(T.unpack $ T.decodeUtf8 nm)++(" does not exist") call (df,_) = localHS (\hs' -> hs' {_curTemplateFile = dfFile df}) $ runNodeList $ X.docContent $ dfDoc df interpret :: Monad n => DList (Chunk n) -> n Builder interpret = flip evalStateT HE.empty . unRT . codeGen ------------------------------------------------------------------------------ -- | Converts a pure text splice function to a pure Builder splice function. textSplice :: (a -> Text) -> a -> Builder textSplice f = fromText . f --------------- -- New Stuff -- --------------- ------------------------------------------------------------------------------ -- | This is the same as htmlNodeSplice. nodeSplice :: (a -> [X.Node]) -> a -> Builder nodeSplice f = X.renderHtmlFragment X.UTF8 . f {-# DEPRECATED nodeSplice "Use xmlNodeSplice or htmlNodeSplice, will be removed in Heist 1.1" #-} ------------------------------------------------------------------------------ -- | Converts a pure XML Node splice function to a pure Builder splice -- function. xmlNodeSplice :: (a -> [X.Node]) -> a -> Builder xmlNodeSplice f = X.renderXmlFragment X.UTF8 . f ------------------------------------------------------------------------------ -- | Converts a pure HTML Node splice function to a pure Builder splice -- function. htmlNodeSplice :: (a -> [X.Node]) -> a -> Builder htmlNodeSplice f = X.renderHtmlFragment X.UTF8 . f ------------------------------------------------------------------------------ -- | Converts a pure Builder splice function into a monadic splice function -- of a RuntimeSplice. pureSplice :: Monad n => (a -> Builder) -> RuntimeSplice n a -> Splice n pureSplice f n = return $ yieldRuntime (return . f =<< n) ------------------------------------------------------------------------------ -- | Runs a splice, but first binds splices given by splice functions that -- need some runtime data. withSplices :: Monad n => Splice n -- ^ Splice to be run -> Splices (RuntimeSplice n a -> Splice n) -- ^ Splices to be bound first -> RuntimeSplice n a -- ^ Runtime data needed by the above splices -> Splice n withSplices splice splices runtimeAction = withLocalSplices splices' mempty splice where splices' = mapV ($runtimeAction) splices ------------------------------------------------------------------------------ {-# INLINE foldMapM #-} foldMapM :: (Monad f, Monoid m, Foldable list) => (a -> f m) -> list a -> f m foldMapM f = Foldable.foldlM (\xs x -> xs `seq` liftM (xs <>) (f x)) mempty ------------------------------------------------------------------------------ -- | Like withSplices, but evaluates the splice repeatedly for each element in -- a list generated at runtime. manyWithSplices :: (Foldable f, Monad n) => Splice n -> Splices (RuntimeSplice n a -> Splice n) -> RuntimeSplice n (f a) -> Splice n manyWithSplices splice splices runtimeAction = manyWith splice splices mempty runtimeAction ------------------------------------------------------------------------------ -- | More powerful version of manyWithSplices that lets you also define -- attribute splices. manyWith :: (Foldable f, Monad n) => Splice n -> Splices (RuntimeSplice n a -> Splice n) -> Splices (RuntimeSplice n a -> AttrSplice n) -> RuntimeSplice n (f a) -> Splice n manyWith splice splices attrSplices runtimeAction = do p <- newEmptyPromise let splices' = mapV ($ getPromise p) splices let attrSplices' = mapV ($ getPromise p) attrSplices chunks <- withLocalSplices splices' attrSplices' splice return $ yieldRuntime $ do items <- runtimeAction foldMapM (\item -> putPromise p item >> codeGen chunks) items ------------------------------------------------------------------------------ -- | Similar to 'mapSplices' in interpreted mode. Gets a runtime list of -- items and applies a compiled runtime splice function to each element of the -- list. deferMany :: (Foldable f, Monad n) => (RuntimeSplice n a -> Splice n) -> RuntimeSplice n (f a) -> Splice n deferMany f getItems = do promise <- newEmptyPromise chunks <- f $ getPromise promise return $ yieldRuntime $ do items <- getItems foldMapM (\item -> putPromise promise item >> codeGen chunks) items ------------------------------------------------------------------------------ -- | Saves the results of a runtime computation in a 'Promise' so they don't -- get recalculated if used more than once. -- -- Note that this is just a specialized version of function application ($) -- done for the side effect in runtime splice. defer :: Monad n => (RuntimeSplice n a -> Splice n) -> RuntimeSplice n a -> Splice n defer pf n = do p2 <- newEmptyPromise let action = yieldRuntimeEffect $ putPromise p2 =<< n res <- pf $ getPromise p2 return $ action `mappend` res ------------------------------------------------------------------------------ -- | A version of defer which applies a function on the runtime value. deferMap :: Monad n => (a -> RuntimeSplice n b) -> (RuntimeSplice n b -> Splice n) -> RuntimeSplice n a -> Splice n deferMap f pf n = defer pf $ f =<< n ------------------------------------------------------------------------------ -- | Like deferMap, but only runs the result if a Maybe function of the -- runtime value returns Just. If it returns Nothing, then no output is -- generated. mayDeferMap :: Monad n => (a -> RuntimeSplice n (Maybe b)) -> (RuntimeSplice n b -> Splice n) -> RuntimeSplice n a -> Splice n mayDeferMap f pf n = deferMany pf $ f =<< n ------------------------------------------------------------------------------ -- | Converts an RuntimeSplice into a Splice, given a helper function that -- generates a Builder. bindLater :: (Monad n) => (a -> RuntimeSplice n Builder) -> RuntimeSplice n a -> Splice n bindLater f p = return $ yieldRuntime $ f =<< p