summaryrefslogtreecommitdiff
path: root/protocols/codegen/Wayland/CodeGen
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/codegen/Wayland/CodeGen')
-rw-r--r--protocols/codegen/Wayland/CodeGen/BuildGen.hs46
-rw-r--r--protocols/codegen/Wayland/CodeGen/CodeGen.hs293
-rw-r--r--protocols/codegen/Wayland/CodeGen/Parser.hs39
-rw-r--r--protocols/codegen/Wayland/CodeGen/Types.hs75
-rw-r--r--protocols/codegen/Wayland/CodeGen/Utils.hs88
5 files changed, 505 insertions, 36 deletions
diff --git a/protocols/codegen/Wayland/CodeGen/BuildGen.hs b/protocols/codegen/Wayland/CodeGen/BuildGen.hs
new file mode 100644
index 0000000..42d4b1d
--- /dev/null
+++ b/protocols/codegen/Wayland/CodeGen/BuildGen.hs
@@ -0,0 +1,46 @@
+module Wayland.CodeGen.BuildGen
+ ( generateBuild
+ ) where
+
+import Wayland.CodeGen.Types
+import Wayland.CodeGen.Utils
+
+data ModuleInfo
+ = ModuleInfo { modProtocol :: !Protocol
+ , modInterface :: !InterfaceDesc
+ }
+
+generateBuild :: [Protocol] -> String
+generateBuild protocols = unlines ([ "cabal-version: 3.0"
+ , "name: highway-protocols"
+ , "version: 1.0.0"
+ , "synopsis: Generated bindings for some Wayland protocols."
+ , "build-type: Simple"
+ , ""
+ , "common warnings"
+ , " ghc-options: -Wall -Wno-unused-imports -Wno-dodgy-exports"
+ , ""
+ , "library"
+ , " import: warnings"
+ , " build-depends: base,"
+ , " hashable,"
+ , " bytestring,"
+ , " highway-wayland"
+ , " default-language: Haskell2010" ]
+ ++ exposedModules (collectModules protocols))
+ where exposedModules [] = []
+ exposedModules [m] = [" exposed-modules: " ++ formatModule m]
+ exposedModules (m:ms) = (" exposed-modules: " ++ formatModule m ++ ",") : exposedModules' ms
+ exposedModules' [m] = [" " ++ formatModule m]
+ exposedModules' (m:ms) = (" " ++ formatModule m ++ ",") : exposedModules' ms
+ exposedModules' [] = []
+
+formatModule :: ModuleInfo -> String
+formatModule m = fullModule (modProtocol m) (modInterface m)
+
+collectModules :: [Protocol] -> [ModuleInfo]
+collectModules = concatMap singleProtocol
+ where singleProtocol p = fmap (singleInterface p) (protInterfaces p)
+ singleInterface p i = ModuleInfo { modProtocol = p
+ , modInterface = i
+ }
diff --git a/protocols/codegen/Wayland/CodeGen/CodeGen.hs b/protocols/codegen/Wayland/CodeGen/CodeGen.hs
new file mode 100644
index 0000000..2fea7c6
--- /dev/null
+++ b/protocols/codegen/Wayland/CodeGen/CodeGen.hs
@@ -0,0 +1,293 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Wayland.CodeGen.CodeGen
+ ( generateInterface
+ , generateInterfaceBoot
+ ) where
+
+-- TODO: Documentation
+
+import Wayland.CodeGen.Types
+import Wayland.CodeGen.Utils
+import Control.Monad.State.Strict (State, runState)
+import Lens.Micro.TH (makeLenses)
+import Data.List (intercalate, sort, isPrefixOf)
+import Lens.Micro.Mtl ((%=))
+import Data.Functor (($>))
+import Control.Monad.Reader (ReaderT (..), asks)
+import Data.Map ((!))
+import qualified Data.Map as M
+
+newtype Declaration = Declaration { declLines :: [String] }
+data Import = Import { imModule :: !String
+ , imSource :: !Bool
+ }
+ deriving (Show, Eq)
+
+data GeneratorState
+ = GeneratorState { _stateImports :: ![Import]
+ , _stateExports :: ![String]
+ , _stateExtensions :: ![String]
+ , _stateDecls :: ![Declaration]
+ }
+
+data GeneratorConfig
+ = GeneratorConfig { genModules :: !FlatModules
+ , genProtocol :: !TypedProtocol
+ , genInterface :: !InterfaceDesc
+ , genTypes :: !TypeInfo
+ }
+
+type Generator = ReaderT GeneratorConfig (State GeneratorState)
+
+makeLenses ''GeneratorState
+
+generateInterface :: FlatModules -> TypedProtocol -> TypedInterfaceDesc -> String
+generateInterface flat p i = generate (GeneratorConfig flat p (fst i) (snd i)) emit
+
+generateInterfaceBoot :: FlatModules -> TypedProtocol -> TypedInterfaceDesc -> String
+generateInterfaceBoot flat p i = generate (GeneratorConfig flat p (fst i) (snd i)) emitBoot
+
+generate :: GeneratorConfig -> Generator () -> String
+generate cfg g = transform $ runState (runReaderT g cfg) initialState
+ where initialState = GeneratorState { _stateImports = []
+ , _stateExports = []
+ , _stateExtensions = []
+ , _stateDecls = []
+ }
+ transform (_, state) = undecls $ [ extensionDecl state, moduleDecl state, importDecl state ] ++ _stateDecls state
+ extensionDecl s = Declaration $ sort $ fmap formatExtension (_stateExtensions s)
+ moduleDecl s = Declaration [ "module " ++ genFullModule cfg ++ " (" ++ intercalate ", " (_stateExports s) ++ ") where" ]
+ importDecl s = Declaration $ sort $ fmap formatImport (_stateImports s)
+ undecls decls = intercalate "\n" $ unlines . declLines <$> filter (not . null . declLines) decls
+ formatExtension e = "{-# LANGUAGE " ++ e ++ " #-}"
+ formatImport i = "import " ++ (if imSource i then "{-# SOURCE #-} " else "") ++ "qualified " ++ imModule i
+
+genFullModule :: GeneratorConfig -> String
+genFullModule cfg = fullModule (genProtocol cfg) (genInterface cfg)
+
+emitDecl :: [String] -> Generator ()
+emitDecl c = stateDecls %= (++ [ Declaration c ])
+
+emitImport :: Import -> Generator ()
+emitImport i = stateImports %= \is -> if i `elem` is then is else i:is
+
+emitExport :: String -> Generator ()
+emitExport e = stateExports %= (e:)
+
+emitExtension :: String -> Generator ()
+emitExtension e = stateExtensions %= (e:)
+
+resolve :: String -> String -> Generator String
+resolve m e = do
+ this <- asks genFullModule
+ if this == m
+ then pure e
+ else emitImport (Import m ("Wayland.Protocols." `isPrefixOf` m)) $> m ++ "." ++ e
+
+resolveInterface :: Maybe String -> String -> Generator String
+resolveInterface (Just name) e = do modules <- asks genModules
+ let (p, i) = modules ! name
+ resolve (fullModule p (fst i)) e
+resolveInterface Nothing e = pure e
+
+resolveEnum :: String -> Generator String
+resolveEnum e = resolveQualified $ break (== '.') e
+ where resolveQualified :: (String, String) -> Generator String
+ resolveQualified (e', []) = do types <- asks genTypes
+ pure $ typeEnums types ! e'
+ resolveQualified (i, _:e') = do modules <- asks genModules
+ resolveInterface (Just i) $ typeEnums (snd (snd (modules ! i))) ! e'
+
+resolveArgument :: ArgumentDesc -> Generator String
+resolveArgument a = case argType a of
+ ArgumentInt -> maybe (pure "Int") resolveEnum (argEnum a)
+ ArgumentUInt -> maybe (pure "Word") resolveEnum (argEnum a)
+ ArgumentFixed -> pure "Float"
+ ArgumentArray -> resolve "Data.ByteString" "ByteString"
+ ArgumentFD -> resolve "System.Posix.Types" "Fd"
+ ArgumentString -> pure $ if argAllowNull a then "(Maybe String)" else "String"
+ ArgumentNewID -> resolveInterface (argInterface a) "Object"
+ ArgumentObject -> do obj <- resolveInterface (argInterface a) "Object"
+ pure $ if argAllowNull a then "(Maybe " ++ obj ++ ")" else obj
+
+emitBoot :: Generator ()
+emitBoot = do
+ anyObject <- resolve "Wayland.Server" "AnyObject"
+ interfaceDesc <- resolve "Wayland.Server" "InterfaceDesc"
+ objectClass <- resolve "Wayland.Server" "Object"
+ enumTypes <- asks $ typeEnums . genTypes
+ i <- asks genInterface
+ emitDecl [ "newtype Object = Object " ++ anyObject ]
+ emitExport "Object"
+ emitDecl [ "interface :: " ++ interfaceDesc ]
+ emitExport "interface"
+ emitDecl [ "instance " ++ objectClass ++ " Object"
+ , "instance Show Object"
+ , "instance Eq Object" ]
+ let emitEnumBoot e = do let name = enumTypes ! enumName e
+ emitDecl [ "data " ++ name
+ , "instance Show " ++ name
+ , "instance Eq " ++ name ]
+ emitExport name
+ mapM_ emitEnumBoot (ifEnums i)
+
+emit :: Generator ()
+emit = do
+ anyObject <- resolve "Wayland.Server" "AnyObject"
+ hashable <- resolve "Data.Hashable" "Hashable"
+ objectClass <- resolve "Wayland.Server" "Object"
+ i <- asks genInterface
+ emitExtension "GeneralizedNewtypeDeriving"
+ emitExtension "EmptyDataDeriving"
+ emitDecl [ "newtype Object = Object " ++ anyObject
+ , " deriving (Show, Eq, Ord, " ++ hashable ++ ")" ]
+ emitExport "Object"
+ mapM_ (emitMessageDesc requestDescName) (ifRequests i)
+ mapM_ (emitMessageDesc eventDescName) (ifEvents i)
+ emitInterfaceDesc
+ emitRequests (ifRequests i)
+ emitEvents (ifEvents i)
+ mapM_ emitEnum (ifEnums i)
+ asks (typeRequestArgs . genTypes) >>= mapM_ emitArguments
+ asks (typeEventArgs . genTypes) >>= mapM_ emitArguments
+ emitExtension "TypeFamilies"
+ emitDecl [ "instance " ++ objectClass ++ " Object where"
+ , " type ObjectEvent Object = Event"
+ , " type ObjectRequest Object = Request"
+ , " toAny (Object o) = o"
+ , " fromAny = Object"
+ , " describeInterface _ = interface"
+ , " demarshalRequest _ _ = undefined"
+ , " marshalEvent _ _ = undefined"
+ , " requestSignature _ _ = undefined"
+ , " eventSignature _ _ = undefined" ]
+ -- TODO: Proper marshalling/demarshalling
+
+requestDescName :: MessageDesc -> String
+requestDescName m = "req" ++ pascalCase (msgName m)
+
+eventDescName :: MessageDesc -> String
+eventDescName m = "ev" ++ pascalCase (msgName m)
+
+emitMessageDesc :: (MessageDesc -> String) -> MessageDesc -> Generator ()
+emitMessageDesc name m = do
+ let argumentNullability True = resolve "Wayland.Server" "Nullable"
+ argumentNullability False = resolve "Wayland.Server" "NotNull"
+ let argumentType :: ArgumentDesc -> Generator String
+ argumentType a = case argType a of
+ ArgumentInt -> resolve "Wayland.Server" "ArgumentInt"
+ ArgumentUInt -> resolve "Wayland.Server" "ArgumentUInt"
+ ArgumentFixed -> resolve "Wayland.Server" "ArgumentFixed"
+ ArgumentArray -> resolve "Wayland.Server" "ArgumentArray"
+ ArgumentFD -> resolve "Wayland.Server" "ArgumentFD"
+ ArgumentString -> do aType <- resolve "Wayland.Server" "ArgumentString"
+ aNullability <- argumentNullability (argAllowNull a)
+ pure $ aType ++ " " ++ aNullability
+ ArgumentObject -> do aType <- resolve "Wayland.Server" "ArgumentObject"
+ aIface <- resolveInterface (argInterface a) "interface"
+ aNullability <- argumentNullability (argAllowNull a)
+ pure $ aType ++ " " ++ aNullability ++ " " ++ aIface
+ ArgumentNewID -> do aType <- resolve "Wayland.Server" "ArgumentNewID"
+ aIface <- resolveInterface (argInterface a) "interface"
+ pure $ aType ++ " " ++ aIface
+ messageDesc <- resolve "Wayland.Server" "MessageDesc"
+ msgName' <- resolve "Wayland.Server" "msgName"
+ msgArguments' <- resolve "Wayland.Server" "msgArguments"
+ msgSince' <- resolve "Wayland.Server" "msgSince"
+ msgIsDestructor' <- resolve "Wayland.Server" "msgIsDestructor"
+ arguments <- mapM argumentType (msgArguments m)
+ emitDecl [ name m ++ " :: " ++ messageDesc
+ , name m ++ " ="
+ , " " ++ messageDesc
+ , " { " ++ msgName' ++ " = " ++ escapeStr (msgName m)
+ , " , " ++ msgArguments' ++ " = [" ++ intercalate ", " arguments ++ "]"
+ , " , " ++ msgSince' ++ " = " ++ show (msgSince m)
+ , " , " ++ msgIsDestructor' ++ " = " ++ show (msgType m == MessageDestructor)
+ , " }"
+ ]
+
+emitInterfaceDesc :: Generator ()
+emitInterfaceDesc = do
+ interfaceDesc <- resolve "Wayland.Server" "InterfaceDesc"
+ ifName' <- resolve "Wayland.Server" "ifName"
+ ifVersion' <- resolve "Wayland.Server" "ifVersion"
+ ifRequests' <- resolve "Wayland.Server" "ifRequests"
+ ifEvents' <- resolve "Wayland.Server" "ifEvents"
+ i <- asks genInterface
+ emitDecl [ "interface :: " ++ interfaceDesc
+ , "interface ="
+ , " " ++ interfaceDesc
+ , " { " ++ ifName' ++ " = " ++ escapeStr (ifName i)
+ , " , " ++ ifVersion' ++ " = " ++ show (ifVersion i)
+ , " , " ++ ifRequests' ++ " = [" ++ intercalate ", " (requestDescName <$> ifRequests i) ++ "]"
+ , " , " ++ ifEvents' ++ " = [" ++ intercalate ", " (eventDescName <$> ifEvents i) ++ "]"
+ , " }" ]
+ emitExport "interface"
+
+emitRequests :: [MessageDesc] -> Generator ()
+emitRequests ms = emitMessages ms typeRequestArgs formatConstructor "Request"
+ where formatConstructor m = "Req" ++ pascalCase (msgName m) ++ " Object"
+
+emitEvents :: [MessageDesc] -> Generator ()
+emitEvents ms = emitMessages ms typeEventArgs formatConstructor "Event"
+ where formatConstructor m = "Ev" ++ pascalCase (msgName m)
+
+emitMessages :: [MessageDesc] -> (TypeInfo -> M.Map String ArgumentMode) -> (MessageDesc -> String) -> String -> Generator ()
+emitMessages ms argTypes formatConstructor type' = do
+ typeable <- resolve "Data.Typeable" "Typeable"
+ messageClass <- resolve "Wayland.Server" "MessageClass"
+ typeClass <- resolve "Wayland.Server" (type' ++ "Class")
+ args <- asks $ argTypes . genTypes
+ let constructorArg :: ArgumentMode -> Generator String
+ constructorArg ArgumentModeNone = pure ""
+ constructorArg (ArgumentModeSingle a) = (++) " " <$> resolveArgument a
+ constructorArg (ArgumentModeRecord _ r) = pure $ " " ++ r
+ let constructor m = (++) (formatConstructor m) <$> constructorArg (args ! msgName m)
+ constructors <- mapM constructor ms
+ emitDecl $ [ "data " ++ type' ]
+ ++ dataConstructors constructors
+ ++ [ " deriving (" ++ typeable ++ ", Show, Eq)" ]
+ emitExport $ type' ++ "(..)"
+ emitDecl [ "instance " ++ messageClass ++ " " ++ type'
+ , "instance " ++ typeClass ++ " " ++ type' ]
+
+emitEnum :: EnumDesc -> Generator ()
+emitEnum e = do
+ -- TODO: Handle bitsets
+ types <- asks $ typeEnums . genTypes
+ let name = types ! enumName e
+ let constructorName entry = name ++ pascalCase (entryName entry)
+ let constructors = dataConstructors $ fmap constructorName (enumEntries e)
+ emitDecl ([ "data " ++ name ]
+ ++ constructors ++
+ [ " deriving (Show, Eq)" ])
+ emitExport $ name ++ "(..)"
+
+emitArguments :: ArgumentMode -> Generator ()
+emitArguments ArgumentModeNone = pure ()
+emitArguments (ArgumentModeSingle _) = pure ()
+emitArguments (ArgumentModeRecord args name) = do
+ let field a = recordField name (camelCase (argName (fst a))) ++ " :: " ++ snd a
+ let formatArgs [] = [ " {" ]
+ formatArgs (a:as) = (" { " ++ field a) : fmap formatArg' as
+ formatArg' a = " , " ++ field a
+ resolvedArgs <- mapM resolveArgument args
+ emitDecl ([ "data " ++ name
+ , " = " ++ name ]
+ ++ formatArgs (zip args resolvedArgs) ++
+ [ " }"
+ , " deriving (Show, Eq)" ])
+ emitExport $ name ++ "(..)"
+
+dataConstructors :: [String] -> [String]
+dataConstructors (c:cs) = (" = " ++ c) : fmap (" | " ++) cs
+dataConstructors [] = []
+
+escapeStr :: String -> String
+escapeStr str = "\"" ++ escapeInner str ++ "\""
+ where escapeInner ('\"':xs) = "\\\"" ++ escapeInner xs
+ escapeInner ('\\':xs) = "\\\\" ++ escapeInner xs
+ escapeInner ('\n':xs) = "\\n" ++ escapeInner xs
+ escapeInner (x :xs) = x : escapeInner xs
+ escapeInner [] = []
diff --git a/protocols/codegen/Wayland/CodeGen/Parser.hs b/protocols/codegen/Wayland/CodeGen/Parser.hs
index 63ba2c4..e2f6c23 100644
--- a/protocols/codegen/Wayland/CodeGen/Parser.hs
+++ b/protocols/codegen/Wayland/CodeGen/Parser.hs
@@ -2,6 +2,7 @@ module Wayland.CodeGen.Parser
( parseProtocolSpec
) where
+import Data.Maybe (fromMaybe)
import Text.XML.Light (Element(..), findAttrBy, QName(..), filterChildName, filterChildrenName, strContent, parseXMLDoc)
import Text.Read (readEither)
import Wayland.CodeGen.Types
@@ -34,7 +35,7 @@ processInterface :: Element -> Either String InterfaceDesc
processInterface el = do
name <- requireAttr "name" el
version <- requireAttr "version" el >>= readEither
- description <- traverse processDescription (filterChildName (matchName "description") el)
+ description <- mapM processDescription (filterChildName (matchName "description") el)
enums <- mapM processEnum (filterChildrenName (matchName "enum") el)
requests <- mapM processMessage (filterChildrenName (matchName "request") el)
events <- mapM processMessage (filterChildrenName (matchName "event") el)
@@ -49,14 +50,14 @@ processInterface el = do
processEnum :: Element -> Either String EnumDesc
processEnum el = do
name <- requireAttr "name" el
- description <- traverse processDescription (filterChildName (matchName "description") el)
- since <- traverse readEither (findAttrBy (matchName "since") el)
- bitfield <- traverse readBool (findAttrBy (matchName "bitfield") el)
+ description <- mapM processDescription (filterChildName (matchName "description") el)
+ since <- mapM readEither (findAttrBy (matchName "since") el)
+ bitfield <- mapM readBool (findAttrBy (matchName "bitfield") el)
entries <- mapM processEntry (filterChildrenName (matchName "entry") el)
pure $ EnumDesc { enumName = name
, enumDescription = description
- , enumSince = since
- , enumBitfield = bitfield
+ , enumSince = fromMaybe 1 since
+ , enumBitfield = fromMaybe False bitfield
, enumEntries = entries
}
@@ -64,30 +65,30 @@ processEntry :: Element -> Either String EntryDesc
processEntry el = do
name <- requireAttr "name" el
value <- requireAttr "value" el >>= readEither
- description <- traverse processDescription (filterChildName (matchName "description") el)
- since <- traverse readEither (findAttrBy (matchName "since") el)
- deprecatedSince <- traverse readEither (findAttrBy (matchName "deprecated-since") el)
+ description <- mapM processDescription (filterChildName (matchName "description") el)
+ since <- mapM readEither (findAttrBy (matchName "since") el)
+ deprecatedSince <- mapM readEither (findAttrBy (matchName "deprecated-since") el)
let summary = findAttrBy (matchName "summary") el
pure $ EntryDesc { entryName = name
, entryValue = value
, entryDescription = description
, entrySummary = summary
- , entrySince = since
+ , entrySince = fromMaybe 1 since
, entryDeprecatedSince = deprecatedSince
}
processMessage :: Element -> Either String MessageDesc
processMessage el = do
name <- requireAttr "name" el
- description <- traverse processDescription (filterChildName (matchName "description") el)
- type' <- traverse readMessageType (findAttrBy (matchName "type") el)
- since <- traverse readEither (findAttrBy (matchName "since") el)
- deprecatedSince <- traverse readEither (findAttrBy (matchName "deprecated-since") el)
+ description <- mapM processDescription (filterChildName (matchName "description") el)
+ type' <- mapM readMessageType (findAttrBy (matchName "type") el)
+ since <- mapM readEither (findAttrBy (matchName "since") el)
+ deprecatedSince <- mapM readEither (findAttrBy (matchName "deprecated-since") el)
arguments <- mapM processArgument (filterChildrenName (matchName "arg") el)
pure $ MessageDesc { msgName = name
, msgDescription = description
- , msgType = type'
- , msgSince = since
+ , msgType = fromMaybe MessageNormal type'
+ , msgSince = fromMaybe 1 since
, msgDeprecatedSince = deprecatedSince
, msgArguments = arguments
}
@@ -96,8 +97,8 @@ processArgument :: Element -> Either String ArgumentDesc
processArgument el = do
name <- requireAttr "name" el
type' <- requireAttr "type" el >>= readArgumentType
- description <- traverse processDescription (filterChildName (matchName "description") el)
- allowNull <- traverse readBool (findAttrBy (matchName "allow-null") el)
+ description <- mapM processDescription (filterChildName (matchName "description") el)
+ allowNull <- mapM readBool (findAttrBy (matchName "allow-null") el)
let summary = findAttrBy (matchName "summary") el
let interface = findAttrBy (matchName "interface") el
let enum = findAttrBy (matchName "enum") el
@@ -106,8 +107,8 @@ processArgument el = do
, argDescription = description
, argSummary = summary
, argInterface = interface
- , argAllowNull = allowNull
, argEnum = enum
+ , argAllowNull = fromMaybe False allowNull
}
requireAttr :: String -> Element -> Either String String
diff --git a/protocols/codegen/Wayland/CodeGen/Types.hs b/protocols/codegen/Wayland/CodeGen/Types.hs
index b783a7c..1a1d17e 100644
--- a/protocols/codegen/Wayland/CodeGen/Types.hs
+++ b/protocols/codegen/Wayland/CodeGen/Types.hs
@@ -1,5 +1,8 @@
module Wayland.CodeGen.Types
- ( Protocol(..)
+ ( ProtocolT(..)
+ , Protocol
+ , TypedProtocol
+ , TypedInterfaceDesc
, InterfaceDesc(..)
, EnumDesc(..)
, EntryDesc(..)
@@ -7,17 +10,29 @@ module Wayland.CodeGen.Types
, MessageType(..)
, ArgumentDesc(..)
, ArgumentType(..)
+ , ArgumentMode(..)
, Description(..)
+ , TypeInfo(..)
+ , emptyTypeInfo
+ , isTypeFree
) where
+import qualified Data.Map as M
-data Protocol
+data ProtocolT i
= Protocol { protName :: !String
, protCopyright :: !(Maybe String)
, protDescription :: !(Maybe Description)
- , protInterfaces :: ![InterfaceDesc]
+ , protInterfaces :: ![i]
}
deriving Show
+type Protocol = ProtocolT InterfaceDesc
+
+-- Protocol with type names resolved (avoiding possible name clashes within an interface).
+type TypedProtocol = ProtocolT TypedInterfaceDesc
+
+type TypedInterfaceDesc = (InterfaceDesc, TypeInfo)
+
data InterfaceDesc
= InterfaceDesc { ifName :: !String
, ifVersion :: !Int
@@ -31,8 +46,8 @@ data InterfaceDesc
data EnumDesc
= EnumDesc { enumName :: !String
, enumDescription :: !(Maybe Description)
- , enumSince :: !(Maybe Int)
- , enumBitfield :: !(Maybe Bool)
+ , enumSince :: !Int
+ , enumBitfield :: !Bool
, enumEntries :: ![EntryDesc]
}
deriving Show
@@ -42,7 +57,7 @@ data EntryDesc
, entryValue :: !Int
, entryDescription :: !(Maybe Description)
, entrySummary :: !(Maybe String)
- , entrySince :: !(Maybe Int)
+ , entrySince :: !Int
, entryDeprecatedSince :: !(Maybe Int)
}
deriving Show
@@ -50,8 +65,8 @@ data EntryDesc
data MessageDesc
= MessageDesc { msgName :: !String
, msgDescription :: !(Maybe Description)
- , msgType :: !(Maybe MessageType)
- , msgSince :: !(Maybe Int)
+ , msgType :: !MessageType
+ , msgSince :: !Int
, msgDeprecatedSince :: !(Maybe Int)
, msgArguments :: ![ArgumentDesc]
}
@@ -60,17 +75,17 @@ data MessageDesc
data MessageType
= MessageNormal
| MessageDestructor
- deriving Show
+ deriving (Show, Eq)
data ArgumentDesc
= ArgumentDesc { argName :: !String
- , argType :: !ArgumentType
- , argDescription :: !(Maybe Description)
- , argSummary :: !(Maybe String)
- , argInterface :: !(Maybe String)
- , argAllowNull :: !(Maybe Bool)
- , argEnum :: !(Maybe String)
- }
+ , argType :: !ArgumentType
+ , argDescription :: !(Maybe Description)
+ , argSummary :: !(Maybe String)
+ , argInterface :: !(Maybe String)
+ , argEnum :: !(Maybe String)
+ , argAllowNull :: !Bool
+ }
deriving Show
data ArgumentType
@@ -82,10 +97,36 @@ data ArgumentType
| ArgumentFD
| ArgumentNewID
| ArgumentObject
- deriving Show
+ deriving (Show, Eq)
data Description
= Description { descSummary :: !String
, descBody :: !String
}
deriving Show
+
+data ArgumentMode
+ = ArgumentModeNone
+ | ArgumentModeSingle ArgumentDesc
+ | ArgumentModeRecord [ArgumentDesc] String
+ deriving Show
+
+data TypeInfo
+ = TypeInfo { typeRequestArgs :: !(M.Map String ArgumentMode)
+ , typeEventArgs :: !(M.Map String ArgumentMode)
+ , typeEnums :: !(M.Map String String)
+ }
+ deriving Show
+
+emptyTypeInfo :: TypeInfo
+emptyTypeInfo = TypeInfo { typeRequestArgs = M.empty
+ , typeEventArgs = M.empty
+ , typeEnums = M.empty
+ }
+
+isTypeFree :: String -> TypeInfo -> Bool
+isTypeFree name types = not $ any (argMatches name) (typeRequestArgs types)
+ || any (argMatches name) (typeEventArgs types)
+ || name `elem` typeEnums types
+ where argMatches n (ArgumentModeRecord _ n') = n == n'
+ argMatches _ _ = False
diff --git a/protocols/codegen/Wayland/CodeGen/Utils.hs b/protocols/codegen/Wayland/CodeGen/Utils.hs
new file mode 100644
index 0000000..fa04286
--- /dev/null
+++ b/protocols/codegen/Wayland/CodeGen/Utils.hs
@@ -0,0 +1,88 @@
+module Wayland.CodeGen.Utils
+ ( pascalCase
+ , camelCase
+ , recordField
+ , protocolModule
+ , interfaceModule
+ , fullModule
+ , FlatModules
+ , flattenModules
+ , resolveTypes
+ ) where
+
+import Data.Char (toUpper, toLower)
+import Wayland.CodeGen.Types
+import qualified Data.Map as M
+import Control.Monad.State.Strict (State, runState, gets, modify)
+import Data.Functor (($>))
+
+type FlatModules = M.Map String (TypedProtocol, TypedInterfaceDesc)
+
+pascalCase :: String -> String
+pascalCase (c:cs) = toUpper c : camelCase cs
+pascalCase [] = []
+
+camelCase :: String -> String
+camelCase ('_':cs) = pascalCase cs
+camelCase (c :cs) = c : camelCase cs
+camelCase [] = []
+
+recordField :: String -> String -> String
+recordField name field = applyFirst toLower name ++ applyFirst toUpper field
+ where applyFirst _ [] = []
+ applyFirst f (x:xs) = f x : xs
+
+protocolModule :: ProtocolT i -> String
+protocolModule = fromName . protName
+ where fromName "wayland" = "Core"
+ fromName name = pascalCase name
+
+interfaceModule :: InterfaceDesc -> String
+interfaceModule = pascalCase . ifName
+
+fullModule :: ProtocolT i -> InterfaceDesc -> String
+fullModule p i = "Wayland.Protocols." ++ protocolModule p ++ "." ++ interfaceModule i
+
+flattenModules :: [TypedProtocol] -> FlatModules
+flattenModules protocols = M.unions $ fmap flattenProtocol protocols
+ where flattenProtocol p = M.fromList $ fmap (flattenInterface p) (protInterfaces p)
+ flattenInterface p i = (ifName (fst i), (p, i))
+
+resolveTypes :: Protocol -> TypedProtocol
+resolveTypes p = Protocol { protName = protName p
+ , protDescription = protDescription p
+ , protCopyright = protCopyright p
+ , protInterfaces = runInterface <$> protInterfaces p
+ }
+ where runInterface i = runState (resolveInterfaceTypes i $> i) emptyTypeInfo
+
+resolveInterfaceTypes :: InterfaceDesc -> State TypeInfo ()
+resolveInterfaceTypes i = do
+ let assignRequestArgs r a (TypeInfo requests events enums) = TypeInfo (M.insert r a requests) events enums
+ let assignEventArgs e a (TypeInfo requests events enums) = TypeInfo requests (M.insert e a events) enums
+ let assignEnumName e n (TypeInfo requests events enums) = TypeInfo requests events (M.insert e n enums)
+ let resolveAndAssign res ass = do x <- res; modify $ ass x
+ mapM_ (\m -> resolveAndAssign (resolveMessageType m) (assignEventArgs (msgName m))) (ifEvents i)
+ mapM_ (\m -> resolveAndAssign (resolveMessageType m) (assignRequestArgs (msgName m))) (ifRequests i)
+ mapM_ (\e -> resolveAndAssign (resolveEnumType e) (assignEnumName (enumName e))) (ifEnums i)
+
+resolveMessageType :: MessageDesc -> State TypeInfo ArgumentMode
+resolveMessageType m = case msgArguments m of
+ [] -> pure ArgumentModeNone
+ [a] -> pure $ ArgumentModeSingle a
+ as -> ArgumentModeRecord as <$> getFreeTypeName (pascalCase (msgName m)) Nothing
+
+resolveEnumType :: EnumDesc -> State TypeInfo String
+resolveEnumType e = getFreeTypeName (pascalCase (enumName e)) $ if enumBitfield e then Just "Flags" else Nothing
+
+getFreeTypeName :: String -> Maybe String -> State TypeInfo String
+getFreeTypeName requested conflictSuffix = attemptRequested
+ where attemptName :: String -> State TypeInfo Bool
+ attemptName name = gets $ isTypeFree name
+ attemptRequested = attemptName requested >>= \s -> if s then pure requested else attemptSuffix
+ attemptSuffix = case conflictSuffix of
+ Just suffix -> let n = requested ++ suffix
+ in attemptName n >>= \s -> if s then pure n else attemptPrime n
+ Nothing -> attemptPrime requested
+ attemptPrime n = let n' = n ++ "'"
+ in attemptName n' >>= \s -> if s then pure n' else attemptPrime n'