packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Language/Haskell.hs

-- | General Haskell language types
--
-- Intended for qualified import.
--
-- > import HsBindgen.Language.Haskell qualified as Hs
module HsBindgen.Language.Haskell (
    -- * Module names
    ModuleName(..)
  , moduleNameFromString
  , moduleNameToString
  , moduleNamePath
    -- * Module imports
  , Import(..)
    -- * References
  , ExtRef(..)
  , Ref(..)
    -- * Namespaces
  , Namespace(..)
  , SNamespace(..)
  , namespaceOf
  , SingNamespace(..)
    -- * Namespaced names
  , Name(..)
  , nameToStr
  , SomeName(..)
  , demoteNs
  , assertNs
  ) where

import Data.Foldable qualified as Foldable
import Data.Text qualified as Text
import System.FilePath
import Text.SimplePrettyPrint ((><))
import Text.SimplePrettyPrint qualified as PP

import HsBindgen.Errors
import HsBindgen.Imports
import HsBindgen.Util.Tracer

{-------------------------------------------------------------------------------
  Module names
-------------------------------------------------------------------------------}

-- | Haskell module name
--
-- Example: @HsBindgen.Runtime.LibC@
newtype ModuleName = ModuleName { text :: Text }
  deriving stock (Generic)
  -- 'Show' instance valid due to 'IsString' instance
  deriving newtype (Eq, IsString, Ord, Show)

moduleNameFromString :: String -> ModuleName
moduleNameFromString = ModuleName . Text.pack

moduleNameToString :: ModuleName -> String
moduleNameToString moduleName = Text.unpack moduleName.text

moduleNamePath :: ModuleName -> FilePath
moduleNamePath moduleName = withoutExt <.> "hs"
  where
    withoutExt :: FilePath
    withoutExt =
        Foldable.foldl' (</>) "" $
          map Text.unpack (Text.splitOn "." moduleName.text)

instance PrettyForTrace ModuleName where
  prettyForTrace moduleName = PP.text moduleName.text

{-------------------------------------------------------------------------------
  Module imports
-------------------------------------------------------------------------------}

-- | A qualified or unqualified import of a module
data Import =
    -- | Unqualified import, listing the names in use
    UnqualifiedImport ModuleName
    -- | Qualified import possibly with an alias
  | QualifiedImport   ModuleName (Maybe String)
  deriving (Eq, Ord, Show)

{-------------------------------------------------------------------------------
  References
-------------------------------------------------------------------------------}

-- | External reference
data ExtRef = ExtRef {
      moduleName :: ModuleName
      -- TODO https://github.com/well-typed/hs-bindgen/issues/423
      --
      -- At the moment, we only handle external references to types types. Later
      -- we may support external references to variables (e.g., in macros).
    , name       :: Name NsTypeConstr
    }
  deriving stock (Eq, Generic, Ord, Show)

-- | Reference
data Ref =
    -- | Reference to a name in the local scope
    RefLocal SomeName

  | -- | Reference to a type in a different module
    RefExt ExtRef
  deriving stock (Eq, Show)

{-------------------------------------------------------------------------------
  Namespaces
-------------------------------------------------------------------------------}

-- | Namespace
--
-- See section 1.4, "Namespaces" of the Haskell report
-- <https://www.haskell.org/onlinereport/haskell2010/haskellch1.html#x6-130001.4>
data Namespace =
    NsTypeConstr
  | NsConstr
  | NsVar
  deriving (Eq, Ord, Show)

instance PrettyForTrace Namespace where
  prettyForTrace = \case
    NsTypeConstr -> "type constructor"
    NsConstr     -> "data constructor"
    NsVar        -> "variable"

-- | Namespace singleton
data SNamespace :: Namespace -> Star where
  SNsTypeConstr :: SNamespace 'NsTypeConstr
  SNsConstr     :: SNamespace 'NsConstr
  SNsVar        :: SNamespace 'NsVar

deriving stock instance Show (SNamespace ns)

-- | Get the namespace of a namespace singleton
namespaceOf :: SNamespace ns -> Namespace
namespaceOf = \case
    SNsTypeConstr -> NsTypeConstr
    SNsConstr     -> NsConstr
    SNsVar        -> NsVar

-- | Namespace singleton class
class SingNamespace ns where
  singNamespace :: SNamespace ns

instance SingNamespace 'NsTypeConstr where singNamespace = SNsTypeConstr
instance SingNamespace 'NsConstr     where singNamespace = SNsConstr
instance SingNamespace 'NsVar        where singNamespace = SNsVar

{-------------------------------------------------------------------------------
  Namespaced names
-------------------------------------------------------------------------------}

-- | Haskell name created by "HsBindgen.Config.MangleCandidate" with correctness
-- guarantees.
--
-- Also stores information about the Haskell namespace on the type level.
-- Useful locally, to ensure correctness on the type level.
--
-- The constructor is "unsafe", because only the name mangler can ensure that
-- namespace-specific naming rules are honored.
newtype Name (ns :: Namespace) = UnsafeName {
      text :: Text
    }
    deriving stock   (Show, Eq, Ord, Generic)

nameToStr :: Name ns -> String
nameToStr n = Text.unpack n.text

instance SingNamespace ns => PrettyForTrace (Name ns) where
  prettyForTrace x = prettyForTrace $ UnsafeSomeName ns x.text
    where
      ns :: Namespace
      ns = namespaceOf (singNamespace @ns)

-- | Haskell names with information about the Haskell namespace on the value
--   level. Useful when handling names more globally such as in the list of
--   declarations.
--
-- The constructor is "unsafe", because only the name mangler can ensure that
-- namespace-specific naming rules are honored.
data SomeName = UnsafeSomeName {
      ns   :: Namespace
    , text :: Text
    }
  deriving stock (Show, Eq, Ord, Generic)

instance PrettyForTrace SomeName where
  prettyForTrace x = PP.hsep [
      "'" >< PP.text x.text >< "'"
    , "(" >< prettyForTrace x.ns >< ")"
    ]

-- | Remove the type-level namespace information, and store it on the value
--   level.
demoteNs :: forall ns. SingNamespace ns => Name ns -> SomeName
demoteNs name = UnsafeSomeName (namespaceOf $ singNamespace @ns) name.text

-- | Get Haskell name for some name in the expected namespace
--
-- Panics if the stored namespace does not match the expected one.
assertNs ::
     forall ns. (HasCallStack, SingNamespace ns)
  => Proxy ns -> SomeName -> Name ns
assertNs _ nm
    | nm.ns == expected = UnsafeName nm.text
    | otherwise = panicPure $
        "assertNs: namespace mismatch; expected "
          <> show expected <> " but got " <> show nm.ns
  where
    expected :: Namespace
    expected = namespaceOf (singNamespace @ns)