From c5c9537ed9f4e07ccda01e0b4e83d409c3f7ca12 Mon Sep 17 00:00:00 2001
From: Jens Nolte <git@queezle.net>
Date: Thu, 9 Sep 2021 20:50:31 +0200
Subject: [PATCH] Parse arguments

---
 src/Quasar/Wayland/Protocol/Core.hs | 19 +++++----
 src/Quasar/Wayland/Protocol/TH.hs   | 61 +++++++++++++++++++++++------
 2 files changed, 60 insertions(+), 20 deletions(-)

diff --git a/src/Quasar/Wayland/Protocol/Core.hs b/src/Quasar/Wayland/Protocol/Core.hs
index c4539fe..b4877e7 100644
--- a/src/Quasar/Wayland/Protocol/Core.hs
+++ b/src/Quasar/Wayland/Protocol/Core.hs
@@ -61,9 +61,13 @@ type Opcode = Word16
 
 -- | Signed 24.8 decimal numbers.
 newtype Fixed = Fixed Word32
-  deriving Eq
+  deriving newtype Eq
+
+instance Show Fixed where
+  show x = "[fixed " <> show x <> "]"
 
 newtype NewId = NewId ObjectId
+  deriving newtype (Eq, Show)
 
 
 dropRemaining :: Get ()
@@ -82,9 +86,9 @@ data ArgumentType
   | NewIdArgument String
   | UnknownNewIdArgument
   | FdArgument
-  deriving stock (Show, Lift)
+  deriving stock (Eq, Show, Lift)
 
-class WireFormat a where
+class (Eq (Argument a), Show (Argument a)) => WireFormat a where
   type Argument a
   putArgument :: Argument a -> PutM ()
   getArgument :: Get (Argument a)
@@ -222,7 +226,7 @@ instance IsObjectSide (SomeObject s m) where
       " (" <> show (BSL.length body) <> "B, unknown)"
 
 
-class IsMessage a where
+class (Eq a, Show a) => IsMessage a where
   opcodeName :: Opcode -> Maybe String
   showMessage :: IsMessage a => a -> String
   getMessage :: IsInterface i => Object s m i -> Opcode -> Get a
@@ -389,11 +393,12 @@ handleMessage rawMessage@(oId, opcode, body) = do
     Nothing -> throwM $ ProtocolException $ "Received message with invalid object id " <> show oId
 
     Just (SomeObject object) -> do
+      traceM $ "Received message (raw) " <> describeDownMessage object opcode body
+
       case runGetOrFail (getMessageAction st.objects object rawMessage) body of
         Left (_, _, message) ->
           throwM $ ParserFailed (describeDownMessage object opcode body) message
-        Right ("", _, result) ->
-          traceM $ "Received message " <> (describeDownMessage object opcode body)
+        Right ("", _, result) -> result
         Right (leftovers, _, _) ->
           throwM $ ParserFailed (describeDownMessage object opcode body) (show (BSL.length leftovers) <> "B not parsed")
 
@@ -408,7 +413,7 @@ getMessageAction
   -> Get (ProtocolAction s m ())
 getMessageAction objects object@(Object _ callback) (oId, opcode, body) = do
   message <- getDown object opcode
-  pure $ traceM $ "Received message " <> describeDownMessage object opcode body
+  pure $ traceM $ "Received message " <> show message
 
 type ProtocolAction s m a = StateT (ProtocolState s m) m a
 
diff --git a/src/Quasar/Wayland/Protocol/TH.hs b/src/Quasar/Wayland/Protocol/TH.hs
index 87f36f7..986f0eb 100644
--- a/src/Quasar/Wayland/Protocol/TH.hs
+++ b/src/Quasar/Wayland/Protocol/TH.hs
@@ -7,7 +7,7 @@ import Data.Binary
 import Data.ByteString qualified as BS
 import Language.Haskell.TH
 import Language.Haskell.TH.Lib
-import Language.Haskell.TH.Syntax (addDependentFile)
+import Language.Haskell.TH.Syntax (BangType, addDependentFile)
 import Language.Haskell.TH.Syntax qualified as TH
 import Quasar.Prelude
 import Quasar.Wayland.Protocol.Core
@@ -24,10 +24,10 @@ data InterfaceSpec = InterfaceSpec {
 }
   deriving stock Show
 
-newtype RequestSpec = RequestSpec MessageSpec
+newtype RequestSpec = RequestSpec {messageSpec :: MessageSpec}
   deriving stock Show
 
