packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/LanguageC/Monad.hs

-- | Monad we use when translating the language-c AST to our AST
--
-- Intended for unqualified import.
module HsBindgen.Frontend.LanguageC.Monad (
    FromLanC(..)
  , ReparseEnv(..)
  , runFromLanC
  , getReparseEnv
  , getKnownTypes
  , lookupType
    -- * Throwing errors
  , unexpected
  , unsupported
  , skipped
  , nodeOmitted
  , unexpectedF
    -- * Util
  , repeatedly
  , optionally
  ) where

import Control.Monad.Except (ExceptT, MonadError (..))
import Control.Monad.Except qualified as Except
import Control.Monad.Reader (Reader)
import Control.Monad.Reader qualified as Reader
import Data.Foldable qualified as Foldable
import Data.Map qualified as Map
import Data.Set qualified as Set
import GHC.Stack

import HsBindgen.Frontend.LanguageC.Error
import HsBindgen.Frontend.LanguageC.PartialAST (CName)
import HsBindgen.Frontend.Pass.ReparseMacroExpansions.LanC (LanC)
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

-- | Monad used to translate from the @language-c@ AST to our AST
newtype FromLanC a = WrapFromLanC (
      ExceptT Error (Reader ReparseEnv) a
    )
  deriving newtype (
      Functor
    , Applicative
    , Monad
    , MonadError Error
    )

-- | Types in scope when reparsing a particular declaration
data ReparseEnv = ReparseEnv {
    -- | Known @typedef@s
    knownTypes      :: Map CName (C.Type LanC)
    -- | Known type-like macros
    --
    -- We store macros in a separate field, because we do not translate macros
    -- to their full C types.
    --
    -- Furthermore, we only add the macros /expanded in the reparsed
    -- declaration/ to the environment.
  , knownMacros :: Set CName
  }
  deriving (Show, Eq)

runFromLanC :: ReparseEnv -> FromLanC a -> Either Error a
runFromLanC typeEnv (WrapFromLanC ma) =
      flip Reader.runReader typeEnv $
        Except.runExceptT ma

getReparseEnv :: FromLanC ReparseEnv
getReparseEnv = WrapFromLanC Reader.ask

getKnownTypes :: ReparseEnv -> Set CName
getKnownTypes env = Map.keysSet env.knownTypes <> env.knownMacros

lookupType :: CName -> ReparseEnv -> Maybe (C.Type LanC)
lookupType nm env =
    case (Set.member nm env.knownMacros, Map.lookup nm env.knownTypes) of
      -- Macro types take priority: a typedef and a type-like macro may share the
      -- same bare name (e.g. 'bool' from stdbool.h alongside a typedef 'bool').
      --
      -- The underlying type '()' is a placeholder. The 'Zip' intermediate pass
      -- fills it in by consulting the annotation carrying information from
      -- pre-reparse 'PrepareReparse'.
      (True,  _)            -> Just (C.TypeMacro (C.MacroRef macroId ()))
      (False, mTypedefType) -> mTypedefType
  where
    macroId :: C.DeclId
    macroId = C.DeclId{
        name      = C.DeclName nm C.NameKindMacro
      , isUnnamed = False
      }

{-------------------------------------------------------------------------------
  Throwing errors
-------------------------------------------------------------------------------}

unexpected :: HasCallStack => String -> FromLanC x
unexpected = throwError . UpdateUnexpected callStack

unsupported :: String -> FromLanC x
unsupported = throwError . UpdateUnsupported

skipped :: String -> FromLanC x
skipped = throwError . UpdateSkipped

data NodeOmitted = NodeOmitted
  deriving (Show)

nodeOmitted :: Functor f => f a -> f NodeOmitted
nodeOmitted = fmap (const NodeOmitted)

unexpectedF ::
     (HasCallStack, Functor f, Show (f NodeOmitted))
  => f a -> FromLanC x
unexpectedF = unexpected . show . nodeOmitted

{-------------------------------------------------------------------------------
  Utility
-------------------------------------------------------------------------------}

-- | Run updates in order
--
-- Given
--
-- > addSuffix :: Update Int String
-- > addSuffix n str = return $ str ++ "_" ++ show n
--
-- We have
--
-- >    runUpdateM Map.empty $ repeatedly addSuffix [1..5] "str"
-- > == Right "str_1_2_3_4_5"
repeatedly :: forall t a b.
     Foldable t
  => (  a -> b -> FromLanC b)
  -> (t a -> b -> FromLanC b)
repeatedly f = go . Foldable.toList
  where
   --  go :: [a] -> b -> UpdateM b
    go []     b = return b
    go (a:as) b = f a b >>= \b' -> go as b'

optionally :: (a -> b -> FromLanC b) -> Maybe a -> b -> FromLanC b
optionally = repeatedly