packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/Parse/IsPass.hs

module HsBindgen.Frontend.Pass.Parse.IsPass (
    Parse
    -- * Configuration
  , EmptyMacros(..)
    -- * Macros
  , ReparseInfo(..)
  , invokedMacros
  , Tokens
    -- * Fields
  , FieldOrigin(..)
    -- * IsAnon
  , IsAnon(..)
  ) where

import Data.Set qualified as Set

import Clang.HighLevel.Types

import HsBindgen.Frontend.Pass.Parse.Msg
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass
import HsBindgen.Macro.Interface qualified as Macro
import HsBindgen.Macro.Syntax (MacroInvocation (name))

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

type Parse :: Pass
data Parse a

type family AnnParse (ix :: Symbol) :: Star where
  AnnParse "Function"      = ReparseInfo Tokens
  AnnParse "Global"        = ReparseInfo Tokens
  AnnParse "ImplicitField" = FieldOrigin
  AnnParse "IndirectField" = ReparseInfo Tokens
  AnnParse "RegularField"  = ReparseInfo Tokens
  AnnParse "Struct"        = IsAnon
  AnnParse "Typedef"       = ReparseInfo Tokens
  AnnParse "Union"         = IsAnon
  AnnParse _               = NoAnn

instance IsPass Parse

instance PassId Parse where
  type Id Parse = C.PrelimDeclId

  idNameKind     _ = C.prelimDeclIdNameKind
  idSourceName   _ = C.prelimDeclIdSourceName
  idLocationInfo _ = C.prelimDeclIdLocationInfo

instance PassScopedName Parse

instance PassTypes Parse

instance PassMacro Parse where
  type MacroBody Parse = Macro.Unresolved

instance PassExtBinding Parse

instance PassCommentDecl Parse

instance PassAnn Parse where
  type Ann ix Parse = AnnParse ix

instance PassMsg Parse where
  type Msg Parse = C.WithLocationInfo ImmediateParseMsg

{-------------------------------------------------------------------------------
  Configuration
-------------------------------------------------------------------------------}

-- | Parse macros with an empty replacement list?
--
-- Whether @hs-bindgen@ parses and translates empty macros such as
--
-- @#define FOO@.
--
-- Some macro languages such as 'HsBindgen.Macro.Raw' can handle empty macros,
-- the default macro language 'HsBindgen.Macro.CExpr' has no expression to
-- translate and declines empty macros.
--
-- Include guards are empty macros, so by default @hs-bindgen@ does not attempt
-- to parse an empty macro at all.
data EmptyMacros =
    -- | Pass empty macros to the macro language
    ParseEmptyMacros

    -- | Do not attempt to parse empty macros
  | DoNotParseEmptyMacros
  deriving stock (Show, Eq)

instance Default EmptyMacros where
  def :: EmptyMacros
  def = DoNotParseEmptyMacros

{-------------------------------------------------------------------------------
  Macros
-------------------------------------------------------------------------------}

data ReparseInfo tokens =
    -- | We need to reparse this declaration (to deal with macros)
    --
    -- We do not use this for macro declarations _themselves_ (see
    -- 'ParsedMacro').
    ReparseNeeded
      tokens
      -- ^ Original tokens of declaration without macro expansions
      (NonEmpty MacroInvocation)
      -- ^ Expanded macros

    -- | This declaration does not use macros, so no need to reparse
  | ReparseNotNeeded
  deriving stock (Show, Eq, Ord)

-- | Names of expanded macros
invokedMacros :: NonEmpty MacroInvocation -> Set Text
invokedMacros = foldl' (\acc inv -> Set.insert inv.name acc) Set.empty

type Tokens = [Token SourcePath TokenSpelling]

{-------------------------------------------------------------------------------
  Fields
-------------------------------------------------------------------------------}

-- | The field name used to compute the offset for an implicit field referencing
-- an anonymous struct\/union.
--
-- We track this information because @libclang@ does not expose information
-- about implicit fields, so we have to derive the information ourselves using a
-- custom algorithm. See the "HsBindgen.Frontend.Pass.Parse.Decl.ImplicitFields"
-- module for the algorithm.
data FieldOrigin = FieldOrigin {
    -- | The name of the first field of the anonymous struct\/union
    --
    -- The offset from the enclosing object to an anonymous struct\/union is
    -- equal to the offset from the enclosing object to the first field of the
    -- anonymous struct or union. The first field can be a regular field, or
    -- an implicit field if there are multiple levels of nested anonymous
    -- structs/unions. Indirect fields are ignored.
    --
    -- The field that we record should be one that @libclang@ recognizes. Names
    -- for implicit fields are generated by @hs-bindgen@, so we should use an
    -- equivalent stand-in for them:
    --
    -- * If the first field is a regular field, then we record its name in
    --   'field'.
    --
    -- * If the first field is an implicit field, then we record in 'field' the
    --   name of the first field of the referenced anonymous struct\/union. This
    --   process repeats if that first field is again an implicit field.
    --
    -- Example:
    --
    -- > struct S {
    -- >   struct { // anonymous struct T
    -- >     struct { // anonymous struct U
    -- >       int x; // regular field x
    -- >     }; // implicit field referencing U with field origin x
    -- >   }; // implicit field referencing T with field origin x
    -- > };
    --
    field :: C.ScopedName
  }
  deriving stock (Show, Eq, Ord)

{-------------------------------------------------------------------------------
  IsAnon
-------------------------------------------------------------------------------}

-- | A struct or union is anonymous if it is untagged and if it is referenced by
-- a single unnamed field.
newtype IsAnon = IsAnon { isAnon :: Bool }
  deriving stock (Show, Eq, Ord)