-newtype EventSpec = EventSpec MessageSpec
+newtype EventSpec = EventSpec {messageSpec :: MessageSpec}
   deriving stock Show
 
 data MessageSpec = MessageSpec {
@@ -71,11 +71,11 @@ interfaceDec interface = execWriterT do
   tellQ $ instanceD (pure []) [t|IsInterface $iT|] instanceDecs
 
   when (length interface.requests > 0) do
-    tellQ $ dataD (pure []) rTypeName [] Nothing (rCon <$> interface.requests) []
+    tellQ $ messageTypeD rTypeName rConName (.messageSpec) interface.requests
     tellQ $ messageInstanceD rT ((\req@(RequestSpec msg) -> (msg, rConName req)) <$> interface.requests)
 
   when (length interface.events > 0) do
-    tellQ $ dataD (pure []) eTypeName [] Nothing (eCon <$> interface.events) []
+    tellQ $ messageTypeD eTypeName eConName (.messageSpec) interface.events
     tellQ $ messageInstanceD eT ((\ev@(EventSpec msg) -> (msg, eConName ev)) <$> interface.events)
 
   where
@@ -92,16 +92,18 @@ interfaceDec interface = execWriterT do
     rTypeName = mkName $ "R_" <> interface.name
     rConName :: RequestSpec -> Name
     rConName (RequestSpec request) = mkName $ "R_" <> interface.name <> "_" <> request.name
-    rCon :: RequestSpec -> Q Con
-    rCon request = normalC (rConName request) []
     eT :: Q Type
     eT = if length interface.events > 0 then conT eTypeName else [t|Void|]
     eTypeName :: Name
     eTypeName = mkName $ "E_" <> interface.name
     eConName :: EventSpec -> Name
     eConName (EventSpec event) = mkName $ "E_" <> interface.name <> "_" <> event.name
-    eCon :: EventSpec -> Q Con
-    eCon event = normalC (eConName event) []
+
+messageTypeD :: forall a. Name -> (a -> Name) -> (a -> MessageSpec) -> [a] -> Q Dec
+messageTypeD name conName msgSpec msgs = dataD (pure []) name [] Nothing (con <$> msgs) [derivingEq, derivingShow]
+  where
+    con :: a -> Q Con
+    con msg = normalC (conName msg) (defaultBangType <$> messageArgTs (msgSpec msg))
 
 messageInstanceD :: Q Type -> [(MessageSpec, Name)] -> Q Dec
 messageInstanceD t messages = instanceD (pure []) [t|IsMessage $t|] [opcodeNameD, showMessageD, getMessageD, putMessageD]
@@ -113,20 +115,32 @@ messageInstanceD t messages = instanceD (pure []) [t|IsMessage $t|] [opcodeNameD
     showMessageD :: Q Dec
     showMessageD = funD 'showMessage (showMessageClauseD <$> messages)
     showMessageClauseD :: (MessageSpec, Name) -> Q Clause
-    showMessageClauseD (msg, conName) = clause [conP conName []] (normalB (stringE msg.name)) []
+    showMessageClauseD (msg, conName) = clause [conP conName (replicate (length msg.arguments) wildP)] (normalB (stringE msg.name)) []
     getMessageD :: Q Dec
     getMessageD = funD 'getMessage (getMessageClauseD <$> messages)
     getMessageClauseD :: (MessageSpec, Name) -> Q Clause
-    getMessageClauseD (msg, conName) = clause [[p|_object|], litP (integerL (fromIntegral msg.opcode))] (normalB ([|$(conE conName) <$ dropRemaining|])) []
+    getMessageClauseD (msg, conName) = clause [wildP, litP (integerL (fromIntegral msg.opcode))] (normalB getMessageE) []
+      where
+        getMessageE :: Q Exp
+        getMessageE = applyA (conE conName) ((\argT -> [|getArgument @($argT)|]) <$> messageArgSpecTs msg)
     putMessageD :: Q Dec
     putMessageD = funD 'putMessage [clause [] (normalB [|undefined|]) []]
 
+messageArgTs :: MessageSpec -> [Q Type]
+messageArgTs msg = argumentType <$> msg.arguments
+
+messageArgSpecTs :: MessageSpec -> [Q Type]
+messageArgSpecTs msg = argumentSpecType <$> msg.arguments
+
 interfaceN :: InterfaceSpec -> Name
 interfaceN interface = mkName $ "I_" <> interface.name
 
 interfaceT :: InterfaceSpec -> Q Type
 interfaceT interface = conT (interfaceN interface)
 
+derivingEq :: Q DerivClause
+derivingEq = derivClause (Just StockStrategy) [[t|Eq|]]
+
 derivingShow :: Q DerivClause
 derivingShow = derivClause (Just StockStrategy) [[t|Show|]]
 
@@ -136,15 +150,36 @@ derivingInterfaceClient = derivClause (Just AnyclassStrategy) [[t|IsInterfaceSid
 derivingInterfaceServer :: Q DerivClause
 derivingInterfaceServer = derivClause (Just AnyclassStrategy) [[t|IsInterfaceSide 'Server|]]
 
-promoteArgumentType :: ArgumentType -> Q Type
-promoteArgumentType arg = do
+argumentType :: ArgumentSpec -> Q Type
+argumentType argSpec = [t|Argument $(promoteArgumentSpecType argSpec.argType)|]
+
+argumentSpecType :: ArgumentSpec -> Q Type
+argumentSpecType argSpec = promoteArgumentSpecType argSpec.argType
+
+promoteArgumentSpecType :: ArgumentType -> Q Type
+promoteArgumentSpecType arg = do
   argExp <- (TH.lift arg)
   ConT <$> matchCon argExp
   where
     matchCon :: Exp -> Q Name
     matchCon (ConE name) = pure name
+    matchCon (AppE x _) = matchCon x
     matchCon _ = fail "Can only promote ConE expression"
 
+defaultBangType :: Q Type -> Q BangType
+defaultBangType = bangType (bang noSourceUnpackedness noSourceStrictness)
+
+
+-- | (a -> b -> c -> d) -> [m a, m b, m c] -> m d
+applyA :: Q Exp -> [Q Exp] -> Q Exp
+applyA con [] = [|pure $con|]
+applyA con (monadicE:monadicEs) = foldl (\x y -> [|$x <*> $y|]) [|$con <$> $monadicE|] monadicEs
+
+-- | (a -> b -> c -> m d) -> [m a, m b, m c] -> m d
+applyM :: Q Exp -> [Q Exp] -> Q Exp
+applyM con [] = con
+applyM con args = [|join $(applyA con args)|]
+
 
 -- * XML parser
 
-- 
GitLab