% % (c) The Foo Project, University of Glasgow, 1998 % % @(#) $Docid: Dec. 1st 2003 07:24 Sigbjorn Finne $ % @(#) $Contactid: sof@galois.com $ % Generating marshalling code for Haskell COM servers. \begin{code}
module MarshallServ ( cgServMethod, mkServVTBL, mkServMain ) where

-- IDL representation: 
import CoreIDL
import Attribute
import Literal ( iLit )
import CoreUtils

-- code/defs to generate Haskell code:

import AbstractH   ( HDecl, Pat )
import PpAbstractH ( showAbstractH, ppType )
import AbsHUtils

-- marshalling code: 
import MarshallMethod
import MarshallMonad
import MarshallUtils
import MarshallType
import MarshallDep
import MarshallCore

import CgMonad

-- utility libraries:
import BasicTypes
import LibUtils
import Opts
	( optKeepHRESULT
        , optExportListWithTySig
	, optAnonTLB
	, optUseStdDispatch
	, optCom
        )

-- standard libraries:
import Monad ( when )
import Maybe

\end{code} What it generates: interface IA : IU { HRESULT f ([in]int x); }; primf :: Addr -> Int -> IO HRESULT primf iptr x = do v <- getIfaceState iptr catch (f x v >> return s_OK) (\ _ -> return s_FAIL) mkIA_vtbl init_state = .... \begin{code}
cgServMethod :: Id
	     -> Result
	     -> [Param]
	     -> Bool
	     -> Bool
	     -> CgM HDecl
cgServMethod i result params isSource isDisper = do
 objFlag   <- getInterfaceFlag
 let
        isObj        = objFlag /= StdFFI
	isDispSource = 
	   isDisper ||
	   (isSource && 
	    case objFlag of { ComIDispatch _ -> True ; _ -> False })

 return ( helpStringComment i   `andDecl` 
	  marshallMethod i isObj isDispSource result params
        )
\end{code} \begin{code}
marshallMethod :: Id 
	       -> Bool
	       -> Bool
	       -> Result
	       -> [Param]
	       -> HDecl
marshallMethod i isObj isDispSource result params =
   server_stub_tysig `andDecl` server_stub_def
 where
   m_name      = mkHaskellVarName (name ++ "_meth") -- increased chance of uniqueness..
   name        = idName i

   r_ty	       = resultType result

   -- The server code is split into two:
   --   * the method boilerplate (type sig and lhs of method)
   --   * the method stub that takes care of exposing the
   --     Haskell method to the outside world.
   -- 

   server_stub_tysig = genTypeSig server_stub_name meth_ctxt server_stub_type
   server_stub_type  
     | isDispSource = funTys [method_ty, tyList tyVariant, tyVar "objState"]
                           (io (tyMaybe tyVariant))
     | otherwise    = 
         case generaliseTys ((returnType stub_res_ty):stub_param_tys) of
	   ((r:ts),mbc) -> mbCtxtTyApp mbc (funTys ts r)
   server_stub_def   = funDef server_stub_name server_stub_pats server_stub_rhs
   server_stub_rhs
     | isDispSource     = stub_rhs
     | isHRESULT result = funApp returnHR [stub_rhs]
     | otherwise        = stub_rhs

   server_stub_pats 
     | isDispSource = [patVar m_name]
     | otherwise    = (patVar m_name) : in_stub_pats

   server_stub_name
     | isObj     = mkPrimitiveName  name
     | otherwise = mkWrapperName  name

   obj_state_ty   = uniqueTyVar "objState"

   (method_ty,meth_ctxt) = (funTys ps m_res_ty, ctxt)
      where
	m_res_ty
	  | isObj     = funTy obj_state_ty res
	  | otherwise = res

	(m_ty, ctxt) = toHaskellMethodTy isPure (isDispSource || isObj) False Nothing params result
        (ps,res)     = splitFunTys m_ty


   stub_param_tys = method_ty : stub_tys

   (stub_tys_1, _) = 
      constrainIIDParams (\ p -> toHaskellBaseTy True (paramType p))
      			 (\ p -> toHaskellBaseTy True (paramType p))
			 params
			 out_params

   stub_tys       = map (groundTyVars {-. (toHaskellBaseTy True)-}) stub_tys_core
   stub_tys_core
     | isObj      = toHaskellBaseTy True iUnknownTy : stub_tys_1
     | otherwise  = stub_tys_1

   stub_res_ty    = toHaskellBaseTy True r_ty
   stub_res
     | isObj            = ret unit
     | isHRESULT result = ret unit
     | otherwise        = ret meth_result_expr

   stub_rhs
     | isDispSource = dispatch_rhs
     | otherwise    = prim_stub_rhs

   prim_stub_rhs  =
      runMm (Just name) param_names stub_res $ do
          unmarshallDependents False False in_deps    (findParamTy params)
          unmarshallDependents False False inout_deps (findParamTy params)
          marshallParams False{-unmarshall-} True{-don't free-} True{-is server-} (removeDependents in_deps real_ins)
          marshallParams False  True{-don't free-} True{-is server-} (removeDependers (inout_deps++out_deps) real_inouts)
          when isObj unmarshallIfacePointer
          methCall (i{idName=m_name}) meth_result meth_params
	  when (not ignoreResult) (marshallResult i{idName=outPrefix++idName i} r_ty)
          marshallDependents True{-inside struct-} True inout_deps (findParamTy params) -- in and in-out params
          marshallParams True{-marshall-} False True{-is server-} out_params
          marshallParams True{-marshall-} False True{-is server-} (removeDependers inout_deps real_inouts)
	  marshallDependents False{-not inside struct-}
	                     True{-for a proxy-}
			     out_deps
			     (findParamTy params)
          writeOutParams (removeDependees inout_deps outs)

   dispatch_rhs = foldr unmarshallArg (apply m_name) params'
    where
      params'
       | isHRESULT result || isVoidTy (resultType result) = params
       | otherwise = params ++ [Param (mkId "the_res" "the_res" Nothing 
					    [AttrMode Out, Attribute "retval" []])
                                      Out Void Void False]

      apply nm = 
        funApp (appendStr (show out_arity) applyName)
	       ((funApply (var nm) in_args) : out_args)

      out_arity = length out_args

      out_args = 
        map toOutArgName $
	filter (\ p -> paramMode p /= In) params'
       where
         toOutArgName p =
	   var $ 
	   case (paramMode p) of
	     InOut -> "out_" ++ mkHaskellVarName (idName (paramId p))
	     _     -> mkHaskellVarName (idName (paramId p))

      in_args  = 
        map (var.mkHaskellVarName.idName.paramId) $
	filter (\ p -> paramMode p /= Out) params'

      unmarshallArg p acc = 
        let
         mode     = paramMode p
	 t        = paramType p
	 lam_pat  = mkHaskellVarName (idName (paramId p))
	 
	 (msheller, lams) =
	  case mode of
	    In    | isIUnknownTy t -> (inIUnknownArgName, [patVar lam_pat])
	    	  | otherwise      -> (inArgName, [patVar lam_pat])
	    Out
	     | isRetVal  -> (retValName, [patVar lam_pat])
	     | otherwise -> (outArgName, [patVar lam_pat])
	    InOut        -> (inoutArgName, [patVar lam_pat, patVar ("out_" ++ lam_pat)])
  
         isRetVal  = (idAttributes (paramId p)) `hasAttributeWithName` "retval"
        in
	contApply (varName msheller) (lam lams acc)

   (meth_params, prim_params)
    | isObj 
    = ( real_params ++ [obj_param]
      , iptr_param:params
      )
    | otherwise
    = ( real_params
      , params
      )

   param_names  = map (idName.paramId) params
   in_stub_pats = map mkPat prim_params
       where
         mkPat p
	   | paramMode p == Out = patVar ("out_" ++ (idName (paramId p)))
	   | otherwise          = patVar (idName (paramId p))

   iptr_param       = iPointerParam (idName i)
   obj_param        = objParam (idName i)

   (results, ignoreResult) =
      let results' = (real_outs ++ real_inouts)
      in	   
      case r_ty of
        Void -> (results', True)
	_    -> 
	  case isHRESULT result && not optKeepHRESULT of
	    True -> (results', True)
	    _    -> (results' ++ [res_param], isSimpleTy r_ty)

    -- Note: prefixing the result vals with res__ is reqd in the proxy case
    -- to distinguish it from the name for the out/in-out param (i.e., 'res__foo'
    -- rather than 'foo'.)
   (meth_result, meth_result_expr) = 
     case results of
       []  -> (Nothing, unit)
       _   -> (Just (tuplePat (map patVar res_names)), tup (map var res_names))
         where
	  res_names = map (("res__"++).idName.paramId) results

   isPure = (idAttributes i) `hasAttributeWithName` "pure"

   returnType t = io t

   res_param = 
      let p = mkParam (outPrefix ++ name) Out r_ty in
      p{ paramOrigType=resultOrigType result
       , paramId=(paramId p){idAttributes=idAttributes i}
       }  -- replace attributes.
      
   out_params = map remPtr (removeDependents out_deps real_outs)
    where
     remPtr p 
       | not (isConstructedTy (nukeNames t') && not (isEnumTy t')) &&
         not (isVariantTy t') =	
	 	case paramType p of
		   Pointer Unique isExp (Pointer _ _ x) -> p{paramType=Pointer Unique isExp x}
		   _				        -> p{paramType=t'}
       | otherwise	      = p
      where
       t' = removePtr (paramType p)

   (pars, ins,outs,inouts,_) = binParams params
   (real_params', _)         = findParamDependents False pars
   (real_ins, in_deps)       = findParamDependents False ins
   (real_outs, out_deps)     = findParamDependents False outs
   (real_inouts, inout_deps) = findParamDependents False inouts

   real_params		     = map jiggleInOut real_params'
   
    -- the unmarshaling of inout params leaves the results bound to p__in; hence,
    -- we need to append "__in" to the method parameters here.
   jiggleInOut p 
     | paramMode p == InOut  = p{paramId=(paramId p){idName= "in__" ++ idName (paramId p)}}
     | otherwise             = p

\end{code} \begin{code}
mkServVTBL :: Id -> Bool -> Bool -> [InterfaceDecl] -> CgM HDecl
mkServVTBL iface_id isDispSource justVTBL decls =
    getDeclName $ \ mname   -> do
    dname   <- getDllName 
    let 
     meths = filter isMethod decls
  
     mkFFIDecl m = do
	let res              = methResult m 
	(_, prim_decl,mb_prim) <-
	       primDecl True{-isObj-} True{-isServer-} True{-try to re-use other decls-}
		        (declId m) dname mname
		        (methCallConv m)
		        (resultType res)
		        (methParams m)
	return (prim_decl, mb_prim)

     ffi_decls 
       | isDispSource = return []
       | otherwise    = mapM mkFFIDecl meths

    fs_stuff <- ffi_decls
    let
     (fs, prim_nms) = unzip fs_stuff
     
     mk_vtbl = 
       genTypeSig mk_vtbl_nm (listToMaybe (catMaybes ctxts))
       			     (funTys meth_tys mk_vtbl_res_ty) `andDecl`
       funDef  mk_vtbl_nm mk_vtbl_pats mk_vtbl_rhs

     mk_vtbl_pats   = map (patVar.mkHaskellVarName.idName.declId) meths
     mk_vtbl_nm     = "mk" ++ qName (vtblName iface_id)
     mk_vtbl_res_ty = io (mkTyCon comVTableTy [iid_ty, iface_ty])
    

     mk_vtbl_args   = zipWith (\ _ x -> var ("meth_arg"++show x)) meths [(0::Int)..]
     mk_vtbl_rhs    = 
       foldr
	 (uncurry binder)
	 (funApp vtbl_creator [(hList mk_vtbl_args)])
         (zip (zipWith export_prim meths (prim_nms ++ repeat Nothing)) mk_vtbl_args)
      where
       binder
         | isDispSource = \ m v n -> hLet v m n
	 | otherwise    = bind

       vtbl_creator
         | isDispSource = createDispVTable
	 | otherwise    = createComVTable

     iface_ty    = uniqueTyVar "objState" -- needs to be branded 'unique' so that
     					  -- when we come to constraining type variables
					  -- for the VTBL methods (Variant overloaded), we
					  -- leave 'objState' parameter out of it.
     iid_ty      = tyQCon (idModule iface_id)
     			  (idName iface_id) [tyUnit]

     (meth_tys, ctxts) =
       unzip $
       map (\ m -> toHaskellMethodTy False
       				     True{- is server -}
				     False
				     (Just iface_ty) (methParams m) 
				     (methResult m))
           meths

     export_prim m mb_prim_nm
       | isDispSource = funApp mkDispMethod [ stringLit f_nm, dispid, wrap_up ]
       | otherwise    = funApply (var prim_nm) [wrap_up]
      where
       prim_nm =
         case mb_prim_nm of
	   Just x -> x
	   _      -> mkPrimExportName nm

       i    = declId m
       f_nm = idOrigName i
       dispid = 
          case getDispIdAttribute (idAttributes i) of
	     Nothing -> lit (iLit (0::Int))
	     Just il -> integerLit il

       wrap_up = funApply (var (mkPrimitiveName nm)) 
			  [var (mkHaskellVarName nm)]
       nm = idName i

    addExport (ieValue mk_vtbl_nm)
    if justVTBL then
       return mk_vtbl
     else do
       return (andDecls (mk_vtbl:fs))

vtblName :: Id -> QualName
vtblName i = mkQualName (idModule i) (idName i ++ "_vtbl")
\end{code} \begin{code}
methCall :: Id -> Maybe Pat -> [Param] -> Mm ()
methCall f mb_res params = 
  addCode $
  case mb_res of
      -- i.e., [pure] has no effect of funs return void.
      --          [pure]void f(...); => f :: .... -> IO ()
    Nothing -> bind_ f_app
    Just p 
     | isPure    -> \ e -> hCase f_app [alt p e]
     | otherwise -> genBind  f_app p
 where
  isPure    = (idAttributes f) `hasAttributeWithName` "pure"
  f_app     = funApp (mkVarName meth_name) args
  args      = map (var.idName.paramId) params

  meth_name = mkHaskellVarName (idName f)

unmarshallIfacePointer :: Mm ()
unmarshallIfacePointer =
 addCode (bind (funApp getIfaceState [var iptr]) obj)
   where
    obj     = var "obj"

marshallResult :: Id -> Type -> Mm ()
marshallResult _ Void = return ()
marshallResult i   ty =
--   addCode (bind_ (funApply (marshallType proxyMarshallInfo ty) [nm]))
   addCode (bind (funApply (marshallType proxyMarshallInfo ty) [nm]) nm)
   where
     nm		= var ("res__" ++ idName i)

\end{code} \begin{code}
writeOutParams :: [Param] -> Mm ()
writeOutParams params = do
  sequence (map writeOut params)
  return ()
 where
   writeOut p
     | isVoidTy ty = return ()
     | otherwise   = addCode (bind_ wOut)
    where
     i	      = paramId p
     ty	      = paramType p
     ty'      = removeNames (removePtr ty)
     nm       = idName i
     
     resV n = var ("res__" ++ n)
     
     wOut 
      | isSimpleTy ty' 
      = funApply (refMarshallType proxyMarshallInfo ty') [var ("out_"++nm), resV nm]
      | isEnumTy ty' || isBoolTy ty'
      = funApply (refMarshallType proxyMarshallInfo intTy) [var ("out_"++nm), resV nm]
      | otherwise
      = case ty' of
	  Name "VARIANT_BOOL" _ _ _ _ _ | optCom ->
               funApply (refMarshallType proxyMarshallInfo int16Ty) [var ("out_"++nm), resV nm]
	  _ -> funApp w_mshall [castPtr (var ("out_"++nm)), resV nm]
   	where
	  w_mshall = prefix marshallRefPrefix (mkQVarName hdirectLib ptrKind)
	  
	  ptrKind
	   | isFinalised = fptr
	   | otherwise   = "Ptr"

	  isFinalised = isFOTy (toHaskellBaseTy False ty')

\end{code} What's generated for a coclass decl: \begin{code}
mkServMain :: String -> Id -> [CoClassDecl] -> CgM HDecl
mkServMain lib_nm i cdecls = do
   mapM_ addExp exports
   return ( register_class     `andDecl` 
            new_instance       `andDecl`
	    component_info_def `andDecl`
	    vtbl_decls         `andDecl`
	    ifaces_decl	)
  where
   addExp (nm,ty)
    | optExportListWithTySig = addExportWithComment nm (":: " ++ showAbstractH (ppType ty))
    | otherwise		     = addExport nm

   exports = [ (ieValue component_info_nm, component_info_ty) ]

   class_name = idName i
   clsid_nm   = mkCLSIDName class_name
   libid_nm   = mkLIBIDName lib_nm

   implemented_ifaces = filter nonSourceIface cdecls
   
   nonSourceIface d = not ((idAttributes (coClassId d)) `hasAttributeWithName` "source")

   component_info_nm    = "componentInfo"
   component_info_ty    = mkTyConst componentInfo
   component_info_tysig = typeSig component_info_nm component_info_ty
   component_info_def   = 
	    component_info_tysig `andDecl`
	    valDef component_info_nm component_info_rhs

   component_info_rhs   = 
    (if usesTlb then
         \ x -> funApp hasTypeLib [x]
     else 
        id) $
     funApp mkComponentInfo
	    [ var clsid_nm
	    , var register_class_nm
	    , var new_instance_nm
	    ]

   register_class =
     register_class_sig  `andDecl`
     register_class_def

   register_class_sig = typeSig register_class_nm register_class_ty
   register_class_nm = "register_" ++ class_name
   register_class_ty = funTys [tyString, tyBool] io_unit

   register_class_def = 
      funDef register_class_nm [wildPat, wildPat] register_class_rhs

   register_class_rhs = ret unit --ToDo.

   new_instance = 
      new_instance_sig `andDecl`
      new_instance_def

   new_instance_sig = typeSig new_instance_nm new_instance_ty
   new_instance_nm  = "new" ++ class_name
   new_instance_ty  = 
    funTy tyString  $
    funTy (io_unit) $
    funTy (mkTyCon iID [mkTyCon iUnknown [tyVar "iid"]])
	  (io (mkTyCon iUnknown [tyVar "iid"]))

   component_mod = Just (idName i)
   component_new = qvar component_mod "new"

   new_instance_def = funDef new_instance_nm 
			     [ patVar "dll_path"
			     , patVar "finaliser"
			     , patVar "iid"
			     ]
			     new_instance_rhs
   new_instance_rhs = 
       bind  component_new obj_state $
       funApp createComInst [dll_path, obj_state, var "finaliser", var ifaces_nm, var "iid"]
  
   dll_path  = var "dll_path"
   obj_state = var "obj_state"
   ifaces_nm = "ifaces_" ++ class_name

   objStateTy = tyQConst component_mod "State"

   ifaces_decl =
      ifaces_ty_sig `andDecl`
      ifaces_def

   ifaces_ty     = tyList (mkTyCon comInterfaceTy [objStateTy])
   ifaces_ty_sig = typeSig ifaces_nm ifaces_ty
   ifaces_def = 
     valDef ifaces_nm
            ifaces_rhs
	 
   usesTlb   = not optUseStdDispatch && any derivesFromIDispatch implemented_ifaces 
   ifaces_rhs = hList (map mkIface implemented_ifaces)
   
   mkIface d
     | isDual    = funApp mkDualInterface l_args
     | isAuto    = funApp mkDispInterface l_args
     | otherwise = funApp mkComInterface    args
     where
       isDual = (idAttributes cid) `hasAttributeWithName` "dual"
       isAuto = derivesFromIDispatch d

       tlb_arg 
         | optAnonTLB || optUseStdDispatch = nothing
	 | otherwise  = just (var libid_nm)

       l_args  = (tlb_arg : args)
       args    = [qvar md ("iid" ++nm), var (vtbl_nm ++ "_vtbl")]
       cid     = coClassId d
       md      = idModule cid
       nm      = idName cid
       vtbl_nm = mkHaskellVarName nm

   vtbl_decls = andDecls (map mk_vtbl implemented_ifaces)
   
   mk_vtbl d = 
      typeSig vtbl_nm (mkTyCon comVTableTy [iid_ty, objStateTy]) `andDecl`
      valDef vtbl_nm  vtbl_rhs
    where
      iid_ty      = tyQCon (idModule (coClassId d)) 
      			   (idName (coClassId d)) [tyUnit]
      vtbl_nm_raw = vtblName (coClassId d)
      vtbl_nm     = mkHaskellVarName (qName vtbl_nm_raw)
      vtbl_rhs    = funApp uPerformIO [funApp (prefix "mk" vtbl_nm_raw) meths]

      meths    = 
        case coClassDecl d of
	   Nothing  -> error "Stuck"
	   Just dcl -> 
               let
	        decls = 
		  case dcl of
		    DispInterface{dispExpandedFrom=ii} | isJust ii -> declDecls (fromJust ii)
		    _ -> declDecls dcl

	        the_meths = filter (isMethod) decls
	       in
	       map ((qvar component_mod).mkHaskellVarName.idName.declId) the_meths

\end{code}