packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/RootHeader.hs

-- | Root header (header that includes all headers to be processed)
--
-- Intended for qualified import.
--
-- > import HsBindgen.Frontend.RootHeader (RootHeader)
-- > import HsBindgen.Frontend.RootHeader qualified as RootHeader
module HsBindgen.Frontend.RootHeader (
    -- * RootHeader
    RootHeader -- opaque
  , fromRootDirectives
    -- ** Generation
  , name
  , content
    -- ** Query
  , isRootHeaderPath
  , isInRootHeader
    -- ** Trace message
  , RootHeaderMsg(..)
  ) where

import Prelude hiding (lookup)

import Text.SimplePrettyPrint qualified as PP

import Clang.HighLevel.Types (MultiLoc (multiLocExpansion),
                              SingleLoc (singleLocPath))
import Clang.Paths

import HsBindgen.IR.C qualified as C
import HsBindgen.Util.Tracer

{-------------------------------------------------------------------------------
  RootHeader
-------------------------------------------------------------------------------}

-- | Abstract representation of the root header
--
-- This is /precisely/ the list of root directives as specified by the user.
newtype RootHeader = RootHeader [C.RootDirective C.HashIncludeArg]

-- | Construct a t'RootHeader', returning trace messages
fromRootDirectives ::
     [C.RootDirective C.HashIncludeArg]
  -> ([RootHeaderMsg], RootHeader)
fromRootDirectives directives = (msgs, RootHeader directives)
  where
    -- Without a header there is nothing to generate bindings for. This is the
    -- single check: every way of using @hs-bindgen@ builds the root header here.
    msgs :: [RootHeaderMsg]
    msgs = [RootHeaderNoHashInclude | null (C.hashIncludeArgsOf directives)]

{-------------------------------------------------------------------------------
  Generation
-------------------------------------------------------------------------------}

-- | Root header @UnsavedFile@ name
name :: SourcePath
name = C.rootHeaderName

-- | Root header content
--
-- The content contains one directive per line, in order, with no extra lines.
-- The very same rendering is prepended to the generated CAPI wrapper source and
-- to the generated C test source, so that all C stages agree.
content :: RootHeader -> String
content (RootHeader directives) = C.renderRootDirectives directives

{-------------------------------------------------------------------------------
  Query
-------------------------------------------------------------------------------}

-- | Check if the specified path is the root header
--
-- Takes a 'SourcePath' because it is used with Clang diagnostics, which
-- report locations as 'SourcePath' (not 'RealPath').
isRootHeaderPath :: SourcePath -> Bool
isRootHeaderPath = (== name)

-- | Check if the given location is in the root header
isInRootHeader :: MultiLoc SourcePath -> Bool
isInRootHeader = isRootHeaderPath . singleLocPath . multiLocExpansion

{-------------------------------------------------------------------------------
  Trace messages
-------------------------------------------------------------------------------}

-- | Root header trace message
data RootHeaderMsg =
    RootHeaderNoHashInclude
  deriving stock (Show)

instance PrettyForTrace RootHeaderMsg where
  prettyForTrace = \case
    RootHeaderNoHashInclude ->
      PP.string "no #include root directive: nothing to generate bindings for"

instance IsTrace Level RootHeaderMsg where
  -- A warning, not an error: hs-bindgen runs to completion, and no declarations
  -- is the correct result for a root header with no @#include@.
  getDefaultLogLevel = const Warning
  getSource          = const HsBindgen
  getTraceId         = const "root-header"