packages feed

haskell-gi-0.10: src/GI/CodeGen.hs

module GI.CodeGen
    ( genConstant
    , genFunction
    , genPrelude
    , genModule
    ) where

#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
import Data.Traversable (traverse)
#endif
import Control.Monad (forM, forM_, when, unless, filterM)
import Control.Monad.Writer (tell)
import Data.List (intercalate, nub)
import Data.Tuple (swap)
import Data.Maybe (fromJust, fromMaybe)
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Text as T
import Data.Text (Text)

import Foreign.Storable (sizeOf)
import Foreign.C (CUInt)

import GI.API
import GI.Callable (genCallable)
import GI.Constant (genConstant)
import GI.Code
import GI.GObject
import GI.Inheritance (instanceTree)
import GI.Signal (genSignal, genCallback)
import GI.Struct (genStructOrUnionFields, extractCallbacksInStruct,
                  fixAPIStructs, ignoreStruct)
import GI.SymbolNaming (upperName, ucFirst, classConstraint, noName)
import GI.Type

genFunction :: Name -> Function -> CodeGen ()
genFunction n (Function symbol throws callable) = do
  line $ "-- function " ++ T.unpack symbol
  handleCGExc (\e -> line ("-- XXX Could not generate function "
                           ++ T.unpack symbol
                           ++ "\n-- Error was : " ++ describeCGError e))
              (genCallable n symbol callable throws)

genBoxedObject :: Name -> Text -> CodeGen ()
genBoxedObject n typeInit = do
  name' <- upperName n

  group $ do
    line $ "foreign import ccall \"" ++ T.unpack typeInit ++ "\" c_" ++
            T.unpack typeInit ++ " :: "
    indent $ line "IO GType"
  group $ do
       line $ "instance BoxedObject " ++ name' ++ " where"
       indent $ line $ "boxedType _ = c_" ++ T.unpack typeInit

genBoxedEnum :: Name -> Text -> CodeGen ()
genBoxedEnum n typeInit = do
  name' <- upperName n

  group $ do
    line $ "foreign import ccall \"" ++ T.unpack typeInit ++ "\" c_" ++
            T.unpack typeInit ++ " :: "
    indent $ line "IO GType"
  group $ do
       line $ "instance BoxedEnum " ++ name' ++ " where"
       indent $ line $ "boxedEnumType _ = c_" ++ T.unpack typeInit

