diff options
Diffstat (limited to 'protocols/codegen/Wayland/CodeGen/CodeGen.hs')
| -rw-r--r-- | protocols/codegen/Wayland/CodeGen/CodeGen.hs | 293 |
1 files changed, 293 insertions, 0 deletions
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 [] = [] |
