Handle remaining enums (#36)

This handles the remaining enum types in the headers that aren't in the enums section.

It also automatically handles adding all the enumerations to the inline-c context types table, and a small improvement to the display of parse error messages.
This commit is contained in:
sheaf 2021-02-08 00:07:14 +01:00 committed by GitHub
parent 921aefdd69
commit d4aec47f4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 47 deletions

View File

@ -175,10 +175,14 @@ library dear-imgui-generator
build-depends: build-depends:
template-haskell template-haskell
>= 2.15 && < 2.19 >= 2.15 && < 2.19
, containers
^>= 0.6.2.1
, directory , directory
>= 1.3 && < 1.4 >= 1.3 && < 1.4
, filepath , filepath
>= 1.4 && < 1.5 >= 1.4 && < 1.5
, inline-c
>= 0.9.0.0 && < 0.10
, megaparsec , megaparsec
>= 9.0 && < 9.1 >= 9.0 && < 9.1
, parser-combinators , parser-combinators

View File

@ -6,21 +6,33 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
module DearImGui.Generator module DearImGui.Generator
( declareEnumerations ) ( declareEnumerations, enumerationsTypesTable )
where where
-- base -- base
import Control.Arrow
( second )
import Data.Coerce import Data.Coerce
( coerce ) ( coerce )
import Data.Bits import Data.Bits
( Bits ) ( Bits )
import Data.Foldable import Data.Foldable
( toList ) ( toList )
import qualified Data.List.NonEmpty as NonEmpty
( head )
import Data.String
( fromString )
import Data.Traversable import Data.Traversable
( for ) ( for )
import Foreign.Storable import Foreign.Storable
( Storable ) ( Storable )
-- containers
import Data.Map.Strict
( Map )
import qualified Data.Map.Strict as Map
( fromList )
-- directory -- directory
import System.Directory import System.Directory
( canonicalizePath ) ( canonicalizePath )
@ -29,9 +41,12 @@ import System.Directory
import System.FilePath import System.FilePath
( takeDirectory ) ( takeDirectory )
-- inline-c
import qualified Language.C.Types as InlineC
( TypeSpecifier(TypeName) )
-- megaparsec -- megaparsec
import qualified Text.Megaparsec as Megaparsec import qualified Text.Megaparsec as Megaparsec
( ParseErrorBundle(bundleErrors), parse, parseErrorPretty )
-- template-haskell -- template-haskell
import qualified Language.Haskell.TH as TH import qualified Language.Haskell.TH as TH
@ -47,53 +62,68 @@ import qualified Data.Text.IO as Text
import qualified DearImGui.Generator.Parser as Parser import qualified DearImGui.Generator.Parser as Parser
( headers ) ( headers )
import DearImGui.Generator.Tokeniser import DearImGui.Generator.Tokeniser
( tokenise ) ( Tok, tokenise )
import DearImGui.Generator.Types import DearImGui.Generator.Types
( Comment(..), Enumeration(..), Headers(..) ) ( Comment(..), Enumeration(..), Headers(..)
, generateNames
)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Obtaining parsed header data. -- Obtaining parsed header data.
headers :: Headers headers :: Headers ( TH.Name, TH.Name )
headers = $( do headers = $( do
currentPath <- TH.loc_filename <$> TH.location currentPath <- TH.loc_filename <$> TH.location
TH.lift =<< TH.runIO do basicHeaders <- TH.runIO do
headersPath <- canonicalizePath ( takeDirectory currentPath <> "/../../imgui/imgui.h" ) headersPath <- canonicalizePath ( takeDirectory currentPath <> "/../../imgui/imgui.h" )
headersSource <- Text.readFile headersPath headersSource <- Text.readFile headersPath
tokens <- case tokenise headersSource of tokens <- case tokenise headersSource of
Left err -> error ( "Couldn't tokenise Dear ImGui headers:\n\n" <> show err ) Left err -> error ( "Couldn't tokenise Dear ImGui headers:\n\n" <> show err )
Right toks -> pure toks Right toks -> pure toks
case Megaparsec.parse Parser.headers "" tokens of case Megaparsec.parse Parser.headers "" tokens of
Left err -> error $ Left err -> do
"Couldn't parse Dear ImGui headers:\n\n" <> let
( unlines ( map Megaparsec.parseErrorPretty . toList $ Megaparsec.bundleErrors err ) ) 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 Right res -> pure res
TH.lift =<< generateNames basicHeaders
) )
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Generating TH splices. -- 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 :: TH.Name -> TH.Name -> TH.Q [ TH.Dec ]
declareEnumerations finiteEnumName countName = do declareEnumerations finiteEnumName countName = do
concat <$> mapM ( declareEnumeration finiteEnumName countName ) ( enums headers ) concat <$> mapM ( declareEnumeration finiteEnumName countName ) ( enums headers )
declareEnumeration :: TH.Name -> TH.Name -> Enumeration -> TH.Q [ TH.Dec ] declareEnumeration :: TH.Name -> TH.Name -> Enumeration ( TH.Name, TH.Name ) -> TH.Q [ TH.Dec ]
declareEnumeration finiteEnumName countName ( Enumeration {..} ) = do declareEnumeration finiteEnumName countName ( Enumeration {..} ) = do
let let
enumNameStr :: String tyName, conName :: TH.Name
enumNameStr = Text.unpack enumName ( tyName, conName ) = enumTypeName
isFlagEnum :: Bool isFlagEnum :: Bool
isFlagEnum = "Flags" `Text.isInfixOf` enumName isFlagEnum = "Flags" `Text.isInfixOf` enumName
tyName <- TH.newName enumNameStr
conName <- TH.newName enumNameStr
let
newtypeCon :: TH.Q TH.Con newtypeCon :: TH.Q TH.Con
newtypeCon = newtypeCon =
TH.normalC conName TH.normalC conName
[ TH.bangType [ TH.bangType
( TH.bang TH.noSourceUnpackedness TH.noSourceStrictness ) ( TH.bang TH.noSourceUnpackedness TH.noSourceStrictness )
( TH.conT enumType ) ( TH.conT underlyingType )
] ]
classes :: [ TH.Q TH.Type ] classes :: [ TH.Q TH.Type ]
classes classes
@ -103,6 +133,7 @@ declareEnumeration finiteEnumName countName ( Enumeration {..} ) = do
= map TH.conT [ ''Eq, ''Ord, ''Storable ] = map TH.conT [ ''Eq, ''Ord, ''Storable ]
derivClause :: TH.Q TH.DerivClause derivClause :: TH.Q TH.DerivClause
derivClause = TH.derivClause ( Just TH.NewtypeStrategy ) classes derivClause = TH.derivClause ( Just TH.NewtypeStrategy ) classes
newtypeDecl <- newtypeDecl <-
#if MIN_VERSION_base(4,16,0) #if MIN_VERSION_base(4,16,0)
( if null docs ( if null docs

View File

@ -111,27 +111,44 @@ instance ShowErrorComponent CustomParseError where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Parsing headers. -- Parsing headers.
headers :: MonadParsec CustomParseError [Tok] m => m Headers headers :: MonadParsec CustomParseError [Tok] m => m ( Headers () )
headers = do headers = do
_ <- skipManyTill anySingle ( namedSection "Header mess" ) _ <- skipManyTill anySingle ( namedSection "Header mess" )
_ <- skipManyTill anySingle ( namedSection "Forward declarations" ) _ <- skipManyTill anySingle ( namedSection "Forward declarations" )
( _structNames, enumNamesAndTypes ) <- forwardDeclarations ( _structNames, enumNamesAndTypes ) <- forwardDeclarations
_ <- skipManyTill anySingle ( namedSection "Dear ImGui end-user API functions" ) _ <- skipManyTill anySingle ( namedSection "Dear ImGui end-user API functions" )
_ <- skipManyTill anySingle ( namedSection "Flags & Enumerations" ) _ <- skipManyTill anySingle ( namedSection "Flags & Enumerations" )
( _defines, enums ) <- partitionEithers <$> ( _defines, basicEnums ) <- partitionEithers <$>
manyTill manyTill
( ( Left <$> try ignoreDefine ) ( ( Left <$> try ignoreDefine )
<|> ( Right <$> enumeration enumNamesAndTypes ) <|> ( Right <$> enumeration enumNamesAndTypes )
) )
( namedSection "Helpers: Memory allocations macros, ImVector<>" ) ( namedSection "Helpers: Memory allocations macros, ImVector<>" )
_ <- skipManyTill anySingle ( namedSection "ImGuiStyle" )
_ <- skipManyTill anySingle ( namedSection "ImGuiIO" )
_ <- skipManyTill anySingle ( namedSection "Misc data structures" )
_ <- skipManyTill anySingle ( namedSection "Obsolete functions" )
_ <- skipManyTill anySingle ( namedSection "Helpers" )
_ <- skipManyTill anySingle ( namedSection "Drawing API" )
_ <- skipManyTill anySingle ( namedSection "Font API" )
_ <- skipManyTill anySingle ( namedSection "ImGuiStyle" )
_ <- skipManyTill anySingle ( namedSection "ImGuiIO" )
_ <- skipManyTill anySingle ( namedSection "Misc data structures" )
_ <- skipManyTill anySingle ( namedSection "Obsolete functions" )
_ <- skipManyTill anySingle ( namedSection "Helpers" )
_ <- skipManyTill anySingle ( namedSection "Drawing API" )
skipManyTill anySingle ( try . lookAhead $ many comment *> keyword "enum" )
drawingEnums <- many ( enumeration enumNamesAndTypes )
_ <- skipManyTill anySingle ( namedSection "Font API" )
skipManyTill anySingle ( try . lookAhead $ many comment *> keyword "enum" )
fontEnums <- many ( enumeration enumNamesAndTypes )
let
enums :: [ Enumeration () ]
enums = basicEnums <> drawingEnums <> fontEnums
pure ( Headers { enums } ) pure ( Headers { enums } )
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -151,7 +168,7 @@ forwardDeclarations = do
_ <- many comment _ <- many comment
enums <- many do enums <- many do
keyword "typedef" keyword "typedef"
ty <- enumTypeName ty <- cTypeName
enumName <- identifier enumName <- identifier
reservedSymbol ';' reservedSymbol ';'
doc <- commentText <$> comment doc <- commentText <$> comment
@ -159,8 +176,8 @@ forwardDeclarations = do
-- Stopping after simple structs and enums for now. -- Stopping after simple structs and enums for now.
pure ( HashMap.fromList structs, HashMap.fromList enums ) pure ( HashMap.fromList structs, HashMap.fromList enums )
enumTypeName :: MonadParsec e [Tok] m => m TH.Name cTypeName :: MonadParsec e [Tok] m => m TH.Name
enumTypeName = keyword "int" $> ''CInt cTypeName = keyword "int" $> ''CInt
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Parsing enumerations. -- Parsing enumerations.
@ -172,15 +189,19 @@ data EnumState = EnumState
, hasExplicitCount :: Bool , hasExplicitCount :: Bool
} }
enumeration :: MonadParsec CustomParseError [Tok] m => HashMap Text ( TH.Name, Comment ) -> m Enumeration enumeration :: MonadParsec CustomParseError [Tok] m => HashMap Text ( TH.Name, Comment ) -> m ( Enumeration () )
enumeration enumNamesAndTypes = do enumeration enumNamesAndTypes = do
inlineDocs <- many comment inlineDocs <- try do
keyword "enum" inlineDocs <- many comment
keyword "enum"
pure inlineDocs
fullEnumName <- identifier fullEnumName <- identifier
let let
enumName :: Text enumName :: Text
enumName = Text.dropWhileEnd ( == '_' ) fullEnumName enumName = Text.dropWhileEnd ( == '_' ) fullEnumName
( enumType, forwardDoc ) <- case HashMap.lookup enumName enumNamesAndTypes of enumTypeName :: ()
enumTypeName = ()
( underlyingType, forwardDoc ) <- case HashMap.lookup enumName enumNamesAndTypes of
Just res -> pure res Just res -> pure res
Nothing -> customFailure ( MissingForwardDeclaration { enumName } ) Nothing -> customFailure ( MissingForwardDeclaration { enumName } )
let let

View File

@ -1,19 +1,27 @@
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveLift #-} {-# LANGUAGE DeriveLift #-}
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskellQuotes #-} {-# LANGUAGE TemplateHaskellQuotes #-}
module DearImGui.Generator.Types where module DearImGui.Generator.Types where
-- base
import Data.Traversable
( for )
-- template-haskell -- template-haskell
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Syntax as TH import qualified Language.Haskell.TH.Syntax as TH
( Lift(..), Name(..) )
-- text -- text
import Data.Text import Data.Text
( Text ) ( Text )
import qualified Data.Text as Text
( unpack )
-- th-lift -- th-lift
import Language.Haskell.TH.Lift import Language.Haskell.TH.Lift
@ -25,18 +33,30 @@ newtype Comment = CommentText { commentText :: Text }
deriving stock ( Show, TH.Lift ) deriving stock ( Show, TH.Lift )
deriving newtype ( Eq, Ord ) deriving newtype ( Eq, Ord )
data Enumeration data Enumeration typeName
= Enumeration = Enumeration
{ docs :: ![Comment] { docs :: ![Comment]
, enumName :: !Text , enumName :: !Text
, enumTypeName :: !typeName
, enumSize :: !Integer , enumSize :: !Integer
, enumType :: !TH.Name , underlyingType :: !TH.Name
, hasExplicitCount :: !Bool , hasExplicitCount :: !Bool
, patterns :: [ ( Text, Integer, Comment ) ] , patterns :: [ ( Text, Integer, Comment ) ]
} }
deriving stock ( Show, TH.Lift ) deriving stock ( Show, TH.Lift )
data Headers data Headers typeName
= Headers = Headers
{ enums :: [ Enumeration ] } { enums :: [ Enumeration typeName ] }
deriving stock ( Show, TH.Lift ) deriving stock ( Show, TH.Lift )
generateNames :: Headers () -> TH.Q ( Headers ( TH.Name, TH.Name ) )
generateNames ( Headers { enums = basicEnums } ) = do
enums <- for basicEnums \ enum@( Enumeration { enumName } ) -> do
let
enumNameStr :: String
enumNameStr = Text.unpack enumName
tyName <- TH.newName enumNameStr
conName <- TH.newName enumNameStr
pure $ enum { enumTypeName = ( tyName, conName ) }
pure $ Headers { enums }

View File

@ -18,21 +18,19 @@ import Language.C.Types
( pattern TypeName ) ( pattern TypeName )
-- dear-imgui -- dear-imgui
import DearImGui.Enums
import DearImGui.Structs import DearImGui.Structs
-- dear-imgui-generator
import DearImGui.Generator
( enumerationsTypesTable )
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
imguiContext :: Context imguiContext :: Context
imguiContext = mempty imguiContext = mempty
{ ctxTypesTable = Map.fromList { ctxTypesTable = enumerationsTypesTable <>
[ ( TypeName "ImGuiCol" , [t| ImGuiCol |] ) Map.fromList
, ( TypeName "ImGuiCond", [t| ImGuiCond |] ) [ ( TypeName "ImVec2", [t| ImVec2 |] )
, ( TypeName "ImGuiDir" , [t| ImGuiDir |] )
, ( TypeName "ImGuiStyleVar" , [t| ImGuiStyleVar |] )
, ( TypeName "ImGuiTabBarFlags" , [t| ImGuiTabBarFlags |] )
, ( TypeName "ImGuiTabItemFlags", [t| ImGuiTabItemFlags |] )
, ( TypeName "ImVec2", [t| ImVec2 |] )
, ( TypeName "ImVec3", [t| ImVec3 |] ) , ( TypeName "ImVec3", [t| ImVec3 |] )
, ( TypeName "ImVec4", [t| ImVec4 |] ) , ( TypeName "ImVec4", [t| ImVec4 |] )
] ]