genEnumOrFlags :: Name -> Enumeration -> ExcCodeGen ()
genEnumOrFlags n@(Name ns name) (Enumeration fields eDomain maybeTypeInit storageBytes isDeprecated) = do
  -- Conversion functions expect enums and flags to map to CUInt,
  -- which we assume to be of 32 bits. Fail early, instead of giving
  -- strange errors at runtime.
  when (sizeOf (0 :: CUInt) /= 4) $
       notImplementedError $ "Unsupported CUInt size: " ++ show (sizeOf (0 :: CUInt))
  when (storageBytes /= 4) $
       notImplementedError $ "Storage of size /= 4 not supported : " ++ show storageBytes

  name' <- upperName n
  fields' <- forM fields $ \(fieldName, value) -> do
      n <- upperName $ Name ns (name ++ "_" ++ T.unpack fieldName)
      return (n, value)

  line $ deprecatedPragma name' isDeprecated

  group $ do
    line $ "data " ++ name' ++ " = "
    indent $
      case fields' of
        ((fieldName, _value):fs) -> do
          line $ "  " ++ fieldName
          forM_ fs $ \(n, _) -> line $ "| " ++ n
          line $ "| Another" ++ name' ++ " Int"
          line "deriving (Show, Eq)"
        _ -> return ()
  group $ do
    line $ "instance Enum " ++ name' ++ " where"
    indent $ do
            forM_ fields' $ \(n, v) ->
                line $ "fromEnum " ++ n ++ " = " ++ show v
            line $ "fromEnum (Another" ++ name' ++ " k) = k"
    let valueNames = M.toList . M.fromListWith (curry snd) $ map swap fields'
    blank
    indent $ do
            forM_ valueNames $ \(v, n) ->
                line $ "toEnum " ++ show v ++ " = " ++ n
            line $ "toEnum k = Another" ++ name' ++ " k"
  maybe (return ()) (genErrorDomain name') eDomain
  maybe (return ()) (genBoxedEnum n) maybeTypeInit

genEnum :: Name -> Enumeration -> CodeGen ()
genEnum n@(Name _ name) enum = do
  line $ "-- Enum " ++ name

  handleCGExc (\e -> line $ "-- XXX Could not generate: " ++ describeCGError e)
              (genEnumOrFlags n enum)

-- Very similar to enums, but we also declare ourselves as members of
-- the IsGFlag typeclass.
genFlags :: Name -> Flags -> CodeGen ()
genFlags n@(Name _ name) (Flags enum) = do
  line $ "-- Flags " ++ name

  handleCGExc (\e -> line $ "-- XXX Could not generate: " ++ describeCGError e)
              (do
                genEnumOrFlags n enum

                name' <- upperName n
                group $ line $ "instance IsGFlag " ++ name')

genErrorDomain :: String -> Text -> CodeGen ()
genErrorDomain name' domain = do
  group $ do
    line $ "instance GErrorClass " ++ name' ++ " where"
    indent $ line $
               "gerrorClassDomain _ = \"" ++ T.unpack domain ++ "\""
  -- Generate type specific error handling (saves a bit of typing, and
  -- it's clearer to read).
  group $ do
    let catcher = "catch" ++ name'
    line $ catcher ++ " ::"
    indent $ do
            line   "IO a ->"
            line $ "(" ++ name' ++ " -> GErrorMessage -> IO a) ->"
            line   "IO a"
    line $ catcher ++ " = catchGErrorJustDomain"
  group $ do
    let handler = "handle" ++ name'
    line $ handler ++ " ::"
    indent $ do
            line $ "(" ++ name' ++ " -> GErrorMessage -> IO a) ->"
            line   "IO a ->"
            line   "IO a"
    line $ handler ++ " = handleGErrorJustDomain"

genStruct :: Name -> Struct -> CodeGen ()
genStruct n s = unless (ignoreStruct n s) $ do
      name' <- upperName n
      line $ "-- struct " ++ name'

      line $ "newtype " ++ name' ++ " = " ++ name' ++ " (ForeignPtr " ++ name' ++ ")"

      noName name'

      when (structIsBoxed s) $
           genBoxedObject n (fromJust $ structTypeInit s)

      -- Generate code for fields.
      genStructOrUnionFields n (structFields s)

      -- Methods
      forM_ (structMethods s) $ \(mn, f) ->
          do isFunction <- symbolFromFunction (methodSymbol f)
             unless isFunction $
                  handleCGExc
                  (\e -> line ("-- XXX Could not generate method "
                               ++ name' ++ "::" ++ name mn ++ "\n"
                               ++ "-- Error was : " ++ describeCGError e))
                  (genMethod n mn f)

genUnion :: Name -> Union -> CodeGen ()
genUnion n u = do
  name' <- upperName n

  line $ "-- union " ++ name' ++ " "

  line $ "newtype " ++ name' ++ " = " ++ name' ++ " (ForeignPtr " ++ name' ++ ")"

  noName name'

  when (unionIsBoxed u) $
    genBoxedObject n (fromJust $ unionTypeInit u)

  -- Generate code for fields.
  genStructOrUnionFields n (unionFields u)

  -- Methods
  forM_ (unionMethods u) $ \(mn, f) ->
      do isFunction <- symbolFromFunction (methodSymbol f)
         unless isFunction $
              handleCGExc
              (\e -> line ("-- XXX Could not generate method "
                           ++ name' ++ "::" ++ name mn ++ "\n"
                           ++ "-- Error was : " ++ describeCGError e))
              (genMethod n mn f)

-- Add the implicit object argument to methods of an object.  Since we
-- are prepending an argument we need to adjust the offset of the
-- length arguments of CArrays, and closure and destroyer offsets.
fixMethodArgs :: Name -> Callable -> Callable
fixMethodArgs cn c = c {  args = args' , returnType = returnType' }
    where
      returnType' = fixCArrayLength $ returnType c
      args' = objArg : map (fixDestroyers . fixClosures . fixLengthArg) (args c)

      fixLengthArg :: Arg -> Arg
      fixLengthArg arg = arg { argType = fixCArrayLength (argType arg)}

      fixCArrayLength :: Type -> Type
      fixCArrayLength (TCArray zt fixed length t) =
          if length > -1
          then TCArray zt fixed (length+1) t
          else TCArray zt fixed length t
      fixCArrayLength t = t

      fixDestroyers :: Arg -> Arg
      fixDestroyers arg = let destroy = argDestroy arg in
                          if destroy > -1
                          then arg {argDestroy = destroy + 1}
                          else arg

      fixClosures :: Arg -> Arg
      fixClosures arg = let closure = argClosure arg in
                        if closure > -1
                        then arg {argClosure = closure + 1}
                        else arg

      objArg = Arg {
                 argName = "_obj",
                 argType = TInterface (namespace cn) (name cn),
                 direction = DirectionIn,
                 mayBeNull = False,
                 argScope = ScopeTypeInvalid,
                 argClosure = -1,
                 argDestroy = -1,
                 transfer = TransferNothing }

-- For constructors we want to return the actual type of the object,
-- rather than a generic superclass (so Gtk.labelNew returns a
-- Gtk.Label, rather than a Gtk.Widget)
fixConstructorReturnType :: Bool -> Name -> Callable -> Callable
fixConstructorReturnType returnsGObject cn c = c { returnType = returnType' }
    where
      returnType' = if returnsGObject then
                        TInterface (namespace cn) (name cn)
                    else
                        returnType c

genMethod :: Name -> Name -> Method -> ExcCodeGen ()
genMethod cn mn (Method {
                    methodSymbol = sym,
                    methodCallable = c,
                    methodType = t,
                    methodThrows = throws
                 }) = do
    name' <- upperName cn
    returnsGObject <- isGObject (returnType c)
    line $ "-- method " ++ name' ++ "::" ++ name mn
    line $ "-- method type : " ++ show t
    let -- Mangle the name to namespace it to the class.
        mn' = mn { name = name cn ++ "_" ++ name mn }
    let c'  = if Constructor == t
              then fixConstructorReturnType returnsGObject cn c
              else c
        c'' = if OrdinaryMethod == t
              then fixMethodArgs cn c'
              else c'
    genCallable mn' sym c'' throws

-- Type casting with type checking
genGObjectCasts :: Bool -> Name -> Text -> [Name] -> CodeGen ()
genGObjectCasts isIU n cn_ parents = do
  name' <- upperName n
  qualifiedParents <- traverse upperName parents

  group $ do
    line $ "foreign import ccall \"" ++ T.unpack cn_ ++ "\""
    indent $ line $ "c_" ++ T.unpack cn_ ++ " :: IO GType"

  group $ do
    line $ "type instance ParentTypes " ++ name' ++ " = '[" ++
         intercalate ", " qualifiedParents ++ "]"

  group $ do
    line $ "instance GObject " ++ name' ++ " where"
    indent $ group $ do
            line $ "gobjectIsInitiallyUnowned _ = " ++ show isIU
            line $ "gobjectType _ = c_" ++ T.unpack cn_

  let className = classConstraint name'
  group $ do
    line $ "class GObject o => " ++ className ++ " o"
    line $ "instance (GObject o, IsDescendantOf " ++ name' ++ " o) => "
             ++ className ++ " o"

  -- Safe downcasting.
  group $ do
    let safeCast = "to" ++ name'
    line $ safeCast ++ " :: " ++ className ++ " o => o -> IO " ++ name'
    line $ safeCast ++ " = unsafeCastTo " ++ name'

-- Wrap a given Object. We enforce that every Object that we wrap is a
-- GObject. This is the case for everything expect the ParamSpec* set
-- of objects, we deal with these separately. Notice that in the case
-- that a non-GObject Object is passed, it is simply ignored silently
-- by the handler in handleCGExc below.
genObject :: Name -> Object -> CodeGen ()
genObject n o = handleCGExc (\_ -> return ()) $ do
  name' <- upperName n

  line $ "-- object " ++ name' ++ " "

  let t = (\(Name ns' n') -> TInterface ns' n') n
  isGO <- isGObject t
  unless isGO $ notImplementedError $ "APIObject \"" ++ name' ++
           "\" does not descend from GObject, it will be ignored."

  line $ "newtype " ++ name' ++ " = " ++ name' ++ " (ForeignPtr " ++ name' ++ ")"

  noName name'

  -- Type safe casting to parent objects, and implemented interfaces.
  isIU <- isInitiallyUnowned t
  parents <- instanceTree n
  genGObjectCasts isIU n (objTypeInit o) (parents ++ objInterfaces o)

  -- Methods
  forM_ (objMethods o) $ \(mn, f) ->
         handleCGExc
         (\e -> line ("-- XXX Could not generate method "
                      ++ name' ++ "::" ++ name mn ++ "\n"
                      ++ "-- Error was : " ++ describeCGError e))
         (genMethod n mn f)

  -- And finally signals
  forM_ (objSignals o) $ \s ->
      handleCGExc
      (line . (concat ["-- XXX Could not generate signal ", name', "::"
                      , (T.unpack . sigName) s
                      , "\n", "-- Error was : "] ++) . describeCGError)
      (genSignal s n)

genInterface :: Name -> Interface -> CodeGen ()
genInterface n iface = do
  -- For each interface, we generate a class IFoo and a data structure
  -- Foo. We only really need a separate Foo so that we can return
  -- them from bound functions. In principle we might be able to do
  -- something more elegant with existential types.

  name' <- upperName n
  line $ "-- interface " ++ name' ++ " "
  line $ deprecatedPragma name' $ ifDeprecated iface
  line $ "newtype " ++ name' ++ " = " ++ name' ++ " (ForeignPtr " ++ name' ++ ")"

  noName name'

  isGO <- apiIsGObject n (APIInterface iface)
  if isGO
  then do
    let cn_ = fromMaybe (error "GObject derived interface without a type!") (ifTypeInit iface)
    isIU <- apiIsInitiallyUnowned n (APIInterface iface)
    gobjectPrereqs <- filterM nameIsGObject (ifPrerequisites iface)
    allParents <- forM gobjectPrereqs $ \p -> (p : ) <$> instanceTree p
    let uniqueParents = nub (concat allParents)
    genGObjectCasts isIU n cn_ uniqueParents
  else group $ do
    let cls = classConstraint name'
    line $ "class ForeignPtrNewtype a => " ++ cls ++ " a"
    line $ "instance (ForeignPtrNewtype o, IsDescendantOf " ++ name' ++ " o) => " ++ cls ++ " o"
    line $ "type instance ParentTypes " ++ name' ++ " = '[]"

  -- Methods
  forM_ (ifMethods iface) $ \(mn, f) -> do
    isFunction <- symbolFromFunction (methodSymbol f)
    unless isFunction $
         handleCGExc
         (\e -> line ("-- XXX Could not generate method "
                      ++ name' ++ "::" ++ name mn ++ "\n"
                      ++ "-- Error was : " ++ describeCGError e))
         (genMethod n mn f)

  -- And finally signals
  forM_ (ifSignals iface) $ \s -> handleCGExc
        (line . (concat ["-- XXX Could not generate signal ", name', "::"
                        , (T.unpack . sigName) s
                        , "\n", "-- Error was : "] ++) . describeCGError)
        (genSignal s n)

-- Some type libraries include spurious interface/struct methods,
-- where a method Mod.Foo::func also appears as an ordinary function
-- in the list of APIs. If we find a matching function, we don't
-- generate the method.
--
-- It may be more expedient to keep a map of symbol -> function.
--
-- XXX Maybe the GIR helps here? Sometimes such functions are
-- annotated with the "moved-to".
symbolFromFunction :: Text -> CodeGen Bool
symbolFromFunction sym = do
    apis <- getAPIs
    return $ any (hasSymbol sym . snd) $ M.toList apis
    where
        hasSymbol sym1 (APIFunction (Function { fnSymbol = sym2 })) = sym1 == sym2
        hasSymbol _ _ = False

genAPI :: Name -> API -> CodeGen ()
genAPI n (APIConst c) = genConstant n c
genAPI n (APIFunction f) = genFunction n f
genAPI n (APIEnum e) = genEnum n e
genAPI n (APIFlags f) = genFlags n f
genAPI n (APICallback c) = genCallback n c
genAPI n (APIStruct s) = genStruct n s
genAPI n (APIUnion u) = genUnion n u
genAPI n (APIObject o) = genObject n o
genAPI n (APIInterface i) = genInterface n i

genPrelude :: String -> String -> CodeGen ()
genPrelude name modulePrefix = do
    let mp = (modulePrefix ++)

    line "-- Generated code."
    blank
    line "{-# OPTIONS_GHC -fno-warn-unused-imports #-}"
    blank
    line "{-# LANGUAGE ForeignFunctionInterface, ConstraintKinds,"
    line "    TypeFamilies, MultiParamTypeClasses, KindSignatures,"
    line "    FlexibleInstances, UndecidableInstances, DataKinds,"
    line "    OverloadedStrings, NegativeLiterals, FlexibleContexts #-}"
    blank
    -- XXX: Generate export list.
    line $ "module " ++ mp name ++ " where"
    blank
    -- The Prelude functions often clash with variable names or
    -- functions defined in the bindings, so we only import the
    -- necessary minimum into our namespace.
    line "import Prelude ()"
    line "import Data.GI.Base.ShortPrelude"
    line "import Data.Char"
    line "import Data.Int"
    line "import Data.Word"
    line "import qualified Data.ByteString.Char8 as B"
    line "import Data.ByteString.Char8 (ByteString)"
    line "import qualified Data.Map as Map"
    line "import Foreign.C"
    line "import Foreign.Ptr"
    line "import Foreign.ForeignPtr"
    line "import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)"
    line "import Foreign.Storable (peek, poke, sizeOf)"
    line "import Control.Applicative ((<$>))"
    line "import Control.Exception (onException)"
    line "import Control.Monad.IO.Class"
    line "import qualified Data.Text as T"
    blank
    line "import Data.GI.Base.Attributes hiding (get, set)"
    line "import Data.GI.Base.BasicTypes"
    line "import Data.GI.Base.BasicConversions"
    line "import Data.GI.Base.Closure"
    line "import Data.GI.Base.GError"
    line "import Data.GI.Base.GHashTable"
    line "import Data.GI.Base.GParamSpec"
    line "import Data.GI.Base.GVariant"
    line "import Data.GI.Base.GValue"
    line "import Data.GI.Base.ManagedPtr"
    line "import Data.GI.Base.Overloading"
    line "import Data.GI.Base.Properties hiding (new)"
    line "import Data.GI.Base.Signals (SignalConnectMode(..), connectSignalFunPtr, SignalHandlerId)"
    line "import Data.GI.Base.Utils"
    blank

genModule :: String -> [(Name, API)] -> String -> CodeGen ()
genModule name apis modulePrefix = do
  let mp = (modulePrefix ++)
      name' = ucFirst name

  -- Any module depends on itself, load the information onto the state
  -- of the monad. This has to be done early, so symbolFromFunction
  -- above has access to it. It would probably be better to prune the
  -- duplicated method/functions in advance, and get rid of
  -- symbolFromFunction and this loadDependency altogether.
  loadDependency name

  -- Some API symbols are embedded into structures, extract these and
  -- inject them into the set of APIs loaded and being generated.
  let embeddedAPIs = concatMap extractCallbacksInStruct apis
  injectAPIs embeddedAPIs

  code <- recurse' $ mapM_ (uncurry genAPI) $
          -- We provide these ourselves
          filter ((`notElem` [ Name "GLib" "Array"
                             , Name "GLib" "Error"
                             , Name "GLib" "HashTable"
                             , Name "GLib" "List"
                             , Name "GLib" "SList"
                             , Name "GLib" "Variant"
                             , Name "GObject" "Value"
                             , Name "GObject" "Closure"]) . fst) $
          -- Some callback types are defined inside structs
          map fixAPIStructs $ (++ embeddedAPIs) apis

  genPrelude name' modulePrefix
  deps <- S.toList <$> getDeps
  forM_ deps $ \i -> when (i /= name) $ do
    line $ "import qualified " ++ mp (ucFirst i) ++ " as " ++ ucFirst i
    line $ "import qualified " ++ mp (ucFirst i) ++ "Attributes as "
             ++ ucFirst i ++ "A"
  blank

  tell code