{-# LANGUAGE BlockArguments #-} {-# LANGUAGE CPP #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} module DearImGui.Generator ( declareEnumerations, enumerationsTypesTable ) where -- base import Control.Arrow ( second ) import Data.Bits ( Bits ) import Data.Foldable ( toList ) import qualified Data.List.NonEmpty as NonEmpty ( head ) import Data.String ( fromString ) import Data.Traversable ( for ) import Foreign.Storable ( Storable ) #if MIN_VERSION_template_haskell(2,18,0) import Data.Coerce ( coerce ) #endif -- containers import Data.Map.Strict ( Map ) import qualified Data.Map.Strict as Map ( fromList ) -- directory import System.Directory ( canonicalizePath ) -- filepath import System.FilePath ( takeDirectory ) -- inline-c import qualified Language.C.Types as InlineC ( TypeSpecifier(TypeName) ) -- megaparsec import qualified Text.Megaparsec as Megaparsec -- template-haskell import qualified Language.Haskell.TH as TH import qualified Language.Haskell.TH.Syntax as TH -- text import qualified Data.Text as Text import qualified Data.Text.IO as Text ( readFile ) -- dear-imgui-generator import qualified DearImGui.Generator.Parser as Parser ( headers ) import DearImGui.Generator.Tokeniser ( Tok, tokenise ) import DearImGui.Generator.Types ( Comment(..), Enumeration(..), Headers(..) , generateNames ) -------------------------------------------------------------------------------- -- Obtaining parsed header data. headers :: Headers ( TH.Name, TH.Name ) headers = $( do currentPath <- TH.loc_filename <$> TH.location basicHeaders <- TH.runIO do headersPath <- canonicalizePath ( takeDirectory currentPath <> "/../../imgui/imgui.h" ) headersSource <- Text.readFile headersPath tokens <- case tokenise headersSource of Left err -> error ( "Couldn't tokenise Dear ImGui headers:\n\n" <> show err ) Right toks -> pure toks case Megaparsec.parse Parser.headers "" tokens of Left err -> do let errorPos :: Int errorPos = Megaparsec.errorOffset . NonEmpty.head $ Megaparsec.bundleErrors err prev, rest :: [ Tok ] ( prev, rest ) = second ( take 15 ) . splitAt 5 . drop ( errorPos - 5 ) $ tokens error $ "Couldn't parse Dear ImGui headers:\n\n" <> ( unlines ( map Megaparsec.parseErrorPretty . toList $ Megaparsec.bundleErrors err ) ) <> "\n" <> ( unlines ( map show prev ) <> "\n\n" <> unlines ( map show rest ) ) Right res -> pure res TH.lift $ generateNames basicHeaders ) -------------------------------------------------------------------------------- -- Generating TH splices. enumerationsTypesTable :: Map InlineC.TypeSpecifier ( TH.Q TH.Type ) enumerationsTypesTable = Map.fromList . map mkTypePair $ enums headers where mkTypePair :: Enumeration ( TH.Name, TH.Name ) -> ( InlineC.TypeSpecifier, TH.Q TH.Type ) mkTypePair ( Enumeration { enumName, enumTypeName } ) = ( InlineC.TypeName $ fromString ( Text.unpack enumName ) , TH.conT ( fst $ enumTypeName ) ) declareEnumerations :: TH.Name -> TH.Name -> TH.Q [ TH.Dec ] declareEnumerations finiteEnumName countName = do concat <$> mapM ( declareEnumeration finiteEnumName countName ) ( enums headers ) declareEnumeration :: TH.Name -> TH.Name -> Enumeration ( TH.Name, TH.Name ) -> TH.Q [ TH.Dec ] declareEnumeration finiteEnumName countName ( Enumeration {..} ) = do let tyName, conName :: TH.Name ( tyName, conName ) = enumTypeName isFlagEnum :: Bool isFlagEnum = "Flags" `Text.isInfixOf` enumName newtypeCon :: TH.Q TH.Con newtypeCon = TH.normalC conName [ TH.bangType ( TH.bang TH.noSourceUnpackedness TH.noSourceStrictness ) ( TH.conT underlyingType ) ] classes :: [ TH.Q TH.Type ] classes | isFlagEnum = map TH.conT [ ''Eq, ''Ord, ''Show, ''Storable, ''Bits ] | otherwise = map TH.conT [ ''Eq, ''Ord, ''Show, ''Storable ] derivClause :: TH.Q TH.DerivClause derivClause = TH.derivClause ( Just TH.NewtypeStrategy ) classes newtypeDecl <- #if MIN_VERSION_template_haskell(2,18,0) ( if null docs then TH.newtypeD else \ ctx name bndrs kd con derivs -> TH.newtypeD_doc ctx name ( fmap pure bndrs ) ( fmap pure kd ) ( con, Nothing, [] ) derivs ( Just . Text.unpack . Text.unlines . coerce $ docs ) ) #else TH.newtypeD #endif ( pure [] ) tyName [] Nothing newtypeCon [ derivClause ] mbAddFiniteEnumInst <- if hasExplicitCount then do finiteEnumInst <- TH.instanceD ( pure [] ) ( TH.appT ( TH.conT finiteEnumName ) ( TH.conT tyName ) ) [ TH.tySynInstD ( TH.TySynEqn Nothing <$> TH.appT ( TH.conT countName ) ( TH.conT tyName ) <*> TH.litT ( TH.numTyLit enumSize ) ) ] pure ( finiteEnumInst : ) else pure id synonyms <- for patterns \ ( patternName, patternValue, CommentText _patDoc ) -> do let patNameStr :: String patNameStr = Text.unpack patternName patName <- TH.newName patNameStr patSynSig <- TH.patSynSigD patName ( TH.conT tyName ) pat <- #if MIN_VERSION_template_haskell(2,18,0) ( if Text.null _patDoc then TH.patSynD else \ nm args dir pat -> TH.patSynD_doc nm args dir pat ( Just $ Text.unpack _patDoc ) [] ) #else TH.patSynD #endif patName ( TH.prefixPatSyn [] ) TH.implBidir ( TH.conP conName [ TH.litP $ TH.integerL patternValue ] ) pure ( patSynSig, pat ) pure ( newtypeDecl : mbAddFiniteEnumInst ( unpairs synonyms ) ) unpairs :: [ ( a, a ) ] -> [ a ] unpairs [] = [] unpairs ( ( x, y ) : as ) = x : y : unpairs as