packages feed

ideas-0.6: src/Service/StrategyInfo.hs

-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed 
-- under the terms of the GNU General Public License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Converting a strategy to XML, and the other way around.
--
-----------------------------------------------------------------------------
module Service.StrategyInfo (strategyToXML, xmlToStrategy) where

import Data.Char
import Data.Maybe
import Control.Monad
import Common.Strategy.Core (Core(..), noLabels)
import Common.Strategy.Abstract
import Text.XML
import Common.Transformation hiding (name)
import Common.Utils (readInt)

instance InXML (LabeledStrategy a) where
   toXML       = toXML . toStrategy
   fromXML xml = fromXML xml >>= toLabeledStrategy

instance InXML (Strategy a) where
   toXML   = strategyToXML
   fromXML = xmlToStrategy unknownRule

-----------------------------------------------------------------------
-- Strategy to XML

strategyToXML :: IsStrategy f => f a -> XML
strategyToXML = coreToXML . toCore . toStrategy

infoToXML :: LabelInfo -> XMLBuilder
infoToXML info = do
   "name" .=. labelName info
   when (removed   info) ("removed"   .=. "true")
   when (collapsed info) ("collapsed" .=. "true")
   when (hidden    info) ("hidden"    .=. "true")

coreToXML :: Core LabelInfo a -> XML
coreToXML core = makeXML "label" $ 
   case core of
      Label l a -> infoToXML l >> coreBuilder infoToXML a
      _         -> coreBuilder infoToXML core

coreBuilder :: (l -> XMLBuilder) -> Core l a -> XMLBuilder
coreBuilder f = rec
 where
   rec core = 
      case core of
         _ :*:  _  -> asList  "sequence" isSequence
         _ :|:  _  -> asList  "choice"   isChoice
         _ :|>: _  -> asList  "orelse"   isOrElse
         Many a    -> element "many"     (rec a)
         Repeat a  -> element "repeat"   (rec a)
         Label l a -> element "label"    (f l >> rec a)
         Rec n a   -> element "rec"      (("var" .=. show n) >> rec a)
         Not a     -> element "not"      (recNot a)
         Rule l r  -> element "rule"     (maybe ("name" .=. show r) f l)
         Var n     -> element "var"      ("var" .=. show n)
         Succeed   -> tag     "succeed"
         Fail      -> tag     "fail"
    where
      asList s g = element s (mapM_ rec (collect g core))
      recNot = coreBuilder (const (return ()))

collect :: (a -> Maybe (a, a)) -> a -> [a]
collect f = ($ []) . rec
 where rec a = maybe (a:) (\(x, y) -> rec x . rec y) (f a)
      
isSequence :: Core l a -> Maybe (Core l a, Core l a)
isSequence (a :*: b) = Just (a, b)
isSequence _ = Nothing

isChoice :: Core l a -> Maybe (Core l a, Core l a)
isChoice (a :|: b) = Just (a, b)
isChoice _ = Nothing

isOrElse :: Core l a -> Maybe (Core l a, Core l a)
isOrElse (a :|>: b) = Just (a, b)
isOrElse _ = Nothing

-----------------------------------------------------------------------
-- XML to strategy

xmlToStrategy :: Monad m => (String -> Maybe (Rule a)) ->  XML -> m (Strategy a)
xmlToStrategy f = liftM fromCore . readStrategy xmlToInfo g
 where
   g info = case f (labelName info) of
               Just r  -> return r
               Nothing -> fail $ "Unknown rule: " ++ show (labelName info) 

xmlToInfo :: Monad m => XML -> m LabelInfo
xmlToInfo xml = do
   n <- findAttribute "name" xml
   let boolAttr s = fromMaybe False (findBool s xml)
   return (makeInfo n)
      { removed   = boolAttr "removed"
      , collapsed = boolAttr "collapsed"
      , hidden    = boolAttr "hidden"
      }

findBool :: Monad m => String -> XML -> m Bool
findBool attr xml = do
   s <- findAttribute attr xml
   case map toLower s of 
      "true"  -> return True
      "false" -> return False
      _       -> fail "not a boolean"

unknownRule :: Monad m => String -> m (Rule a)
unknownRule s = 
   let n = "#Unknown rule:" ++ s
   in return (makeSimpleRule n (const Nothing))

readStrategy :: Monad m => (XML -> m l) -> (l -> m (Rule a)) -> XML -> m (Core l a)
readStrategy f g xml = do
   xs <- mapM (readStrategy f g) (children xml)
   let s = name xml
   case lookup s table of
      Just f  -> f s xs
      Nothing -> 
         fail $ "Unknown strategy combinator " ++ show s
 where
   buildSequence _ xs 
      | null xs   = return Succeed
      | otherwise = return (foldr1 (:*:) xs)
   buildChoice _ xs
      | null xs   = return Fail
      | otherwise = return (foldr1 (:|:) xs)
   buildOrElse _ xs
      | null xs   = return Fail
      | otherwise = return (foldr1 (:|>:) xs)
   buildLabel x = do
      info <- f xml
      return (Label info x)
   buildRule = do
      info <- f xml
      rule <- g info
      return (Rule (Just info) rule)
   buildRec x = do
      s <- findAttribute "var" xml
      i <- maybe (fail "var: not an int") return (readInt s)
      return (Rec i x)
   buildVar = do
      s <- findAttribute "var" xml
      i <- maybe (fail "var: not an int") return (readInt s)
      return (Var i)

   nullary a _ [] = return a
   nullary _ s _  = fail $ "Strategy combinator " ++ s ++ "expects 0 args"
 
   unary f _ [x] = return (f x)
   unary _ s _   = fail $ "Strategy combinator " ++ s ++ "expects 1 arg"
 
   join2 f g a b = join (f g a b)
 
   table =
      [ ("sequence", buildSequence)
      , ("choice",   buildChoice)
      , ("orelse",   buildOrElse)
      , ("many",     unary Many)
      , ("repeat",   unary Repeat)
      , ("label",    join2 unary buildLabel)
      , ("rec",      join2 unary buildRec)
      , ("not",      unary (Not . noLabels))
      , ("rule",     join2 nullary buildRule)
      , ("var",      join2 nullary buildVar)
      , ("succeed",  nullary Succeed)
      , ("fail",     nullary Fail) 
      ]