packages feed

hoppy-generator-0.8.0: src/Foreign/Hoppy/Generator/Spec/Enum.hs

-- This file is part of Hoppy.
--
-- Copyright 2015-2021 Bryan Gardiner <bog@khumba.net>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

-- | Interface for defining bindings to C++ enumerations.
--
-- In generated Haskell code:
--
-- An enum gets a single algebraic data type with data constructors for each of
-- the values defined in the interface.  If the enum has an unknown value name
-- defined, then an additional data constructor is generated that holds a
-- numeric value, and this constructor is used whenever numeric values for which
-- no name is explicitly defined are encountered (otherwise, 'error' is called).
--
-- From the runtime module, a @CppEnum@ instance is generated for the type, and
-- if the enum is declared to permit bit operations, then a 'Data.Bits.Bits'
-- instance is also generated.  'Eq' and 'Ord' instances are generated that
-- compare numeric values.
module Foreign.Hoppy.Generator.Spec.Enum (
  -- * Data type
  CppEnum, enumT,
  -- * Construction
  makeEnum, makeAutoEnum, IsAutoEnumValue (..),
  -- * Properties
  enumExtName,
  enumIdentifier,
  enumNumericType, enumSetNumericType,
  enumValues,
  enumReqs,
  enumAddendum,
  enumValuePrefix, enumSetValuePrefix,
  enumAddEntryNameOverrides,
  enumGetOverriddenEntryName,
  IsEnumUnknownValueEntry (..),
  enumUnknownValueEntry, enumSetUnknownValueEntry, enumSetNoUnknownValueEntry,
  enumUnknownValueEntryDefault,
  enumHasBitOperations, enumSetHasBitOperations,
  -- * C++ generator
  cppGetEvaluatedEnumData,
  -- * Haskell generator
  hsGetEvaluatedEnumData,
  -- ** Names
  toHsEnumTypeName, toHsEnumTypeName',
  toHsEnumCtorName, toHsEnumCtorName',
  ) where

import Control.Arrow ((&&&), (***))
import Control.Monad (forM, forM_, when)
import Control.Monad.Except (throwError)
import Data.Function (on)
import qualified Data.Map as M
import Foreign.Hoppy.Generator.Common (butLast, capitalize, for)
import Foreign.Hoppy.Generator.Spec.Base
import Foreign.Hoppy.Generator.Spec.Computed (
  EvaluatedEnumData,
  evaluatedEnumNumericType,
  evaluatedEnumValueMap,
  getEvaluatedEnumData,
  numType,
  )
import qualified Foreign.Hoppy.Generator.Language.Cpp as LC
import qualified Foreign.Hoppy.Generator.Language.Haskell as LH
import Foreign.Hoppy.Generator.Override (addOverrideMap, overriddenMapLookup, plainMap)
import Foreign.Hoppy.Generator.Types (manualT)
import Foreign.Hoppy.Generator.Util (splitIntoWords)
import GHC.Stack (HasCallStack)
import Language.Haskell.Syntax (
  HsName (HsIdent),
  HsQName (UnQual),
  HsType (HsTyCon),
  )

