packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Config/Prelims.hs

module HsBindgen.Config.Prelims (
    -- * Base module name
    BaseModuleName(..)
  , baseModuleNameToString
  , fromBaseModuleName

    -- * Field naming strategy
  , FieldNamingStrategy(..)

    -- * Qualified style
  , QualifiedStyle(..)

    -- * Module render config
  , ModuleRenderConfig(..)

    -- * Unique IDs
  , UniqueId (..)
  , UniqueIdMsg (..)
  , checkUniqueId
  ) where

import Data.Text qualified as Text
import Text.SimplePrettyPrint qualified as PP

import HsBindgen.Backend.Category
import HsBindgen.Imports
import HsBindgen.Language.Haskell qualified as Hs
import HsBindgen.Util.Tracer

{-------------------------------------------------------------------------------
  Base module name
-------------------------------------------------------------------------------}

-- | Base module name from which other module names are derived
--
-- For example, the base module name might be @Generated@, from which we
-- derive @Generated@, @Generated.Safe@, etc.
newtype BaseModuleName = BaseModuleName { text :: Text }
  deriving stock (Eq, Generic)
  deriving newtype (IsString, Show)

instance Default BaseModuleName where
  def = "Generated"

baseModuleNameToString :: BaseModuleName -> String
baseModuleNameToString baseModule = Text.unpack baseModule.text

fromBaseModuleName :: BaseModuleName -> Maybe Category -> Hs.ModuleName
fromBaseModuleName (BaseModuleName base) Nothing =
    Hs.ModuleName base
fromBaseModuleName (BaseModuleName base) (Just CType) =
    Hs.ModuleName base
fromBaseModuleName (BaseModuleName base) (Just (CTerm cat)) =
    Hs.ModuleName (base <> "." <> submodule cat)
  where
    -- NOTE: It is important that types are stored in a module without any
    -- suffix; we depend on this assumption for binding specifications (which
    -- only refer to types, never to functions or globals).
    submodule :: TermCategory -> Text
    submodule CSafe   = "Safe"
    submodule CUnsafe = "Unsafe"
    submodule CFunPtr = "FunPtr"
    submodule CGlobal = "Global"

{-------------------------------------------------------------------------------
  Field naming strategy
-------------------------------------------------------------------------------}

-- | Strategy for naming struct\/union fields.
--
-- With 'AddFieldPrefixes' (the default), field names are prefixed with the
-- struct name to avoid name collisions (e.g., @timeval_tv_sec@).
--
-- With 'OmitFieldPrefixes', field names are not prefixed (e.g., @tv_sec@). This
-- usually necessitates the @DuplicateRecordFields@ GHC extension, which is
-- automatically added when this strategy is selected.
data FieldNamingStrategy =
    AddFieldPrefixes
  | OmitFieldPrefixes
  deriving stock (Show, Eq, Generic)

instance Default FieldNamingStrategy where
  def = AddFieldPrefixes

{-------------------------------------------------------------------------------
  Qualified style
-------------------------------------------------------------------------------}

-- | Style for qualified imports in generated code.
--
-- With 'PreQualified' (the default), imports are prepositive:
-- @import qualified Data.Proxy@.
--
-- With 'PostQualified', imports use the @ImportQualifiedPost@ extension:
-- @import Data.Proxy qualified@. This avoids warnings from
-- @-Wprepositive-qualified-module@.
data QualifiedStyle =
    PreQualified
  | PostQualified
  deriving stock (Show, Eq, Generic)

instance Default QualifiedStyle where
  def = PreQualified

{-------------------------------------------------------------------------------
  Module render config
-------------------------------------------------------------------------------}

-- | Configuration for rendering Haskell modules.
--
-- Bundles settings that affect the surface syntax of generated modules but
-- do not change semantics.
data ModuleRenderConfig = ModuleRenderConfig {
      qualifiedStyle :: QualifiedStyle
    }
  deriving stock (Show, Eq, Generic)
  deriving anyclass (Default)

{-------------------------------------------------------------------------------
  Unique IDs
-------------------------------------------------------------------------------}

-- | C uses a global namespace. We must ensure that identifiers generated by
-- @hs-bindgen@ are unique.
--
-- Defaults to the empty string @t'UniqueId' ""@, which is __suboptimal__. We
-- encourage using unique identifiers such as
-- "com.well-typed.your-package-name".
data UniqueId = UniqueId { unUniqueId :: String }
  deriving stock (Show, Eq, Ord)

instance Default UniqueId where
  def = UniqueId ""

data UniqueIdMsg =
    UniqueIdEmpty
  deriving (Show, Eq, Ord)

instance PrettyForTrace UniqueIdMsg where
  prettyForTrace = \case
    UniqueIdEmpty -> PP.vcat [
        "empty unique identifier ('UniqueId', '--unique-id'):"
      , "  C uses a global namespace."
      , "  We encourage using a unique identifier to avoid duplicate symbol names."
      , "  For example, use and adapt 'com.example.package'."
      ]

instance IsTrace Level UniqueIdMsg where
  getDefaultLogLevel = \case
    UniqueIdEmpty     -> Warning
  getSource  = const HsBindgen
  getTraceId = const "unique-id"

checkUniqueId :: Tracer UniqueIdMsg -> UniqueId -> IO ()
checkUniqueId tracer (UniqueId val) = do
  when (null val) $
    traceWith tracer $ withCallStack UniqueIdEmpty