-- | SiffML : Sifflet Markup Language. -- An XML application for storing and retrieving Sifflet programs -- and libraries. module SiffML ( ToXml(..) , produceFile , consumeFile , Functions(..) , xmlToFunctions , testOut -- testing , xmlToX -- testing , testIn, testFromFile -- testing ) where import Text.XML.HXT.Arrow import Text.XML.HXT.DOM.FormatXmlTree import Expr import Examples import Util class ToXml a where toXml :: a -> Producer -- | A Producer produces XML type Producer = IOSLA (XIOState ()) XmlTree XmlTree -- | A Consumer consumes XML type Consumer a b = IOSLA (XIOState ()) a b defaultOptions = [(a_indent, v_yes), (a_validate, v_no)] produceStdout :: (ToXml a) => a -> IO () produceStdout src = produceFile src "-" produceFile :: (ToXml a) => a -> FilePath -> IO () produceFile src path = let arrow :: Producer arrow = toXml src options = defaultOptions in do { putStrLn "" ; [rc] <- runX (root [] [arrow] >>> writeDocument options path >>> getErrStatus) ; putStrLn (case rc of 0 -> "Okay" _ -> "Failed") } produceXmlTrees :: (ToXml a) => a -> IO [XmlTree] produceXmlTrees src = let arrow :: Producer arrow = toXml src options = defaultOptions in do { putStrLn "" ; docs <- runX (root [] [arrow] >>> writeDocument options "-") ; case docs of [] -> putStrLn "Failed" doc : _ -> print doc ; return docs } consumeFile :: Consumer XmlTree a -> FilePath -> IO [a] consumeFile fromXml filePath = let options = defaultOptions in runX (readDocument options filePath >>> fromXml) consumeStdin :: Consumer XmlTree a -> IO [a] consumeStdin fromXml = consumeFile fromXml "-" -- | testFromXml :: (ToXml a, Show a) => a -> Consumer XmlTree a -> IO () -- VVV This type generalization (a, a to a, b) is for debugging, undo it later: testFromXml :: (ToXml a, Show b) => a -> Consumer XmlTree b -> IO () testFromXml src consumer = do { produceFile src "test.xml" ; results <- runX (readDocument defaultOptions "test.xml" >>> isElem >>> -- document root getChildren >>> consumer) ; case results of [] -> putStrLn "Failed" result : _ -> print result } testToXmlAndBack :: (ToXml a, Show a) => a -> Consumer XmlTree a -> IO () testToXmlAndBack = testFromXml -- | Symbols instance ToXml Symbol where toXml = symbolToXml symbolToXml :: Symbol -> Producer symbolToXml (Symbol name) = selem "symbol" [txt name] xmlToSymbol :: Consumer XmlTree Symbol xmlToSymbol = isElem >>> hasName "symbol" >>> -- symbol element getChildren >>> isText >>> -- text element getText >>> -- String arr Symbol -- quasi (return . Symbol) testXmlToSymbol :: Symbol -> IO () testXmlToSymbol sym = testFromXml sym xmlToSymbol -- | Expr instance ToXml Expr where toXml = exprToXml exprToXml :: Expr -> Producer exprToXml expr = case expr of EUndefined -> eelem "undefined" ESymbol (Symbol name) -> selem "symbol" [txt name] -- ** To simplify, collapse 2.5 -- to 2.5, and similarly with other -- literal values? ELit value -> selem "literal" [toXml value] EIf e1 e2 e3 -> selem "if" [toXml e1, toXml e2, toXml e3] EList xs -> selem "list" (map toXml xs) ECall (Symbol name) xs -> selem "call" (selem "symbol" [txt name] : map toXml xs) xmlToExpr :: Consumer XmlTree Expr xmlToExpr = isElem >>> ( (hasName "undefined" >>> constA EUndefined) <+> (hasName "symbol" >>> getChildren >>> isText >>> getText >>> arr (ESymbol . Symbol)) <+> (hasName "literal" >>> getChildren >>> xmlToValue >>> arr ELit) <+> (hasName "if" >>> listA (getChildren >>> xmlToExpr) >>> arr (\ [a, b, c] -> EIf a b c)) <+> (hasName "list" >>> listA (getChildren >>> xmlToExpr) >>> arr EList) <+> -- VVV Would be less awkward if ECall :: Symbol -> [Expr] -> Expr -- were changed to ECall :: Expr -> [Expr] -> Expr (hasName "call" >>> listA (getChildren >>> xmlToExpr) >>> arr (\ (ESymbol symf : args) -> ECall symf args)) ) exampleIfExpr = (EIf (ELit (VBool False)) -- (eCall ">" [eInt 32, eInt 61]) (ELit (VStr "yes")) (ELit (VStr "no"))) exampleListExpr = EList [ELit (VInt 1), ELit (VInt 2), ELit (VInt 3)] exampleCallExpr = ECall (Symbol "foo") [ESymbol (Symbol "x"), ELit (VInt 2)] -- | Values instance ToXml Value where toXml = valueToXml valueToXml :: Value -> Producer valueToXml value = case value of VBool b -> -- or -- complicate? selem "bool" [txt (show b)] eelem (show b) VChar c -> selem "char" [txt [c]] VStr s -> selem "string" [txt s] VInt i -> selem "int" [txt (show i)] VFloat x -> selem "float" [txt (show x)] -- *** Are VFun and VList needed??? VFun f -> selem "function" [toXml f] VList vs -> selem "list" (map toXml vs) exampleVList :: Value exampleVList = VList [VInt 32, VInt 64, VInt 69] xmlToValue :: Consumer XmlTree Value xmlToValue = isElem >>> ((hasName "True" >>> constA (VBool True)) <+> (hasName "False" >>> constA (VBool False)) <+> (hasName "char" >>> getChildren >>> isText >>> getText >>> arr (VChar . head)) <+> (hasName "string" >>> getChildren >>> isText >>> getText >>> arr VStr) <+> (hasName "int" >>> getChildren >>> isText >>> getText >>> arr (VInt . read)) -- dangerous? <+> (hasName "float" >>> getChildren >>> isText >>> getText >>> arr (VFloat . read)) -- dangerous? <+> (hasName "function" >>> getChildren >>> xmlToFunction >>> arr VFun) <+> -- listA arr collects the results of arr into a list, so to speak; -- note that listA (arr1 >>> arr2) -- does not equal listA arr1 >>> listA arr2 -- and probably will not even have a well-defined type. -- In particular: -- getChildren --> [child1] -- listA getChildren --> [childi for i = 1 to n] -- listA getChildren >>> xmlToValue -- --> [child1] if child1 passes xmlToValue (it does not) -- listA (getChildren >>> xmlToValue) -- --> [childi for i = 1 to n if childi passes xmlToValue] (hasName "list" >>> listA (getChildren >>> xmlToValue) >>> arr VList) ) -- | VpTypes instance ToXml VpType where toXml = vpTypeToXml vpTypeToXml :: VpType -> Producer vpTypeToXml vtype = case vtype of VpTypeString -> eelem "string-type" VpTypeChar -> eelem "char-type" VpTypeNum -> eelem "num-type" VpTypeBool -> eelem "bool-type" VpTypeList eltType -> selem "list-type" [vpTypeToXml eltType] VpTypeVar typeVarName -> selem "type-variable" [txt typeVarName] xmlToVpType :: Consumer XmlTree VpType xmlToVpType = isElem >>> ((hasName "string-type" >>> constA VpTypeString) <+> (hasName "char-type" >>> constA VpTypeChar) <+> (hasName "num-type" >>> constA VpTypeNum) <+> (hasName "bool-type" >>> constA VpTypeBool) <+> (hasName "list-type" >>> getChildren >>> xmlToVpType >>> arr VpTypeList) <+> (hasName "type-variable" >>> getChildren >>> isText >>> getText >>> arr VpTypeVar) ) -- | Functions instance ToXml Function where toXml = functionToXml functionToXml :: Function -> Producer functionToXml (Function mName argTypes retType impl) = case impl of Primitive _ -> -- shouldn't happen errcats ["functionToXml:", "primitive functions cannot be exported to XML", show (mName, argTypes, retType)] Compound argNames body -> selem "compound-function" (let name name = selem "name" [txt name] rest = [selem "return-type" [vpTypeToXml retType], selem "arg-types" (map vpTypeToXml argTypes), selem "arg-names" (map name argNames), selem "body" [toXml body]] in case mName of Nothing -> rest Just fName -> name fName : rest ) xmlToFunction :: Consumer XmlTree Function xmlToFunction = let getChildElem :: Consumer XmlTree XmlTree getChildElem = getChildren >>> isElem getFuncName :: Consumer XmlTree String getFuncName = hasName "name" >>> getChildren >>> isText >>> getText getReturnType :: Consumer XmlTree VpType getReturnType = hasName "return-type" >>> getChildren >>> xmlToVpType getArgTypes :: Consumer XmlTree [VpType] getArgTypes = hasName "arg-types" >>> listA (getChildren >>> xmlToVpType) getArgNames :: Consumer XmlTree [String] getArgNames = hasName "arg-names" >>> listA (getChildElem >>> getFuncName) getBody :: Consumer XmlTree Expr getBody = hasName "body" >>> getChildren >>> xmlToExpr in isElem >>> hasName "compound-function" >>> -- NOTE: -- If arr1 "produces" a, and arr2 "produces" b, -- then (arr1 &&& arr2) "produces" (a, b). ( ( -- function name is optional, though it *should* be in the XML file listA (getChildElem >>> getFuncName)) &&& (getChildElem >>> getReturnType) &&& (getChildElem >>> getArgTypes) &&& (getChildElem >>> getArgNames) &&& (getChildElem >>> getBody) ) >>> (arr (\ (names, (returnType, (argTypes, (argNames, body)))) -> Function (case names of [] -> Nothing (fname : _) -> (Just fname) ) argTypes returnType (Compound argNames body))) exampleFunction = let esym = ESymbol . Symbol in Function (Just "cincr") [VpTypeBool, VpTypeNum, VpTypeNum] VpTypeNum (Compound ["incr", "a", "b"] (EIf (esym "incr") (ECall (Symbol "+") [esym "a", esym "b"]) (esym "a"))) -- | A collection of functions, to be saved or read from a file data Functions = Functions [Function] deriving (Eq, Show) functionsToXml :: Functions -> Producer functionsToXml (Functions fs) = selem "functions" (map toXml fs) xmlToFunctions :: Consumer XmlTree Functions xmlToFunctions = isElem >>> -- document root getChildren >>> hasName "functions" >>> listA (getChildren >>> xmlToFunction) >>> arr Functions -- | This is for testing, when I don't know the type -- of the result I'm getting xmlToX :: Consumer XmlTree Functions -- [Function] -- XmlTree xmlToX = isElem -- document root >>> getChildren >>> hasName "functions" >>> listA (getChildren >>> xmlToFunction) >>> arr Functions instance ToXml Functions where toXml = functionsToXml -- | Tests testOut :: IO () testOut = produceStdout (Functions [exampleFunctions !! 0, exampleFunctions !! 1]) testFromFile :: (Show a) => Consumer XmlTree a -> FilePath -> IO () testFromFile fromXml filePath = do { results <- consumeFile fromXml filePath ; putStrLn "" ; print (length results) ; print results ; putStrLn "" } testIn :: (Show a) => Consumer XmlTree a -> IO () testIn fromXml = testFromFile fromXml "-"