module Data.XCB.FromXML(fromFiles
,fromStrings
) where
import Data.XCB.Types
import Data.XCB.Utils
import Text.XML.Light
import Data.List as List
import qualified Data.Map as Map
import Data.Maybe
import Control.Applicative ((<$>))
import Control.Monad
import Control.Monad.Reader
import System.IO (openFile, IOMode (ReadMode), hSetEncoding, utf8, hGetContents)
fromFiles :: [FilePath] -> IO [XHeader]
fromFiles xs = do
strings <- sequence $ map readFileUTF8 xs
return $ fromStrings strings
readFileUTF8 :: FilePath -> IO String
readFileUTF8 fp = do
h <- openFile fp ReadMode
hSetEncoding h utf8
hGetContents h
fromStrings :: [String] -> [XHeader]
fromStrings xs =
let rs = mapAlt fromString xs
Just headers = runReaderT rs headers
in headers
type Parse = ReaderT ([XHeader],Name) Maybe
localName :: Parse Name
localName = snd `liftM` ask
allModules :: Parse [XHeader]
allModules = fst `liftM` ask
extractAlignment :: (MonadPlus m, Functor m) => [Element] -> m (Maybe Alignment, [Element])
extractAlignment (el : xs) | el `named` "required_start_align" = do
align <- el `attr` "align" >>= readM
offset <- el `attr` "offset" >>= readM
return (Just (Alignment align offset), xs)
| otherwise = return (Nothing, el : xs)
extractAlignment xs = return (Nothing, xs)
lookupThingy :: ([XDecl] -> Maybe a)
-> (Maybe Name)
-> Parse (Maybe a)
lookupThingy f Nothing = do
lname <- localName
liftM2 mplus (lookupThingy f $ Just lname)
(lookupThingy f $ Just "xproto")
lookupThingy f (Just mname) = do
xs <- allModules
return $ do
x <- findXHeader mname xs
f $ xheader_decls x
lookupEvent :: Maybe Name -> Name -> Parse (Maybe EventDetails)
lookupEvent mname evname = flip lookupThingy mname $ \decls ->
findEvent evname decls
lookupError :: Maybe Name -> Name -> Parse (Maybe ErrorDetails)
lookupError mname ername = flip lookupThingy mname $ \decls ->
findError ername decls
findXHeader :: Name -> [XHeader] -> Maybe XHeader
findXHeader name = List.find $ \ x -> xheader_header x == name
findError :: Name -> [XDecl] -> Maybe ErrorDetails
findError pname xs =
case List.find f xs of
Nothing -> Nothing
Just (XError name code alignment elems) -> Just $ ErrorDetails name code alignment elems
_ -> error "impossible: fatal error in Data.XCB.FromXML.findError"
where f (XError name _ _ _) | name == pname = True
f _ = False
findEvent :: Name -> [XDecl] -> Maybe EventDetails
findEvent pname xs =
case List.find f xs of
Nothing -> Nothing
Just (XEvent name code alignment elems noseq) ->
Just $ EventDetails name code alignment elems noseq
_ -> error "impossible: fatal error in Data.XCB.FromXML.findEvent"
where f (XEvent name _ _ _ _) | name == pname = True
f _ = False
data EventDetails = EventDetails Name Int (Maybe Alignment) [StructElem] (Maybe Bool)
data ErrorDetails = ErrorDetails Name Int (Maybe Alignment) [StructElem]
fromString :: String -> ReaderT [XHeader] Maybe XHeader
fromString str = do
el@(Element _qname _ats cnt _) <- lift $ parseXMLDoc str
guard $ el `named` "xcb"
header <- el `attr` "header"
let name = el `attr` "extension-name"
xname = el `attr` "extension-xname"
maj_ver = el `attr` "major-version" >>= readM
min_ver = el `attr` "minor-version" >>= readM
multiword = el `attr` "extension-multiword" >>= readM . ensureUpper
decls <- withReaderT (\r -> (r,header)) $ extractDecls cnt
return $ XHeader {xheader_header = header
,xheader_xname = xname
,xheader_name = name
,xheader_multiword = multiword
,xheader_major_version = maj_ver
,xheader_minor_version = min_ver
,xheader_decls = decls
}
extractDecls :: [Content] -> Parse [XDecl]
extractDecls = mapAlt declFromElem . onlyElems
declFromElem :: Element -> Parse XDecl
declFromElem el
| el `named` "request" = xrequest el
| el `named` "event" = xevent el
| el `named` "eventcopy" = xevcopy el
| el `named` "error" = xerror el
| el `named` "errorcopy" = xercopy el
| el `named` "struct" = xstruct el
| el `named` "union" = xunion el
| el `named` "xidtype" = xidtype el
| el `named` "xidunion" = xidunion el
| el `named` "typedef" = xtypedef el
| el `named` "enum" = xenum el
| el `named` "import" = ximport el
| el `named` "eventstruct" = xeventstruct el
| otherwise = mzero
ximport :: Element -> Parse XDecl
ximport = return . XImport . strContent
xenum :: Element -> Parse XDecl
xenum el = do
nm <- el `attr` "name"
fields <- mapAlt enumField $ elChildren el
guard $ not $ null fields
return $ XEnum nm fields
enumField :: Element -> Parse (EnumElem Type)
enumField el = do
guard $ el `named` "item"
name <- el `attr` "name"
let expr = firstChild el >>= expression
return $ EnumElem name expr
xrequest :: Element -> Parse XDecl
xrequest el = do
nm <- el `attr` "name"
code <- el `attr` "opcode" >>= readM
(alignment, xs) <- extractAlignment $ elChildren el
fields <- mapAlt structField $ xs
let reply = getReply el
return $ XRequest nm code alignment fields reply
getReply :: Element -> Maybe XReply
getReply el = do
childElem <- unqual "reply" `findChild` el
(alignment, xs) <- extractAlignment $ elChildren childElem
fields <- mapM structField xs
guard $ not $ null fields
return $ GenXReply alignment fields
xevent :: Element -> Parse XDecl
xevent el = do
name <- el `attr` "name"
number <- el `attr` "number" >>= readM
let noseq = ensureUpper `liftM` (el `attr` "no-sequence-number") >>= readM
(alignment, xs) <- extractAlignment (elChildren el)
fields <- mapM structField $ xs
guard $ not $ null fields
return $ XEvent name number alignment fields noseq
xevcopy :: Element -> Parse XDecl
xevcopy el = do
name <- el `attr` "name"
number <- el `attr` "number" >>= readM
ref <- el `attr` "ref"
let (mname,evname) = splitRef ref
details <- lookupEvent mname evname
return $ let EventDetails _ _ alignment fields noseq =
case details of
Nothing ->
error $ "Unresolved event: " ++ show mname ++ " " ++ ref
Just x -> x
in XEvent name number alignment fields noseq
mkType :: String -> Type
mkType str =
let (mname, name) = splitRef str
in case mname of
Just modifier -> QualType modifier name
Nothing -> UnQualType name
splitRef :: Name -> (Maybe Name, Name)
splitRef ref = case split ':' ref of
(x,"") -> (Nothing, x)
(a, b) -> (Just a, b)
split :: Char -> String -> (String, String)
split c = go
where go [] = ([],[])
go (x:xs) | x == c = ([],xs)
| otherwise =
let (lefts, rights) = go xs
in (x:lefts,rights)
xerror :: Element -> Parse XDecl
xerror el = do
name <- el `attr` "name"
number <- el `attr` "number" >>= readM
(alignment, xs) <- extractAlignment $ elChildren el
fields <- mapM structField $ xs
return $ XError name number alignment fields
xercopy :: Element -> Parse XDecl
xercopy el = do
name <- el `attr` "name"
number <- el `attr` "number" >>= readM
ref <- el `attr` "ref"
let (mname, ername) = splitRef ref
details <- lookupError mname ername
return $ uncurry (XError name number) $ case details of
Nothing -> error $ "Unresolved error: " ++ show mname ++ " " ++ ref
Just (ErrorDetails _ _ alignment elems) -> (alignment, elems)
xstruct :: Element -> Parse XDecl
xstruct el = do
name <- el `attr` "name"
(alignment, xs) <- extractAlignment $ elChildren el
fields <- mapAlt structField $ xs
guard $ not $ null fields
return $ XStruct name alignment fields
xunion :: Element -> Parse XDecl
xunion el = do
name <- el `attr` "name"
(alignment, xs) <- extractAlignment $ elChildren el
fields <- mapAlt structField $ xs
guard $ not $ null fields
return $ XUnion name alignment fields
xidtype :: Element -> Parse XDecl
xidtype el = liftM XidType $ el `attr` "name"
xidunion :: Element -> Parse XDecl
xidunion el = do
name <- el `attr` "name"
let types = mapMaybe xidUnionElem $ elChildren el
guard $ not $ null types
return $ XidUnion name types
xidUnionElem :: Element -> Maybe XidUnionElem
xidUnionElem el = do
guard $ el `named` "type"
return $ XidUnionElem $ mkType $ strContent el
xtypedef :: Element -> Parse XDecl
xtypedef el = do
oldtyp <- liftM mkType $ el `attr` "oldname"
newname <- el `attr` "newname"
return $ XTypeDef newname oldtyp
xeventstruct :: Element -> Parse XDecl
xeventstruct el = do
name <- el `attr` "name"
allowed <- mapAlt allowedEvent $ elChildren el
return $ XEventStruct name allowed
allowedEvent :: (MonadPlus m, Functor m) => Element -> m AllowedEvent
allowedEvent el = do
extension <- el `attr` "name"
xge <- el `attr` "xge" >>= readM
opMin <- el `attr` "opcode-min" >>= readM
opMax <- el `attr` "opcode-max" >>= readM
return $ AllowedEvent extension xge opMin opMax
structField :: (MonadPlus m, Functor m) => Element -> m StructElem
structField el
| el `named` "field" = do
typ <- liftM mkType $ el `attr` "type"
let enum = liftM mkType $ el `attr` "enum"
let mask = liftM mkType $ el `attr` "mask"
name <- el `attr` "name"
return $ SField name typ enum mask
| el `named` "pad" = do
bytes <- el `attr` "bytes" >>= readM
return $ Pad bytes
| el `named` "list" = do
typ <- liftM mkType $ el `attr` "type"
name <- el `attr` "name"
let enum = liftM mkType $ el `attr` "enum"
let expr = firstChild el >>= expression
return $ List name typ expr enum
| el `named` "valueparam" = do
mask_typ <- liftM mkType $ el `attr` "value-mask-type"
mask_name <- el `attr` "value-mask-name"
let mask_pad = el `attr` "value-mask-pad" >>= readM
list_name <- el `attr` "value-list-name"
return $ ValueParam mask_typ mask_name mask_pad list_name
| el `named` "switch" = do
nm <- el `attr` "name"
(exprEl,caseEls) <- unconsChildren el
expr <- expression exprEl
(alignment, xs) <- extractAlignment $ caseEls
cases <- mapM bitCase xs
return $ Switch nm expr alignment cases
| el `named` "exprfield" = do
typ <- liftM mkType $ el `attr` "type"
name <- el `attr` "name"
expr <- firstChild el >>= expression
return $ ExprField name typ expr
| el `named` "reply" = fail ""
| el `named` "doc" = do
fields <- el `children` "field"
let mkField = \x -> fmap (\y -> (y, strContent x)) $ x `attr` "name"
fields' = Map.fromList $ catMaybes $ map mkField fields
sees = findChildren (unqual "see") el
sees' = catMaybes $ flip map sees $ \s -> do typ <- s `attr` "type"
name <- s `attr` "name"
return (typ, name)
brief = fmap strContent $ findChild (unqual "brief") el
return $ Doc brief fields' sees'
| el `named` "fd" = do
name <- el `attr` "name"
return $ Fd name
| otherwise = let name = elName el
in error $ "I don't know what to do with structelem "
++ show name
bitCase :: (MonadPlus m, Functor m) => Element -> m BitCase
bitCase el | el `named` "bitcase" || el `named` "case" = do
let mName = el `attr` "name"
(exprEl, fieldEls) <- unconsChildren el
expr <- expression exprEl
(alignment, xs) <- extractAlignment $ fieldEls
fields <- mapM structField xs
return $ BitCase mName expr alignment fields
| otherwise =
let name = elName el
in error $ "Invalid bitCase: " ++ show name
expression :: (MonadPlus m, Functor m) => Element -> m XExpression
expression el | el `named` "fieldref"
= return $ FieldRef $ strContent el
| el `named` "enumref" = do
enumTy <- mkType <$> el `attr` "ref"
let enumVal = strContent el
guard $ enumVal /= ""
return $ EnumRef enumTy enumVal
| el `named` "value"
= Value `liftM` readM (strContent el)
| el `named` "bit"
= Bit `liftM` do
n <- readM (strContent el)
guard $ n >= 0
return n
| el `named` "op" = do
binop <- el `attr` "op" >>= toBinop
[exprLhs,exprRhs] <- mapM expression $ elChildren el
return $ Op binop exprLhs exprRhs
| el `named` "unop" = do
op <- el `attr` "op" >>= toUnop
expr <- firstChild el >>= expression
return $ Unop op expr
| el `named` "popcount" = do
expr <- firstChild el >>= expression
return $ PopCount expr
| el `named` "sumof" = do
ref <- el `attr` "ref"
return $ SumOf ref
| el `named` "paramref"
= return $ ParamRef $ strContent el
| otherwise =
let nm = elName el
in error $ "Unknown epression " ++ show nm ++ " in Data.XCB.FromXML.expression"
toBinop :: MonadPlus m => String -> m Binop
toBinop "+" = return Add
toBinop "-" = return Sub
toBinop "*" = return Mult
toBinop "/" = return Div
toBinop "&" = return And
toBinop "&" = return And
toBinop ">>" = return RShift
toBinop _ = mzero
toUnop :: MonadPlus m => String -> m Unop
toUnop "~" = return Complement
toUnop _ = mzero
firstChild :: MonadPlus m => Element -> m Element
firstChild = listToM . elChildren
unconsChildren :: MonadPlus m => Element -> m (Element, [Element])
unconsChildren el
= case elChildren el of
(x:xs) -> return (x,xs)
_ -> mzero
listToM :: MonadPlus m => [a] -> m a
listToM [] = mzero
listToM (x:_) = return x
named :: Element -> String -> Bool
named (Element qname _ _ _) name | qname == unqual name = True
named _ _ = False
attr :: MonadPlus m => Element -> String -> m String
(Element _ xs _ _) `attr` name = case List.find p xs of
Just (Attr _ res) -> return res
_ -> mzero
where p (Attr qname _) | qname == unqual name = True
p _ = False
children :: MonadPlus m => Element -> String -> m [Element]
(Element _ _ xs _) `children` name = case List.filter p xs of
[] -> mzero
some -> return $ onlyElems some
where p (Elem (Element n _ _ _)) | n == unqual name = True
p _ = False
readM :: (MonadPlus m, Read a) => String -> m a
readM = liftM fst . listToM . reads