-- | A C++ enum declaration.
--
-- See 'Foreign.Hoppy.Generator.Spec.EnumInfo'.
data CppEnum = CppEnum
  { enumExtName :: ExtName
    -- ^ The enum's external name.
  , enumIdentifier :: Identifier
    -- ^ The identifier used to refer to the enum.
  , enumNumericType :: Maybe Type
    -- ^ An optional, explicit numeric type provided for the enum's values, that
    -- matches what the C++ compiler uses.  Hoppy will use
    -- 'Foreign.Hoppy.Generator.Hook.Hooks' to compute this automatically, if
    -- not given manually.  This does not need to be provided.  If absent
    -- (default), then Hoppy will calculate the enum's numeric type on its own,
    -- using a C++ compiler.  If this is present however, Hoppy will use it, and
    -- additionally validate it against what the C++ compiler thinks, if
    -- validation is enabled (see 'interfaceValidateEnumTypes').
  , enumScoped :: Scoped
    -- ^ Whether the enum is scoped or unscoped.
  , enumValues :: EnumValueMap
    -- ^ The numeric values and names of the enum entires.
  , enumReqs :: Reqs
    -- ^ Requirements for bindings to access this enum.  Currently unused, but
    -- will be in the future.
  , enumAddendum :: Addendum
    -- ^ The enum's addendum.
  , enumValuePrefix :: String
    -- ^ The prefix applied to value names ('enumValues') when determining the
    -- names of values in foreign languages.  This defaults to the external name
    -- of the enum, plus an underscore.
    --
    -- See 'enumSetValuePrefix'.
  , enumUnknownValueEntry :: Maybe EnumEntryWords
    -- ^ A name (a list of words, a la the fields in 'EnumValueMap') for an
    -- optional fallback enum "entry" in generated bindings for holding unknown
    -- values.  See 'enumUnknownValueEntryDefault'.
    --
    -- When this is a @Just@, then the generated foreign binding gets an extra
    -- entry that takes an argument holding an arbitrary numeric value (an extra
    -- data constructor in Haskell), and this value is used whenever an unknown
    -- value is seen.
    --
    -- When this is @Nothing@, the enum will not support unknown values.
    -- @toCppEnum@ in the @Foreign.Hoppy.Runtime.CppEnum@ typeclass, as well as
    -- calls or returns from C++ that pass a value not defined in the interface,
    -- will raise an 'error'.
    --
    -- Enums that have this set to @Nothing@ should also have
    -- 'enumHasBitOperations' set to false, to avoid potential errors at
    -- runtime; see that function's documentation.
    --
    -- The 'enumValuePrefix' applies to this name, just as it does to other enum
    -- entries.
  , enumHasBitOperations :: Bool
    -- ^ Whether generated bindings should support bitwise operations on the
    -- enum.  This defaults to true.
    --
    -- It is not recommended to disable the unknown value entry
    -- ('enumUnknownValueEntry') while having this be true, because any
    -- computation involving enum values not explicitly defined will cause a
    -- runtime error.  This includes undefined combinations of defined values.
  }

instance Eq CppEnum where
  (==) = (==) `on` enumExtName

instance Show CppEnum where
  show e = concat ["<Enum ", show (enumExtName e), " ", show (enumIdentifier e), ">"]

instance Exportable CppEnum where
  sayExportCpp _ _ = return ()  -- Nothing to do for the C++ side of an enum.

  sayExportHaskell = sayHsExport

  getExportEnumInfo e =
    Just EnumInfo
    { enumInfoExtName = enumExtName e
    , enumInfoIdentifier = enumIdentifier e
    , enumInfoNumericType = enumNumericType e
    , enumInfoReqs = enumReqs e
    , enumInfoScoped = enumScoped e
    , enumInfoValues = enumValues e
    }

instance HasExtNames CppEnum where
  getPrimaryExtName = enumExtName

instance HasReqs CppEnum where
  getReqs = enumReqs
  setReqs reqs e = e { enumReqs = reqs }

instance HasAddendum CppEnum where
  getAddendum = enumAddendum
  setAddendum addendum e = e { enumAddendum = addendum }

-- | Sets an explicit numeric type for the enum.  See 'enumNumericType'.
enumSetNumericType :: Maybe Type -> CppEnum -> CppEnum
enumSetNumericType maybeType enum = enum { enumNumericType = maybeType }

