packages feed

haskell-gi-0.13: src/GI/GIR/Type.hs

{-# LANGUAGE RecordWildCards, PatternGuards #-}
-- | Parsing type information from GIR files.
module GI.GIR.Type
    ( parseType
    ) where

#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>), (<*>))
#endif

import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import Foreign.Storable (sizeOf)
import Foreign.C (CShort, CUShort, CInt, CUInt, CLong, CULong, CSize)
import System.Posix.Types (CSsize)

import GI.Type (Type(..), BasicType(..))
import GI.GIR.Parser

-- | Map the given type name to a BasicType (in GI.Type), if possible.
nameToBasicType :: Text -> Maybe BasicType
nameToBasicType "none"     = Just TVoid
-- XXX Should we try to distinguish TPtr from TVoid?
nameToBasicType "gpointer" = Just TVoid
nameToBasicType "gboolean" = Just TBoolean
nameToBasicType "gchar"    = Just TInt8
nameToBasicType "gint8"    = Just TInt8
nameToBasicType "guint8"   = Just TUInt8
nameToBasicType "gint16"   = Just TInt16
nameToBasicType "guint16"  = Just TUInt16
nameToBasicType "gint32"   = Just TInt32
nameToBasicType "guint32"  = Just TUInt32
nameToBasicType "gint64"   = Just TInt64
nameToBasicType "guint64"  = Just TUInt64
nameToBasicType "gfloat"   = Just TFloat
nameToBasicType "gdouble"  = Just TDouble
nameToBasicType "gunichar" = Just TUniChar
nameToBasicType "GType"    = Just TGType
nameToBasicType "utf8"     = Just TUTF8
nameToBasicType "filename" = Just TFileName
nameToBasicType "gintptr"  = Just TIntPtr
nameToBasicType "guintptr" = Just TUIntPtr
nameToBasicType "gshort"   = case sizeOf (0 :: CShort) of
                               2 -> Just TInt16
                               4 -> Just TInt32
                               8 -> Just TInt64
                               n -> error $ "Unexpected short size: " ++ show n
nameToBasicType "gushort"  = case sizeOf (0 :: CUShort) of
                               2 -> Just TUInt16
                               4 -> Just TUInt32
                               8 -> Just TUInt64
                               n -> error $ "Unexpected ushort size: " ++ show n
nameToBasicType "gint"     = case sizeOf (0 :: CInt) of
                               4 -> Just TInt32
                               8 -> Just TInt64
                               n -> error $ "Unexpected int length: " ++ show n
nameToBasicType "guint"    = case sizeOf (0 :: CUInt) of
                               4 -> Just TUInt32
                               8 -> Just TUInt64
                               n -> error $ "Unexpected uint length: " ++ show n
nameToBasicType "glong"    = case sizeOf (0 :: CLong) of
                               4 -> Just TInt32
                               8 -> Just TInt64
                               n -> error $ "Unexpected long length: " ++ show n
nameToBasicType "gulong"   = case sizeOf (0 :: CULong) of
                               4 -> Just TUInt32
                               8 -> Just TUInt64
                               n -> error $ "Unexpected ulong length: " ++ show n
nameToBasicType "gssize"   = case sizeOf (0 :: CSsize) of
                               4 -> Just TInt32
                               8 -> Just TInt64
                               n -> error $ "Unexpected ssize length: " ++ show n
nameToBasicType "gsize"    = case sizeOf (0 :: CSize) of
                               4 -> Just TUInt32
                               8 -> Just TUInt64
                               n -> error $ "Unexpected size length: " ++ show n
nameToBasicType _          = Nothing

-- | The different array types.
parseArrayInfo :: Parser Type
parseArrayInfo = queryAttr "name" >>= \case
      Just "GLib.Array" -> TGArray <$> parseType
      Just "GLib.PtrArray" -> TPtrArray <$> parseType
      Just "GLib.ByteArray" -> return TByteArray
      Just other -> parseError $ "Unsupported array type: \"" <> other <> "\""
      Nothing -> parseCArrayType

-- | A C array
parseCArrayType :: Parser Type
parseCArrayType = do
  zeroTerminated <- queryAttr "zero-terminated" >>= \case
                    Just b -> parseBool b
                    Nothing -> return True
  length <- queryAttr "length" >>= \case
            Just l -> parseIntegral l
            Nothing -> return (-1)
  fixedSize <- queryAttr "fixed-size" >>= \case
               Just s -> parseIntegral s
               Nothing -> return (-1)
  elementType <- parseType
  return $ TCArray zeroTerminated fixedSize length elementType

-- | A hash table.
parseHashTable :: Parser Type
parseHashTable = parseTypeElements >>= \case
                 [key, value] -> return $ TGHash key value
                 other -> parseError $ "Unsupported hash type: "
                                       <> T.pack (show other)

-- | For GLists and GSLists there is sometimes no information about
-- the type of the elements. In these cases we report them as
-- pointers.
parseListType :: Parser Type
parseListType = queryType >>= \case
                Just t -> return t
                Nothing -> return (TBasicType TVoid)

-- | A type which is not a BasicType or array.
parseFundamentalType :: Text -> Text -> Parser Type
parseFundamentalType "GLib" "List" = TGList <$> parseListType
parseFundamentalType "GLib" "SList" = TGSList <$> parseListType
parseFundamentalType "GLib" "HashTable" = parseHashTable
parseFundamentalType "GLib" "Error" = return TError
parseFundamentalType "GLib" "Variant" = return TVariant
parseFundamentalType "GObject" "ParamSpec" = return TParamSpec
-- A TInterface type (basically, everything that is not of a known type).
parseFundamentalType ns n = resolveQualifiedTypeName ns n

-- | Parse information on a "type" element.
parseTypeInfo :: Parser Type
parseTypeInfo = do
  typeName <- getAttr "name"
  case nameToBasicType typeName of
    Just b -> return (TBasicType b)
    Nothing -> case T.split ('.' ==) typeName of
                 [ns, n] -> parseFundamentalType ns n
                 [n] -> do
                   ns <- currentNamespace
                   parseFundamentalType ns n
                 _ -> parseError $ "Unsupported type form: \""
                                   <> typeName <> "\""

-- | Find the children giving the type of the given element.
parseTypeElements :: Parser [Type]
parseTypeElements = (++) <$> parseChildrenWithLocalName "type" parseTypeInfo <*>
                    parseChildrenWithLocalName "array" parseArrayInfo

-- | Try to find a type node, but do not error out if it is not
-- found. This _does_ give an error if more than one type node is
-- found.
queryType :: Parser (Maybe Type)
queryType = parseTypeElements >>= \case
            [e] -> return (Just e)
            [] -> return Nothing
            _ -> parseError $ "Found more than one type for the element."

-- | Parse the type of a node (which will be described by a child node
-- named "type" or "array").
parseType :: Parser Type
parseType = parseTypeElements >>= \case
            [e] -> return e
            [] -> parseError $ "Did not find a type for the element."
            _ -> parseError $ "Found more than one type for the element."