packages feed

haskell-gi-0.26.19: lib/Data/GI/GIR/Field.hs

-- | Parsing of object/struct/union fields.
module Data.GI.GIR.Field
    ( Field(..)
    , AnonymousMember(..)
    , FieldInfoFlag
    , parseFields
    ) where

import Control.Monad.Except (catchError, throwError)

import Data.Maybe (isJust, catMaybes)
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid ((<>))
#endif
import Data.Text (Text, isSuffixOf)

import Data.GI.GIR.BasicTypes (Type(..), BasicType(..))
import Data.GI.GIR.Callback (Callback, parseCallback)
import Data.GI.GIR.Type (parseType, queryElementCType)
import Data.GI.GIR.Parser

data Field = Field {
      fieldName :: Text,
      fieldVisible :: Bool,
      fieldType :: Type,
      fieldIsPointer :: Maybe Bool, -- ^ `Nothing` if not known.
      fieldArrayOfPointers :: Bool,
      -- ^ For C arrays stored inline in the struct (that is, with a
      -- fixed size), whether the elements are pointers.
      fieldCallback :: Maybe Callback,
      fieldOffset :: Int,
      -- ^ For bitfields, this is the offset of the storage unit
      -- containing the bitfield.
      fieldBits :: Maybe Int,
      -- ^ The width in bits, if the field is a bitfield.
      fieldBitShift :: Maybe Int,
      -- ^ For bitfields, the position of the least significant bit
      -- of the bitfield inside its storage unit (a value of the
      -- declared type of the field, stored at `fieldOffset`). This
      -- is `Nothing` if the layout of the bitfield could not be
      -- determined.
      fieldAnonymous :: Maybe AnonymousMember,
      -- ^ If this member is an (anonymous type) union or struct
      -- declared inline, its contents. In this case `fieldType`
      -- is meaningless, and the field is never visible. We keep
      -- track of these members since they affect the layout of
      -- the containing struct or union.
      fieldFlags :: [FieldInfoFlag],
      fieldDocumentation :: Documentation,
      fieldDeprecated :: Maybe DeprecationInfo }
    deriving Show

data FieldInfoFlag = FieldIsReadable | FieldIsWritable
                   deriving Show

-- | Contents of a union or struct declared inline in a struct or
-- union.
data AnonymousMember = AnonymousUnion [Field]
                     | AnonymousStruct [Field]
                     deriving Show

-- | Parse a single field in a struct or union. We parse
-- non-introspectable fields too (but set fieldVisible = False for
-- them), this is necessary since they affect the computation of
-- offsets of fields and sizes of containing structs.
parseField :: Parser (Maybe Field)
parseField = do
  name <- getAttr "name"
  deprecated <- parseDeprecation
  bits <- optionalAttr "bits" Nothing (fmap Just . parseIntegral)
  readable <- optionalAttr "readable" True parseBool
  writable <- optionalAttr "writable" False parseBool
  let flags = if readable then [FieldIsReadable] else []
             <> if writable then [FieldIsWritable] else []
  introspectable <- optionalAttr "introspectable" True parseBool
  private <- optionalAttr "private" False parseBool
  doc <- parseDocumentation
  -- Sometimes fields marked as not introspectable contain invalid
  -- introspection info. We are lenient in these cases with parsing
  -- errors, and simply ignore the fields.
  flip catchError (\e -> if not introspectable
                         then return Nothing
                         else throwError e) $ do
    (t, isPtr, callback) <-
      if introspectable
      then do
        callbacks <- parseChildrenWithLocalName "callback" parseCallback
        (cbn, callback) <- case callbacks of
                             [] -> return (Nothing, Nothing)
                             [(n, cb)] -> return (Just n, Just cb)
                             _ -> parseError "Multiple callbacks in field"
        (t, isPtr) <- case cbn of
               Nothing -> do
                 t <- parseType
                 ct <- queryElementCType
                 return (t, fmap ("*" `isSuffixOf`) ct)
               Just n -> return (TInterface n, Nothing)
        return (t, isPtr, callback)
      else do
        callbacks <- parseAllChildrenWithLocalName "callback" parseName
        case callbacks of
          [] -> do
               t <- parseType
               ct <- queryElementCType
               return (t, fmap ("*" `isSuffixOf`) ct, Nothing)
          [n] -> return (TInterface n, Just True, Nothing)
          _ -> parseError "Multiple callbacks in field"

    elementCTypes <- parseAllChildrenWithLocalName "array" queryElementCType
    let arrayOfPointers = case elementCTypes of
          [Just ct] -> "*" `isSuffixOf` ct
          _ -> False

    return $ Just $ Field {
               fieldName = name
             , fieldVisible = introspectable && not private
             , fieldType = t
             , fieldIsPointer = if isJust callback
                                then Just True
                                else isPtr
             , fieldArrayOfPointers = arrayOfPointers
             , fieldCallback = callback
             , fieldOffset = error ("unfixed field offset " ++ show name)
             , fieldBits = if bits == Just 0 then Nothing else bits
             , fieldBitShift = Nothing
             , fieldAnonymous = Nothing
             , fieldFlags = flags
             , fieldDocumentation = doc
             , fieldDeprecated = deprecated
          }

-- | Parse an union or struct declared inline in a struct or union.
parseAnonymousMember :: Bool -> Parser Field
parseAnonymousMember isUnion = do
  name <- optionalAttr "name" (if isUnion
                               then "<anonymous union>"
                               else "<anonymous struct>") return
  -- We do not keep track of the offsets of the members of the
  -- anonymous union or struct, only of its total size.
  members <- map (\f -> f {fieldOffset = 0}) <$> parseFields
  doc <- parseDocumentation
  return $ Field {
               fieldName = name
             , fieldVisible = False
             , fieldType = TBasicType TPtr -- Not meaningful
             , fieldIsPointer = Just False
             , fieldArrayOfPointers = False
             , fieldCallback = Nothing
             , fieldOffset = 0
             , fieldBits = Nothing
             , fieldBitShift = Nothing
             , fieldAnonymous = Just (if isUnion
                                      then AnonymousUnion members
                                      else AnonymousStruct members)
             , fieldFlags = []
             , fieldDocumentation = doc
             , fieldDeprecated = Nothing
          }

-- | Parse the members of a struct or union, in order.
parseFields :: Parser [Field]
parseFields = catMaybes <$>
  parseAllChildrenWithLocalNames ["field", "union", "record"] parseMember
  where parseMember :: Text -> Parser (Maybe Field)
        parseMember "union" = Just <$> parseAnonymousMember True
        parseMember "record" = Just <$> parseAnonymousMember False
        parseMember _ = parseField