-- | The default value for 'enumUnknownValueEntry'.  This is @[\"Unknown\"]@.
enumUnknownValueEntryDefault :: EnumEntryWords
enumUnknownValueEntryDefault = ["Unknown"]

-- | Creates a binding for a C++ enum.
--
-- The numeric values of each of the enum's entries must be specified manually
-- using this function.  To have these determined automatically, instead use
-- 'makeAutoEnum'.
makeEnum ::
  Identifier  -- ^ 'enumIdentifier'
  -> Maybe ExtName
  -- ^ An optional external name; will be automatically derived from
  -- the identifier if absent.
  -> [(Integer, EnumEntryWords)]
  -- ^ A list of (numeric value, symbolic name) pairs describing enum entries to
  -- generate bindings for.  Each symbolic name is a list of words, which will
  -- be combined into a single identifier of appropriate naming style for the
  -- target language (title case, for Haskell) with 'enumValuePrefix' prepended.
  -> CppEnum
makeEnum identifier maybeExtName entries =
  let extName = extNameOrIdentifier identifier maybeExtName
  in CppEnum
     extName
     identifier
     Nothing
     Unscoped  -- Assume this is an unscoped enum.
     (let entries' = for entries $ \(num, words') -> (words', EnumValueManual num)
          entryNames = map fst entries'
      in EnumValueMap
         { enumValueMapNames = entryNames
         , enumValueMapForeignNames = plainMap $ M.fromList $ map (id &&& id) entryNames
         , enumValueMapValues = M.fromList entries'
         })
     mempty
     mempty
     (fromExtName extName ++ "_")
     (Just enumUnknownValueEntryDefault)
     True

-- | Creates a binding for a C++ enum.
--
-- An enum created using this function will determine its entries' numeric
-- values automatically when the generator is run, by compiling a temporary,
-- autogenerated C++ helper program.
--
-- This helper program needs to be able to access the C++ declaration of the
-- enum.  In addition to any 'includeStd' or 'includeLocal' requirements added
-- to the enum for the generated C++ bindings to use, the /interface's compiler/
-- ('interfaceCompiler') will need to be able to use these includes to access
-- the enum from C++ file built in a temporary directory.  To add @-I@ arguments
-- or otherwise change the compiler, you can reconfigure the interface:
--
-- @
-- myInterface =
--   'interfaceSetCompiler' (prependArguments [\"-I\" ++ pathToIncludes] defaultCompiler) $
--   'interface' ...
-- @
--
-- See "Foreign.Hoppy.Generator.Compiler".
makeAutoEnum ::
  IsAutoEnumValue v
  => Identifier  -- ^ 'enumIdentifier'
  -> Maybe ExtName
  -- ^ An optional external name; will be automatically derived from the
  -- identifier if absent.
  -> Scoped
  -- ^ Is the enum scoped (@enum class@ or @enum struct@)?  That is, are its
  -- entries scoped underneath its name, rather than being at the same level as
  -- its name (as with just @enum@).
  -> [v]
  -- ^ A list of enum entries to calculate and generate bindings for.  See
  -- 'IsAutoEnumValue'.
  -> CppEnum
makeAutoEnum identifier maybeExtName scoped entries =
  let extName = extNameOrIdentifier identifier maybeExtName
  in CppEnum
     extName
     identifier
     Nothing
     scoped
     (let namespaceForValues = case scoped of
            Scoped -> identifier
            Unscoped -> makeIdentifier $ butLast $ identifierParts identifier
          entries' =
            map (fmap (\name -> namespaceForValues `mappend` ident name) .
                 toAutoEnumValue)
            entries
          entryNames = map fst entries'
       in EnumValueMap
          { enumValueMapNames = entryNames
          , enumValueMapForeignNames = plainMap $ M.fromList $ map (id &&& id) entryNames
          , enumValueMapValues = M.map EnumValueAuto $ M.fromList entries'
          })
     mempty
     mempty
     (fromExtName extName ++ "_")
     (Just enumUnknownValueEntryDefault)
     True

-- | Represents a mapping to an automatically evaluated C++ enum entry.
--
-- The @('EnumEntryWords', String)@ instance is the canonical one, with
-- 'toAutoEnumValue' defined as @id@.  The string on the right is the C++ name
-- of the entry, and the list of strings on the left are the words from which to
-- generate foreign bindings' entry names.
--
-- The @String@ instance takes the C++ name of the entry, and splits it into
-- words via 'splitIntoWords'.
class IsAutoEnumValue a where
  toAutoEnumValue :: a -> (EnumEntryWords, String)

instance IsAutoEnumValue (EnumEntryWords, String) where
  toAutoEnumValue = id

instance IsAutoEnumValue String where
  toAutoEnumValue = splitIntoWords &&& id

-- | Adds overrides for some of an enum's entry names, in a specific language.
enumAddEntryNameOverrides :: IsAutoEnumValue v => ForeignLanguage -> [(v, v)] -> CppEnum -> CppEnum
enumAddEntryNameOverrides lang nameOverrides enum = enum { enumValues = enumValues' }
  where enumValues' =
          (enumValues enum)
          { enumValueMapForeignNames =
            addOverrideMap lang overrideMap $ enumValueMapForeignNames $ enumValues enum }
        overrideMap = M.fromList $ map (toEntryName *** toEntryName) nameOverrides
        toEntryName = fst . toAutoEnumValue

-- | Retrieves the name for an enum entry in a specific foreign language.
enumGetOverriddenEntryName :: ForeignLanguage -> CppEnum -> EnumEntryWords -> EnumEntryWords
enumGetOverriddenEntryName lang enum words' =
  case overriddenMapLookup lang words' $ enumValueMapForeignNames $ enumValues enum of
    Just words'' -> words''
    Nothing ->
      error $ "enumGetOverriddenEntryName: Entry with name " ++ show words' ++
      " not found in " ++ show enum ++ "."

-- | Sets the prefix applied to the names of enum values' identifiers in foreign
-- languages.
--
-- See 'enumValuePrefix'.
enumSetValuePrefix :: String -> CppEnum -> CppEnum
enumSetValuePrefix prefix enum = enum { enumValuePrefix = prefix }

-- | Sets the entry name (a list of words, a la the fields in 'EnumValueMap')
-- for the fallback enum entry that holds unknown values.
--
-- Set 'enumUnknownValueEntry', 'enumSetNoUnknownValueEntry'.
enumSetUnknownValueEntry :: IsEnumUnknownValueEntry a => a -> CppEnum -> CppEnum
enumSetUnknownValueEntry name enum =
  enum { enumUnknownValueEntry = Just $ toEnumUnknownValueEntry name }

-- | Sets an enum to have no unknown value entry.
--
-- Set 'enumUnknownValueEntry', 'enumSetUnknownValueEntry'.
enumSetNoUnknownValueEntry :: CppEnum -> CppEnum
enumSetNoUnknownValueEntry enum =
  enum { enumUnknownValueEntry = Nothing }

-- | Values that can be used as a name for an enum's unknown value entry.  See
-- 'enumUnknownValueEntry'.
class IsEnumUnknownValueEntry a where
  -- | Converts a value to a list of words to use for an enum's unknown entry
  -- name.
  toEnumUnknownValueEntry :: a -> EnumEntryWords

instance IsEnumUnknownValueEntry EnumEntryWords where
  toEnumUnknownValueEntry = id

instance IsEnumUnknownValueEntry String where
  toEnumUnknownValueEntry = splitIntoWords

-- | Sets whether generated bindings will support bitwise operations on the
-- enum.
--
-- See 'enumHasBitOperations'.
enumSetHasBitOperations :: Bool -> CppEnum -> CppEnum
enumSetHasBitOperations b enum = enum { enumHasBitOperations = b }

makeConversion :: CppEnum -> ConversionSpec
makeConversion e =
  (makeConversionSpec (show e) cpp)
  { conversionSpecHaskell = Just hs }
  where cpp =
          makeConversionSpecCpp (LC.renderIdentifier $ enumIdentifier e)
                                (return $ enumReqs e)

        hs =
          makeConversionSpecHaskell
            (HsTyCon . UnQual . HsIdent <$> toHsEnumTypeName e)
            (Just $ do evaluatedData <- hsGetEvaluatedEnumData $ enumExtName e
                       LH.cppTypeToHsTypeAndUse LH.HsCSide $
                         numType $ evaluatedEnumNumericType evaluatedData)
            (CustomConversion $ do
               LH.addImports $ mconcat [hsImport1 "Prelude" "(.)",
                                        hsImportForPrelude,
                                        hsImportForRuntime]
               LH.sayLn "HoppyP.return . HoppyFHR.fromCppEnum")
            (CustomConversion $ do
               LH.addImports $ mconcat [hsImport1 "Prelude" "(.)",
                                        hsImportForPrelude,
                                        hsImportForRuntime]
               LH.sayLn "HoppyP.return . HoppyFHR.toCppEnum")

-- | Constructs a type value for an enum.
enumT :: CppEnum -> Type
-- (Keep docs in sync with hs-boot.)
enumT = manualT . makeConversion

sayHsExport :: LH.SayExportMode -> CppEnum -> LH.Generator ()
sayHsExport mode enum =
  LH.withErrorContext ("generating enum " ++ show (enumExtName enum)) $
  case mode of
    -- Nothing to import from the C++ side of an enum.
    LH.SayExportForeignImports -> return ()

    LH.SayExportDecls -> do
      hsTypeName <- toHsEnumTypeName enum
      evaluatedData <- hsGetEvaluatedEnumData $ enumExtName enum
      numericType <- LH.cppTypeToHsTypeAndUse LH.HsCSide $
        numType $ evaluatedEnumNumericType evaluatedData
      let evaluatedValueMap = evaluatedEnumValueMap evaluatedData
      evaluatedValues <- forM (enumValueMapNames $ enumValues enum) $ \name ->
        case M.lookup name evaluatedValueMap of
          Just value -> return (name, value)
          Nothing -> throwError $ "Couldn't find evaluated value for " ++ show name
      values :: [(Integer, String)] <- forM evaluatedValues $ \(entryName, value) -> do
        let entryName' = enumGetOverriddenEntryName Haskell enum entryName
        ctorName <- toHsEnumCtorName enum entryName'
        return (value, ctorName)
      maybeUnknownValueCtorName <- forM (enumUnknownValueEntry enum) $ toHsEnumCtorName enum
      LH.addImports $ mconcat [hsImport1 "Prelude" "(==)",
                               hsImportForPrelude,
                               hsImportForRuntime]

      -- Print out the data declaration.
      LH.ln
      LH.addExport' hsTypeName
      LH.saysLn ["data ", hsTypeName, " ="]
      LH.indent $ do
        forM_ (zip (False:repeat True) values) $ \(cont, (_, hsCtorName)) ->
          LH.saysLn [if cont then "| " else "", hsCtorName]
        -- Only print an unknown value ctor if one has been requested.
        forM_ maybeUnknownValueCtorName $ \unknownValueCtorName ->
          LH.saysLn ["| ", unknownValueCtorName, " (", LH.prettyPrint numericType, ")"]
        LH.sayLn "deriving (HoppyP.Show)"

      -- Print out the (runtime) CppEnum instance.
      LH.ln
      LH.saysLn ["instance HoppyFHR.CppEnum (", LH.prettyPrint numericType, ") ", hsTypeName,
                 " where"]
      LH.indent $ do
        forM_ values $ \(num, hsCtorName) ->
          LH.saysLn ["fromCppEnum ", hsCtorName, " = ", show num]
        forM_ maybeUnknownValueCtorName $ \unknownValueCtorName ->
          LH.saysLn ["fromCppEnum (", unknownValueCtorName, " n) = n"]
        LH.ln
        -- We pass the values list through a map here to only keep the first
        -- constructor mapped to each numeric value, otherwise we'd write
        -- duplicate cases.
        forM_ (M.toList $ M.fromListWith const values) $ \(num, hsCtorName) ->
          LH.saysLn ["toCppEnum (", show num, ") = ", hsCtorName]
        case maybeUnknownValueCtorName of
          Just unknownValueCtorName -> LH.saysLn ["toCppEnum n = ", unknownValueCtorName, " n"]
          Nothing -> do
            LH.addImports $ hsImports "Prelude" ["($)", "(++)"]
            LH.saysLn ["toCppEnum n' = HoppyP.error $ ",
                       show (concat ["Unknown ", hsTypeName, " numeric value: "]),
                       " ++ HoppyP.show n'"]

      -- Print out Eq and Ord instances.
      LH.ln
      LH.saysLn ["instance HoppyP.Eq ", hsTypeName, " where"]
      LH.indent $
        LH.sayLn "x == y = HoppyFHR.fromCppEnum x == HoppyFHR.fromCppEnum y"
      LH.ln
      LH.saysLn ["instance HoppyP.Ord ", hsTypeName, " where"]
      LH.indent $
        LH.sayLn "compare x y = HoppyP.compare (HoppyFHR.fromCppEnum x) (HoppyFHR.fromCppEnum y)"

      when (enumHasBitOperations enum) $ do
        LH.addImports $ mconcat [hsImports "Prelude" ["($)", "(.)"],
                                 hsImports "Data.Bits" ["(.&.)", "(.|.)"],
                                 hsImportForBits]
        LH.saysLn ["instance HoppyDB.Bits ", hsTypeName, " where"]
        LH.indent $ do
          let fun1 f =
                LH.saysLn [f, " x = HoppyFHR.toCppEnum $ HoppyDB.",
                           f, " $ HoppyFHR.fromCppEnum x"]
              fun1Int f =
                LH.saysLn [f, " x i = HoppyFHR.toCppEnum $ HoppyDB.",
                           f, " (HoppyFHR.fromCppEnum x) i"]
              fun2 f =
                LH.saysLn [f, " x y = HoppyFHR.toCppEnum $ HoppyDB.",
                           f, " (HoppyFHR.fromCppEnum x) (HoppyFHR.fromCppEnum y)"]
              op2 op =
                LH.saysLn ["x ", op, " y = HoppyFHR.toCppEnum ",
                           "(HoppyFHR.fromCppEnum x ", op, " HoppyFHR.fromCppEnum y)"]
          op2 ".&."
          op2 ".|."
          fun2 "xor"
          fun1 "complement"
          fun1Int "shift"
          fun1Int "rotate"
          LH.sayLn "bitSize x = case HoppyDB.bitSizeMaybe x of"
          LH.indent $ do
            LH.sayLn "  HoppyP.Just n -> n"
            -- Same error message as the prelude here:
            LH.sayLn "  HoppyP.Nothing -> HoppyP.error \"bitSize is undefined\""
          LH.sayLn "bitSizeMaybe = HoppyDB.bitSizeMaybe . HoppyFHR.fromCppEnum"
          LH.sayLn "isSigned = HoppyDB.isSigned . HoppyFHR.fromCppEnum"
          LH.sayLn "testBit x i = HoppyDB.testBit (HoppyFHR.fromCppEnum x) i"
          LH.sayLn "bit = HoppyFHR.toCppEnum . HoppyDB.bit"
          LH.sayLn "popCount = HoppyDB.popCount . HoppyFHR.fromCppEnum"

    LH.SayExportBoot -> do
      hsTypeName <- toHsEnumTypeName enum
      evaluatedData <- hsGetEvaluatedEnumData $ enumExtName enum
      numericType <- LH.cppTypeToHsTypeAndUse LH.HsCSide $
        numType $ evaluatedEnumNumericType evaluatedData
      LH.addImports $ mconcat [hsImportForPrelude, hsImportForRuntime]
      LH.addExport hsTypeName
      LH.ln
      LH.saysLn ["data ", hsTypeName]
      LH.saysLn ["instance HoppyFHR.CppEnum (", LH.prettyPrint numericType, ") ", hsTypeName]
      LH.saysLn ["instance HoppyP.Eq ", hsTypeName]
      LH.saysLn ["instance HoppyP.Ord ", hsTypeName]
      LH.saysLn ["instance HoppyP.Show ", hsTypeName]
      when (enumHasBitOperations enum) $ do
        LH.addImports hsImportForBits
        LH.saysLn ["instance HoppyDB.Bits ", hsTypeName]

-- | Reads evaluated data for the named enum from the C++ generator environment.
cppGetEvaluatedEnumData :: HasCallStack => ExtName -> LC.Generator EvaluatedEnumData
cppGetEvaluatedEnumData extName = do
  computed <- LC.askComputedInterfaceData
  return $ getEvaluatedEnumData computed extName

-- | Reads evaluated data for the named enum from the Haskell generator
-- environment.
hsGetEvaluatedEnumData :: HasCallStack => ExtName -> LH.Generator EvaluatedEnumData
hsGetEvaluatedEnumData extName = do
  computed <- LH.askComputedInterfaceData
  return $ getEvaluatedEnumData computed extName

-- | Returns the Haskell name for an enum.
--
-- TODO Clarify, and split into type and data ctor names.
toHsEnumTypeName :: CppEnum -> LH.Generator String
toHsEnumTypeName enum =
  LH.inFunction "toHsEnumTypeName" $
  LH.addExtNameModule (enumExtName enum) $ toHsEnumTypeName' enum

-- | Pure version of 'toHsEnumTypeName' that doesn't create a qualified name.
toHsEnumTypeName' :: CppEnum -> String
toHsEnumTypeName' = LH.toHsTypeName' Nonconst . enumExtName

-- | Constructs the data constructor name for a value in an enum.  Like C++ and
-- unlike say Java, Haskell enum values aren't in a separate enum-specific
-- namespace, so we prepend the enum name to the value name to get the data
-- constructor name.  The value name is a list of words.
toHsEnumCtorName :: CppEnum -> EnumEntryWords -> LH.Generator String
toHsEnumCtorName enum words' =
  LH.inFunction "toHsEnumCtorName" $
  LH.addExtNameModule (enumExtName enum) $ toHsEnumCtorName' enum words'

-- | Pure version of 'toHsEnumCtorName' that doesn't create a qualified name.
toHsEnumCtorName' :: CppEnum -> EnumEntryWords -> String
toHsEnumCtorName' enum words' =
  concat $ enumValuePrefix enum : map capitalize words'