{-# LANGUAGE FlexibleInstances, LambdaCase #-} -- | The core types involved used by the pre-processor. module Hpp.Types where import Control.Monad (ap, join) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.Class (MonadTrans, lift) import Control.Monad.Trans.Except (ExceptT, throwE) import Data.IORef (IORef, newIORef, readIORef, writeIORef) -- import qualified Data.Map as M import Hpp.Config import Hpp.Tokens -- | Line numbers are represented as 'Int's type LineNum = Int -- | A macro binding environment. type Env = [(String, Macro)] -- type Env = M.Map String Macro -- * Errors -- | Error conditions we may encounter. data Error = UnterminatedBranch | BadMacroDefinition LineNum | BadIfPredicate | BadLineArgument LineNum String | IncludeDoesNotExist LineNum FilePath | FailedInclude LineNum FilePath | UserError LineNum String | UnknownCommand LineNum String | TooFewArgumentsToMacro LineNum String | BadMacroArguments LineNum String | NoInputFile | BadCommandLine String | RanOutOfInput deriving (Eq, Ord, Show) -- | Hpp can raise various parsing errors. class HasError m where throwError :: Error -> m a instance Monad m => HasError (ExceptT Error m) where throwError = throwE instance (Monad m, HasHppState m) => HasHppState (ExceptT e m) where getState = lift getState {-# INLINE getState #-} setState = lift . setState {-# INLINE setState #-} -- * Resource cleanup -- | A cleanup action that is run at most once. To be used as an -- abstract type with only 'runCleanup' and 'mkCleanup' as interface. newtype Cleanup = Cleanup (IORef (IO ())) -- | Runs an action and replaces it with a nop runCleanup :: Cleanup -> IO () runCleanup (Cleanup r) = join (readIORef r) >> writeIORef r (return ()) -- | @mkCleanup cleanup@ returns two things: a 'Cleanup' value, and an -- action to neutralize that 'Cleanup'. In this way, the 'Cleanup' -- value can be registered with a resource manager so that, in the -- event of an error, the cleanup action is run, while the neutralizer -- may be used to ensure that the registered 'Cleanup' action has no -- effect if it is run. Typically one would neutralize a registered -- cleanup action before performing a manual cleanup that subsumes the -- registered cleanup. mkCleanup :: IO () -> IO (Cleanup, IO ()) mkCleanup m = do r <- newIORef m return $ (Cleanup r, writeIORef r (return ())) -- * Free Monad Transformers -- | Base functor for a free monad transformer data FreeF f a r = PureF a | FreeF (f r) instance Functor f => Functor (FreeF f a) where fmap _ (PureF x) = PureF x fmap f (FreeF x) = FreeF $ fmap f x {-# INLINE fmap #-} -- * Pre-processor Actions -- | Dynamic state of the preprocessor engine. data HppState = HppState { hppConfig :: Config , hppLineNum :: LineNum , hppCleanups :: [Cleanup] , hppEnv :: Env } -- | A free monad construction to strictly delimit what capabilities -- we need to perform pre-processing. data HppF t r = ReadFile Int FilePath (t -> r) | ReadNext Int FilePath (t -> r) | GetState (HppState -> r) | SetState HppState r | ThrowError Error instance Functor (HppF t) where fmap f (ReadFile ln file k) = ReadFile ln file (f . k) fmap f (ReadNext ln file k) = ReadNext ln file (f . k) fmap f (GetState k) = GetState (f . k) fmap f (SetState cfg k) = SetState cfg (f k) fmap _ (ThrowError e) = ThrowError e {-# INLINE fmap #-} -- * Hpp Monad Transformer -- | A free monad transformer specialized to HppF as the base functor. newtype HppT t m a = HppT { runHppT :: m (FreeF (HppF t) a (HppT t m a)) } instance Functor m => Functor (HppT t m) where fmap f (HppT x) = HppT $ fmap f' x where f' (PureF y) = PureF (f y) f' (FreeF y) = FreeF $ fmap (fmap f) y {-# INLINE fmap #-} instance Monad m => Applicative (HppT t m) where pure = HppT . pure . PureF {-# INLINE pure #-} (<*>) = ap {-# INLINE (<*>) #-} instance Monad m => Monad (HppT t m) where return = pure {-# INLINE return #-} HppT ma >>= fb = HppT $ ma >>= \case PureF x -> runHppT $ fb x FreeF x -> return . FreeF $ fmap (>>= fb) x {-# INLINE (>>=) #-} instance MonadTrans (HppT t) where lift = HppT . fmap PureF {-# INLINE lift #-} instance MonadIO m => MonadIO (HppT t m) where liftIO = HppT . fmap PureF . liftIO {-# INLINE liftIO #-} -- | An interpreter capability to modify dynamic state. class HasHppState m where getState :: m HppState setState :: HppState -> m () instance Monad m => HasHppState (HppT t m) where getState = HppT . pure . FreeF $ GetState pure {-# INLINE getState #-} setState s = HppT . pure . FreeF $ SetState s (pure ()) {-# INLINE setState #-} -- | An interpreter capability of threading a binding environment. class HasEnv m where getEnv :: m Env setEnv :: Env -> m () instance Monad m => HasEnv (HppT t m) where getEnv = fmap hppEnv getState {-# INLINE getEnv #-} setEnv e = getState >>= setState . (\s -> s { hppEnv = e }) {-# INLINE setEnv #-} instance Applicative m => HasError (HppT t m) where throwError = HppT . pure . FreeF . ThrowError {-# INLINE throwError #-} instance (HasEnv m, Monad m) => HasEnv (ExceptT e m) where getEnv = lift getEnv {-# INLINE getEnv #-} setEnv = lift . setEnv {-# INLINE setEnv #-} -- * Expansion -- | Macro expansion involves treating tokens differently if they -- appear in the original source for or as the result of a previous -- macro expansion. This distinction is used to prevent divergence by -- masking out definitions that could be used recursively. -- -- Things are made somewhat more complicated than one might expect due -- to the fact that the scope of this masking is /not/ structurally -- recursive. A object-like macro can expand into a fragment of a -- macro function application, one of whose arguments is a token -- matching the original object-like macro. That argument should /not/ -- be expanded. data Scan = Unmask String | Mask String | Scan Token | Rescan Token deriving (Eq, Show) -- * Macros -- | There are object-like macros and function-like macros. data Macro = Object [Token] -- ^ An object-like macro is replaced with its definition | Function Int ([([Scan],String)] -> [Scan]) -- ^ A function-like macro of some arity taks -- macro-expanded and raw versions of its arguments, then -- substitutes them into a body producing a new set of -- tokens. instance Show Macro where show (Object ts) = "Object "++ detokenize ts show (Function n _) = "Fun<"++show n++">"