------------------------------------------------------------------------------
--- A generator for XML data conversion.
---
--- If this program is applied to some Curry module,
--- it generates a new Curry module containing conversion functions
--- from and to an XML representation for all data types declared
--- in this module.
---
--- For instance, if `Nat` is a module containing the declaration
---
--- data Nat = Z | S Nat
---
--- applying this program to `Nat` generates a new module `NatDataToXml`
--- containing the implementation of the following operations:
---
--- natToXml :: Nat -> XmlExp
--- xmlToNat :: XmlExp -> Nat
---
--- Hence, one can store a `Nat` term `num` into the file `Nat.xml` by
---
--- writeXmlFile "Nat.xml" (natToXml num)
---
--- provided that the module `XML` is imported. Similarly, one can read
--- the data from this file by
---
--- readXmlFile "Nat.xml" >>= return . xmlToNat
---
--- @author Bernd Brassel, Michael Hanus
--- @version December 2018
------------------------------------------------------------------------------
module Data2Xml where
import Char
import FilePath ( (>) )
import List
import System
import AbstractCurry.Types
import AbstractCurry.Files
import AbstractCurry.Select
import AbstractCurry.Build
import AbstractCurry.Pretty ( showCProg )
import System.CurryPath ( stripCurrySuffix )
data Option = LowCase | FileName String | OutDir String
deriving Eq
main :: IO ()
main = do
args <- getArgs
derive (reverse (argsToOptions args))
printUsage :: IO ()
printUsage = putStrLn $ unlines
[ "Usage:"
, ""
, " data2xml [-l] [-d
] "
, ""
, "Options:"
, "-l : make all tags lowercase"
, "-d : write conversion program into directory (default: .)"
]
argsToOptions :: [String] -> [Option]
argsToOptions args = case args of
["-h"] -> []
["--help"] -> []
["-?"] -> []
"-l" : opts -> LowCase : argsToOptions opts
"-d" : f : opts -> OutDir f : argsToOptions opts
[s] -> [FileName (stripCurrySuffix s)]
_ -> []
outDirOf :: [Option] -> String
outDirOf [] = "."
outDirOf (o : os) = case o of OutDir d -> d
_ -> outDirOf os
derive :: [Option] -> IO ()
derive [] = printUsage
derive (LowCase : _ ) = printUsage
derive (OutDir _ : _ ) = printUsage
derive (FileName fn : opts) = do
CurryProg modName _ _ _ _ ts _ _ <- readCurry fn
let (specials,types) = if isPrelude modName
then (specialFuncs opts, filterSpecials ts)
else ([],ts)
progName = transModName fn
impTypes = maybeString $ nub $ filter ((/=modName) .fst)
$ concatMap requiredTypesTypeDecl types
imports <- importTypes modName impTypes
let outfile = outDirOf opts > progName ++ ".curry"
writeFile outfile $ showCProg $
CurryProg progName (nub $ ["XML",fn] ++ imports) Nothing [] [] []
(map (mkType2Xml opts) types ++
map (mkXml2Type opts) types ++ specials)
[]
putStrLn ("You can now import: " ++ outfile)
maybeString :: [QName] -> [QName]
maybeString xs
= if notElem (pre "String") xs && elem (pre "[]") xs && elem (pre "Char") xs
then (pre "String" : xs)
else xs
-- Transform original module name into name of the transformation module.
-- Hierarchical module names are "flattened" by replacing dots with underscores.
transModName :: String -> String
transModName mn = map dot2us mn ++ "DataToXml"
where dot2us c = if c=='.' then '_' else c
----------------------------
-- naming the new functions
----------------------------
toXmlName :: QName -> QName
toXmlName (m,s) = case (isPrelude m, s, isTupleName s) of
(True,"[]",_) -> (nm,"list_To_Xml")
(True,"()",_) -> (nm,"unitToXml")
(True,_,True) -> (nm,"tuple"++show (length s-1)++"ToXml")
(_,c:cs,_) -> (nm,toLower c:cs ++ "ToXml")
(_, [], _) -> error "Data2Xml.toXmlName: empty identifier"
where nm = transModName m
fromXmlName :: QName -> QName
fromXmlName (m,s) = case (isPrelude m,s,isTupleName s) of
(True,"[]",_) -> (nm,"xml_To_List")
(True,"()",_) -> (nm,"xmlToUnit")
(True,_,True) -> (nm,"xmlToTuple"++show (length s-1))
_ -> (nm,"xmlTo"++s)
where nm = transModName m
listTag :: [Option] -> String
listTag opts = tag opts "List"
isTupleName :: String -> Bool
isTupleName [] = False
isTupleName (n:name) = n == '(' && isTuple name
where
isTuple "" = False
isTuple [c] = c == ')'
isTuple (c:c':cs) = c==',' && isTuple (c':cs)
----------------------------
-- generating tags
----------------------------
tag :: [Option] -> String -> String
tag opts s = if LowCase `elem` opts then map toLower s else s
tagNameForCons :: QName -> String
tagNameForCons (mname,cname)
| isTupleName cname = "Tuple" ++ show (length cname - 1)
| isPrelude mname = cname
| otherwise = mname++"_"++cname
-------------------------------------------------
-- make functions to transform data terms to xml
-------------------------------------------------
mkType2Xml :: [Option] -> CTypeDecl -> CFuncDecl
mkType2Xml _ (CTypeSyn name vis vars texp) =
stFunc (toXmlName name) 1 vis
(CFuncType (applyTC name (map CTVar vars)) xmlType)
[simpleRule [CPVar (0,"x0")] (call2xml (texp,0))]
mkType2Xml opts (CType name vis vars cs _) =
stFunc (toXmlName name) (1+length vars) vis
(type2XmlType vars
(CFuncType (applyTC name (map CTVar vars)) xmlType))
(map (mkConsDecl2Xml opts $ map (CPVar . renVar) vars) cs)
mkType2Xml _ (CNewType _ _ _ _ _) =
error "Data2Xml.mkType2Xml: CNewType not yet implemented!"
mkConsDecl2Xml :: [Option] -> [CPattern] -> CConsDecl -> CRule
mkConsDecl2Xml opts patVars (CCons _ _ name _ args) =
simpleRule (newPatVars++[CPComb name (pVars arity)])
(xml opts (tagNameForCons name) []
(map call2xml (zip args [0..])))
where
arity = length args
newPatVars = renameUnused (map renVar $ concatMap tvarsOfType args) patVars
mkConsDecl2Xml _ _ (CRecord _ _ _ _ _)
= error "Data2Xml.mkConsDecl2Xml: CRecord not yet implemented!"
type2XmlType :: [(Int,String)] -> CTypeExpr -> CTypeExpr
type2XmlType vars t
= foldr CFuncType t (map (\x->CFuncType (CTVar x) xmlType) vars)
call2xml :: (CTypeExpr,Int) -> CExpr
call2xml (t,i) = CApply (call2xmlType t) (toVar i)
call2xmlType :: CTypeExpr -> CExpr
call2xmlType (CTVar v) = CVar (renVar v)
call2xmlType (CTCons name) = constF (toXmlName name)
call2xmlType t@(CTApply _ _) =
maybe (error $ "unable to transform type applications to XML: " ++ show t)
(\ (name,args) ->
if name == pre "[]" && args == [charType]
then constF (toXmlName (pre "String"))
else applyF (toXmlName name) (map call2xmlType args))
(tconsArgsOfType t)
call2xmlType t@(CFuncType _ _) =
error $ "unable to transform function types to XML: " ++ show t
xml :: [Option] -> String -> [CExpr] -> [CExpr] -> CExpr
xml opts name attrs elems
= applyF ("XML","XElem")
[string2ac (tag opts name), list2ac attrs, list2ac elems]
xmlType :: CTypeExpr
xmlType = baseType ("XML","XmlExp")
-------------------------------------------------
-- make functions to transform xml to data terms
-------------------------------------------------
mkXml2Type :: [Option] -> CTypeDecl -> CFuncDecl
mkXml2Type _ (CTypeSyn name vis vars texp) =
stFunc (fromXmlName name) 1 vis
(CFuncType xmlType (applyTC name (map CTVar vars)))
[simpleRule [CPVar (0,"x0")] (callXml2 (texp,0))]
mkXml2Type opts (CType name vis vars cs _) =
stFunc (fromXmlName name) (1+length vars) vis
(xml2typeType vars
(CFuncType xmlType (applyTC name (map CTVar vars))))
(map (mkXml2ConsDecl opts $ map (CPVar . renVar) vars) cs)
mkXml2Type _ (CNewType _ _ _ _ _) =
error "Data2Xml.mkXml2Type: CNewType not yet implemented!"
renVar :: (a,String) -> (a,String)
renVar (i,s) = case s of
('x':xs) -> (i,'t':xs)
_ -> (i,s)
xml2typeType :: [(Int,String)] -> CTypeExpr -> CTypeExpr
xml2typeType vars t
= foldr CFuncType t (map (\x->CFuncType xmlType (CTVar x)) vars)
mkXml2ConsDecl :: [Option] -> [CPattern] -> CConsDecl -> CRule
mkXml2ConsDecl opts patVars (CCons _ _ name _ args)
= simpleRule (newPatVars++[pxml opts (tagNameForCons name) [] (pVars arity)])
(applyF name (map callXml2 (zip args [0..])))
where
arity = length args
newPatVars = renameUnused (map renVar $ concatMap tvarsOfType args) patVars
mkXml2ConsDecl _ _ (CRecord _ _ _ _ _)
= error "Data2Xml.mkXml2ConsDecl: CRecord not yet implemented!"
renameUnused :: [(Int,String)] -> [CPattern] -> [CPattern]
renameUnused _ [] = []
renameUnused usedVars (p:ps) = case p of
CPVar (i,v) | elem (i,v) usedVars -> CPVar (i,v) : renameUnused usedVars ps
| otherwise -> CPVar (i,"_") : renameUnused usedVars ps
_ -> p : renameUnused usedVars ps
pxml :: [Option] -> String -> [CPattern] -> [CPattern] -> CPattern
pxml opts name attrs elems
= CPComb ("XML","XElem")
[stringPattern (tag opts name), listPattern attrs, listPattern elems]
callXml2 :: (CTypeExpr,Int) -> CExpr
callXml2 (t,i) = CApply (callXml2Type t) (toVar i)
callXml2Type :: CTypeExpr -> CExpr
callXml2Type (CTVar v) = CVar (renVar v)
callXml2Type (CTCons name) = constF (fromXmlName name)
callXml2Type t@(CTApply _ _) =
maybe (error $ "unable to transform type applications from XML: " ++ show t)
(\ (name,args) ->
if name == pre "[]" && args == [charType]
then constF (fromXmlName (pre "String"))
else applyF (fromXmlName name) (map callXml2Type args))
(tconsArgsOfType t)
callXml2Type t@(CFuncType _ _) =
error $ "unable to transform functions from XML: " ++ show t
-----------------------------
-- treat imported data types
-----------------------------
importTypes :: String -> [QName] -> IO ([String])
importTypes m ts = do
let imps = nub (map importType ts)
let specials = if isPrelude m then ["Read","ReadShowTerm"] else []
imessage imps
return (imps++specials)
imessage :: [String] -> IO ()
imessage [] = done
imessage [m] = putStrLn $ "You also need to generate the module "++m
imessage (m:m':ms) =
putStrLn $ "You also need to generate the modules "++(unwords $ m:m':ms)
importType :: QName -> String
importType (m,f)
| isPrelude m && elem f ["String","[]","Char","Int","Float"]
= "PreludeDataToXml"
| isPrelude m && f == "IO"
= error "unable to transform I/O actions to XML"
| isPrelude m && f == "Success"
= error "unable to transform constraints to XML"
| otherwise = m++"DataToXml"
-----------------------------------------
-- treat special prelude types
-----------------------------------------
specialNames :: [String]
specialNames =
["Int","Float","String","Char","IO","Success","[]","()","(,)"
,"DET","ShowS","ReadS"]
filterSpecials :: [CTypeDecl] -> [CTypeDecl]
filterSpecials
= filter ((\ (m,n) -> not (isPrelude m && elem n specialNames)) . typeName)
specialFuncs :: [Option] -> [CFuncDecl]
specialFuncs opts =
[mkList2xml opts,mkXml2List opts] ++
concatMap (\tname -> [baseType2xml opts tname, baseTypeXml2 opts tname])
["String","Int","Float","Char"] ++
concatMap (\tdecl -> [mkType2Xml opts tdecl, mkXml2Type opts tdecl])
(map mkTupleType (0:[2..12]))
-- make tuple type of arity n:
mkTupleType :: Int -> CTypeDecl
mkTupleType n =
CType (pre tcons) Public tvars
[simpleCCons (pre tcons) Public (map CTVar tvars)] []
where tcons = "(" ++ take (n-1) (repeat ',') ++ ")"
tvars = map (\i -> (i,'a':show i)) [1..n]
mkList2xml :: [Option] -> CFuncDecl
mkList2xml opts =
stFunc (toXmlName (pre "[]")) 2 Public
(CFuncType (CFuncType (CTVar (0,"a")) xmlType)
(CFuncType (listType (CTVar (0,"a"))) xmlType))
[simpleRule (pVars 2)
(applyF ("XML","XElem")
[string2ac (listTag opts), list2ac [],
applyE (applyF (pre "map") [toVar 0]) [toVar 1]])]
mkXml2List :: [Option] -> CFuncDecl
mkXml2List opts =
stFunc (fromXmlName (pre "[]")) 2 Public
(CFuncType (CFuncType xmlType (CTVar (0,"a")))
(CFuncType xmlType (listType (CTVar (0,"a")))))
[simpleRule
[x, CPComb ("XML","XElem") [stringPattern (listTag opts),pNil,y]]
(applyF (pre "map") [toVar 0,toVar 1])]
where
[x,y] = pVars 2
baseType2xml :: [Option] -> String -> CFuncDecl
baseType2xml opts s
= stFunc (toXmlName (pre s)) 1 Public
(CFuncType (baseType (pre s)) xmlType)
[simpleRule (pVars 1) (xml opts s [] [writeFun s])]
baseTypeXml2 :: [Option] -> String -> CFuncDecl
baseTypeXml2 opts s =
stFunc (fromXmlName (pre s)) 1 Public
(CFuncType xmlType (baseType (pre s)))
(simpleRule [pxml opts s [] [CPComb ("XML","XText") (pVars 1)]] (readFun s)
: if s=="String"
then [simpleRule [pxml opts s [] []] (string2ac "")]
else [])
readFun :: String -> CExpr
readFun typ = case typ of
"Int" -> applyF ("Read","readInt") [toVar 0]
"Char" -> applyF (pre "head") [toVar 0]
"Float" -> applyF ("ReadShowTerm","readQTerm") [toVar 0]
"String" -> toVar 0
_ -> error ("Dta2Xml.readFun: unknown type " ++ typ)
writeFun :: String -> CExpr
writeFun s = case s of
"String" -> applyF ("XML","xtxt") [toVar 0]
"Char" -> applyF ("XML","xtxt") [list2ac [toVar 0]]
_ -> applyF ("XML","xtxt") [applyF (pre "show") [toVar 0]]
---------------------------------------------------------------------
-- Auxiliaries:
--- yield list of all types the given type depends on
requiredTypesTypeDecl :: CTypeDecl -> [QName]
requiredTypesTypeDecl (CTypeSyn _ _ _ e ) = requiredTypesTypeExpr e
requiredTypesTypeDecl (CType _ _ _ cs _) = concatMap requiredTypesConsDecl cs
requiredTypesTypeDecl (CNewType _ _ _ cd _) = requiredTypesConsDecl cd
requiredTypesConsDecl :: CConsDecl -> [QName]
requiredTypesConsDecl (CCons _ _ _ _ ts) = concatMap requiredTypesTypeExpr ts
requiredTypesConsDecl (CRecord _ _ _ _ fs) = concatMap requiredTypesFieldDecl fs
where requiredTypesFieldDecl (CField _ _ t) = requiredTypesTypeExpr t
requiredTypesTypeExpr :: CTypeExpr -> [QName]
requiredTypesTypeExpr (CTVar _) = []
requiredTypesTypeExpr (CTCons tc) = [tc]
requiredTypesTypeExpr (CTApply tc ta)
| tc == CTCons (pre "[]") && ta == charType
= [pre "String"]
| otherwise
= requiredTypesTypeExpr tc ++ requiredTypesTypeExpr ta
requiredTypesTypeExpr (CFuncType e1 e2)
= requiredTypesTypeExpr e1 ++ requiredTypesTypeExpr e2
---------------------------------------------------------------------