-- | SiffML : Sifflet Markup Language. -- An XML application for storing and retrieving Sifflet programs -- and libraries. module Language.Sifflet.SiffML ( ToXml(..) , produceSiffMLFile , consumeSiffMLFile , xmlToFunctions -- for testing , testFromXml -- , consumeString ) where import Text.XML.HXT.Core import Data.Number.Sifflet import Language.Sifflet.Expr import Language.Sifflet.Util class ToXml a where toXml :: a -> XMLProducer -- | An XMLProducer produces XML type XMLProducer = IOSLA (XIOState ()) XmlTree XmlTree -- | An XMLConsumer consumes XML type XMLConsumer a b = IOSLA (XIOState ()) a b defaultOptions :: SysConfigList defaultOptions = [withIndent yes, withValidate no ] produceSiffMLFile :: (ToXml a) => a -> FilePath -> IO () produceSiffMLFile src path = let arrow :: XMLProducer arrow = toXml src options = defaultOptions in do { putStrLn "" ; [rc] <- runX (root [] [arrow] >>> writeDocument options path >>> getErrStatus) ; putStrLn (case rc of 0 -> "Okay" _ -> "Failed") } consumeSiffMLFile :: XMLConsumer XmlTree a -> FilePath -> IO [a] consumeSiffMLFile fromXml filePath = let options = defaultOptions in do doc <- readFile filePath runX (readString options doc >>> fromXml) -- | Symbols instance ToXml Symbol where toXml = symbolToXml symbolToXml :: Symbol -> XMLProducer symbolToXml (Symbol name) = selem "symbol" [txt name] -- | Expr instance ToXml Expr where toXml = exprToXml exprToXml :: Expr -> XMLProducer exprToXml expr = let literal label text = -- future: (omit label arg.): selem label [txt text] selem "literal" [selem label [txt text]] in case expr of EUndefined -> eelem "undefined" ESymbol (Symbol name) -> selem "symbol" [txt name] -- "Literals" -- New way: duplicates parts of valueToXml (bad) *** EBool b -> -- future: selem "bool" [eelem (show b)] selem "literal" [selem "bool" [eelem (show b)]] EChar c -> -- future: selem "char" [txt [c]] literal "char" [c] ENumber (Exact i) -> -- future: selem "int" [txt (show i)] literal "int" (show i) ENumber (Inexact x) -> -- future: selem "float" [txt (show x)] literal "float" (show x) EString s -> -- future: selem "string" [txt s] literal "string" s EIf e1 e2 e3 -> selem "if" [toXml e1, toXml e2, toXml e3] EList xs -> -- I predict that this is going to be troublesome! *** -- No checking for whether the list elements are literals! selem "literal" [selem "list" (map (toXml . literalToValue) xs)] -- future: selem "list" (map toXml xs) ELambda _ _ -> error "exprToXml: not implemented for lambda expr" ECall (Symbol name) xs -> selem "call" (selem "symbol" [txt name] : map toXml xs) _ -> errcats ["exprToXml: extended expr:", show expr] -- | Convert a literal expression to a value. -- It is an error if the expr is not a literal. -- Compare exprToValue in Expr.hs literalToValue :: Expr -> Value literalToValue e = if exprIsLiteral e then case e of EBool b -> VBool b EChar c -> VChar c ENumber n -> VNumber n EString s -> VString s EList es -> VList (map literalToValue es) EGroup e' -> literalToValue e' _ -> error "literalToValue: expr is literal, but not literal?" else error ("literalToValue: expr is not a literal: " ++ show e) xmlToExpr :: XMLConsumer XmlTree Expr xmlToExpr = isElem >>> ( (hasName "undefined" >>> constA EUndefined) <+> (hasName "symbol" >>> getChildren >>> isText >>> getText >>> arr (ESymbol . Symbol)) <+> -- future: remove extra level "literal" (hasName "literal" >>> getChildren >>> xmlToExpr) <+> -- boolean values (hasName "True" >>> constA (EBool True)) <+> (hasName "False" >>> constA (EBool False)) <+> -- chars (hasName "char" >>> getChildren >>> isText >>> getText >>> -- VVV head dangerous ??? arr (EChar . head)) <+> -- numbers -- why not use parser instead of read??? (hasName "int" >>> getChildren >>> isText >>> getText >>> arr (ENumber . Exact . read)) <+> -- read dangerous? (hasName "float" >>> getChildren >>> isText >>> getText >>> arr (ENumber . Inexact . read)) <+> -- read dangerous? -- strings (hasName "string" >>> getChildren >>> isText >>> getText >>> arr EString) <+> (hasName "if" >>> listA (getChildren >>> xmlToExpr) >>> -- sometimes I get bogus run-time errors here about -- this pattern [a, b, c] being non-exhaustive. -- Of course, it *is* non-exhaustive; but it is -- never violated in practice arr (\ [a, b, c] -> EIf a b c)) <+> -- This is very awkward, but needed for compatibility with the -- present SiffML doctype: (hasName "list" >>> -- future?: listA (getChildren >>> xmlToExpr) >>> -- Anyway, *why* does this not work??? listA (getChildren >>> xmlToExpr) >>> -- past?: -- listA (getChildren >>> xmlToValue >>> arr valueToLiteral') >>> 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)) ) -- | Values -- Still used in exprToXml in the EList case :-( instance ToXml Value where toXml = valueToXml -- Is this still needed? *** valueToXml :: Value -> XMLProducer valueToXml value = case value of VBool b -> -- or -- complicate? selem "bool" [txt (show b)] eelem (show b) VChar c -> selem "char" [txt [c]] VString s -> selem "string" [txt s] VNumber (Exact i) -> selem "int" [txt (show i)] VNumber (Inexact x) -> selem "float" [txt (show x)] -- Are VFun and VList needed??? VFun f -> selem "function" [toXml f] VList vs -> selem "list" (map toXml vs) -- xmlToValue: still needed? *** xmlToValue :: XMLConsumer 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 VString) <+> (hasName "int" >>> getChildren >>> isText >>> getText >>> arr (VNumber . Exact . read)) -- dangerous? <+> (hasName "float" >>> getChildren >>> isText >>> getText >>> arr (VNumber . Inexact . 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) ) -- | Types instance ToXml Type where toXml = typeToXml typeToXml :: Type -> XMLProducer typeToXml vtype = case vtype of TypeVar typeVarName -> selem "type-variable" [txt typeVarName] TypeCons "String" [] -> eelem "string-type" TypeCons "Char" [] -> eelem "char-type" TypeCons "Num" [] -> eelem "num-type" TypeCons "Bool" [] -> eelem "bool-type" TypeCons "List" [eltType] -> selem "list-type" [typeToXml eltType] TypeCons "Function" [argT, resultT] -> selem "function-type" [typeToXml argT, typeToXml resultT] -- error "typeToXml: TypeCons Function cannot be converted to XML" TypeCons _ _ -> errcats ["typeToXml:", show vtype, "cannot be converted to XML"] xmlToType :: XMLConsumer XmlTree Type xmlToType = isElem >>> ((hasName "string-type" >>> constA typeString) <+> (hasName "char-type" >>> constA typeChar) <+> (hasName "num-type" >>> constA typeNum) <+> (hasName "bool-type" >>> constA typeBool) <+> (hasName "list-type" >>> getChildren >>> xmlToType >>> arr typeList) <+> (hasName "function-type" >>> listA (getChildren >>> xmlToType) >>> -- there must be exactly two children, but I'm not checking arr (\ ts -> TypeCons "Function" ts)) <+> (hasName "type-variable" >>> getChildren >>> isText >>> getText >>> arr TypeVar) ) -- | Functions instance ToXml Function where toXml = functionToXml functionToXml :: Function -> XMLProducer 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 s = selem "name" [txt s] rest = [selem "return-type" [typeToXml retType], selem "arg-types" (map typeToXml argTypes), selem "arg-names" (map name argNames), selem "body" [toXml body]] in case mName of Nothing -> rest Just fName -> name fName : rest ) xmlToFunction :: XMLConsumer XmlTree Function xmlToFunction = let getChildElem :: XMLConsumer XmlTree XmlTree getChildElem = getChildren >>> isElem getFuncName :: XMLConsumer XmlTree String getFuncName = hasName "name" >>> getChildren >>> isText >>> getText getReturnType :: XMLConsumer XmlTree Type getReturnType = hasName "return-type" >>> getChildren >>> xmlToType getArgTypes :: XMLConsumer XmlTree [Type] getArgTypes = hasName "arg-types" >>> listA (getChildren >>> xmlToType) getArgNames :: XMLConsumer XmlTree [String] getArgNames = hasName "arg-names" >>> listA (getChildElem >>> getFuncName) getBody :: XMLConsumer 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))) functionsToXml :: Functions -> XMLProducer functionsToXml (Functions fs) = selem "functions" (map toXml fs) xmlToFunctions :: XMLConsumer XmlTree Functions xmlToFunctions = isElem >>> -- document root getChildren >>> hasName "functions" >>> listA (getChildren >>> xmlToFunction) >>> arr Functions instance ToXml Functions where toXml = functionsToXml -- -- | Examples and tests -- exampleFunction :: Function -- exampleFunction = -- let esym = ESymbol . Symbol -- in Function (Just "cincr") -- [typeBool, typeNum, typeNum] -- typeNum -- (Compound ["incr", "a", "b"] -- (EIf (esym "incr") -- (ECall (Symbol "+") [esym "a", esym "b"]) -- (esym "a"))) -- -- | This is for testing, when I don't know the type -- -- of the result I'm getting -- xmlToX :: XMLConsumer XmlTree Functions -- [Function] -- XmlTree -- xmlToX = -- isElem -- document root -- >>> -- getChildren -- >>> -- hasName "functions" -- >>> -- listA (getChildren >>> xmlToFunction) -- >>> -- arr Functions -- -- | Tests -- testOut :: IO () -- testOut = -- produceStdout (Functions [exampleFunctions !! 0, exampleFunctions !! 1]) -- testFromFile :: (Show a) => XMLConsumer XmlTree a -> FilePath -> IO () -- testFromFile fromXml filePath = do -- { -- results <- consumeSiffMLFile fromXml filePath -- ; putStrLn "" -- ; print (length results) -- ; print results -- ; putStrLn "" -- } -- testIn :: (Show a) => XMLConsumer XmlTree a -> IO () -- testIn fromXml = testFromFile fromXml "-" -- UNUSED: -- | testFromXml :: (ToXml a, Show a) => a -> XMLConsumer XmlTree a -> IO () -- VVV This type generalization (a, a to a, b) is for debugging, undo it later: testFromXml :: (ToXml a, Show b) => Int -> a -> XMLConsumer XmlTree b -> IO () testFromXml traceLevel src consumer = do { produceSiffMLFile src "test.xml" ; results <- runX (readDocument (defaultOptions ++ [withTrace traceLevel]) "test.xml" >>> isElem >>> -- document root getChildren >>> consumer) ; case results of [] -> putStrLn "Failed" result : _ -> print result } -- testToXmlAndBack :: (ToXml a, Show a) => a -> XMLConsumer XmlTree a -> IO () -- testToXmlAndBack = testFromXml -- xmlToSymbol :: XMLConsumer 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 -- produceStdout :: (ToXml a) => a -> IO () -- produceStdout src = produceSiffMLFile src "-" -- produceXmlTrees :: (ToXml a) => a -> IO [XmlTree] -- produceXmlTrees src = -- let arrow :: XMLProducer -- arrow = toXml src -- options = defaultOptions -- in do -- { -- putStrLn "" -- ; docs <- runX (root [] [arrow] >>> writeDocument options "-") -- ; case docs of -- [] -> putStrLn "Failed" -- doc : _ -> -- print doc -- ; return docs -- } -- consumeStdin :: XMLConsumer XmlTree a -> IO [a] -- consumeStdin fromXml = consumeSiffMLFile fromXml "-"