fir-0.1.0.0: src/CodeGen/Monad.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-|
Module: CodeGen.Monad
Defines the monad used by the code generator, including the monadic state required for code generation.
-}
module CodeGen.Monad
( MonadFresh(fresh), runFreshSuccT
, CGMonad, putCG
, liftPut
, create, createID
, createRec, createIDRec
, tryToUse, tryToUseWith
, runExceptTPutM, runCGMonad
, note
)
where
-- base
import Control.Monad.Fix
( MonadFix )
import Control.Category
( (>>>) )
-- binary
import Data.Binary
( Binary )
import qualified Data.Binary as Binary
import qualified Data.Binary.Put as Binary
-- bytestring
import Data.ByteString.Builder
( Builder, toLazyByteString )
import Data.ByteString.Lazy
( ByteString )
-- lens
import Control.Lens
( Lens'
, use, assign, (<<%=)
)
-- mtl
import Control.Monad.Except
( MonadError , ExceptT, runExceptT, throwError )
import Control.Monad.Reader
( MonadReader, ReaderT, runReaderT )
import Control.Monad.State
( MonadState , StateT , runStateT )
-- text-short
import Data.Text.Short
( ShortText )
-- transformers
import Control.Monad.Trans.Class
( MonadTrans, lift )
import Control.Monad.Trans.Writer.Lazy
( Writer, runWriter, tell )
-- fir
import Control.Arrow.Strength
( leftStrength )
import CodeGen.Instruction
( ID )
import CodeGen.State
( CGState, CGContext
, _currentID
)
----------------------------------------------------------------------------
-- monad for code generation
type CGMonad
= FreshSuccT CGState -- supply of fresh variable IDs using CGState (see below)
( ReaderT CGContext -- context for code generation
( StateT CGState -- state (including for instance a map of which types have been defined)
( ExceptT ShortText -- for errors during code generation
( Writer Builder ) -- to write instructions in binary form
)
)
)
-- NB: all the layers of this monad are lazy, so that we can tie knots using 'mfix'.
deriving newtype instance MonadReader CGContext CGMonad
deriving newtype instance MonadError ShortText CGMonad
-- other instances automatically derived from the definition of FreshSuccT:
-- Functor, Applicative, Monad, MonadState CGState, MonadFresh ID
liftPut :: Binary.Put -> CGMonad ()
liftPut = lift . lift . lift . lift . tell . Binary.execPut -- apologies
putCG :: Binary a => a -> CGMonad ()
putCG = liftPut . Binary.put
----------------------------------------------------------------------------
-- fresh name generation
class HasSupply v s where
supply :: Lens' s v
instance HasSupply ID CGState where
supply = _currentID
instance HasSupply ID ID where
supply = id
class Monad m => MonadFresh v m where
fresh :: m v
newtype FreshSuccT s m a = FreshSuccT { runFreshSuccT :: m a }
deriving newtype instance Functor m => Functor (FreshSuccT s m)
deriving newtype instance Applicative m => Applicative (FreshSuccT s m)
deriving newtype instance Monad m => Monad (FreshSuccT s m)
deriving newtype instance MonadState s m => MonadState s (FreshSuccT s m)
deriving newtype instance MonadFix m => MonadFix (FreshSuccT s m)
instance MonadTrans (FreshSuccT s) where
lift = FreshSuccT
instance (MonadState s m, HasSupply v s, Enum v) => MonadFresh v (FreshSuccT s m) where
fresh = supply <<%= succ -- <<%= means: "modify, and return the _old_ value"
create :: ( MonadFresh v m, MonadState s m )
=> Lens' s (Maybe a) -> (v -> m a) -> m (v, a)
create _key mk
= do v <- fresh
a <- mk v
assign _key ( Just a )
pure (v, a)
createID :: ( MonadFresh v m, MonadState s m )
=> Lens' s (Maybe a) -> (v -> m a) -> m v
createID _key mk = fst <$> create _key mk
createRec :: ( MonadFresh v m, MonadState s m )
=> Lens' s (Maybe a) -> m b -> (b -> v -> m a) -> m (v, a)
createRec _key before mk
= do b <- before
v <- fresh
a <- mk b v
assign _key ( Just a )
pure (v, a)
createIDRec :: ( MonadFresh v m, MonadState s m )
=> Lens' s (Maybe a) -> m b -> (b -> v -> m a) -> m v
createIDRec _key before mk = fst <$> createRec _key before mk
-- | Try to use a given lens to obtain a value.
--
-- If using the lens returns Nothing, create the value instead, using 'create'.
tryToUse :: ( MonadFresh v m, MonadState s m )
=> Lens' s (Maybe a) -> (a -> b) -> (v -> m a) -> m b
tryToUse _key f mk = tryToUseWith _key f ( f . snd <$> create _key mk )
-- | Like 'tryToUse', but allowing for a custom creation procedure.
tryToUseWith :: ( MonadState s m )
=> Lens' s (Maybe a) -> (a -> b) -> m b -> m b
tryToUseWith _key f creation =
use _key >>= maybe creation (pure . f)
----------------------------------------------------------------------------
-- running the code generation monad
runExceptTPutM :: ExceptT e Binary.PutM a -> Either e (a, ByteString)
runExceptTPutM = runExceptT >>> uncurry leftStrength . Binary.runPutM
runCGMonad :: CGContext -> CGState -> CGMonad r -> Either ShortText (r, CGState, ByteString)
runCGMonad context state
= runFreshSuccT
>>> ( `runReaderT` context )
>>> ( `runStateT` state )
>>> runExceptT
>>> runWriter
>>> \ ( res, b ) -> fmap ( \ ( r, s ) -> ( r, s, toLazyByteString b ) ) res
----------------------------------------------------------------------------
-- utility for exceptions
note :: forall m a e. MonadError e m => e -> Maybe a -> m a
note _ (Just a) = pure a
note e Nothing = throwError e