{-# LANGUAGE GADTs, ExistentialQuantification #-} ----------------------------------------------------------------------------- -- Copyright 2011, 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) -- -- This module defines transformations. Given a term, a transformation returns a list of -- results (often a singleton list or the empty list). A transformation can be parameterized -- with one or more arguments. A rule is in essence just a transformation with a name (which -- should be unique). Both transformations and rules can be lifted to work on more complex domains. -- ----------------------------------------------------------------------------- module Common.Transformation ( -- * Transformations Transformation, makeTrans, makeTransList, makeRewriteTrans -- * Arguments , ArgDescr(..), defaultArgDescr, Argument(..), ArgValue(..), ArgValues , supply1, supply2, supply3 , hasArguments, expectedArguments, getDescriptors, useArguments -- * Rules , Rule, isMinorRule, isMajorRule, isBuggyRule, isRewriteRule , finalRule, isFinalRule, ruleSiblings, rule, ruleList , makeRule, makeRuleList, makeSimpleRule, makeSimpleRuleList , idRule, checkRule, emptyRule, minorRule, buggyRule, doAfter , siblingOf, transformations, getRewriteRules , ruleRecognizer, useRecognizer, useSimpleRecognizer -- * Lifting , liftRule, liftTrans, liftRuleIn, liftTransIn -- * QuickCheck , testRule, propRuleSmart ) where import Common.Classes import Common.Id import Common.Rewriting import Common.Utils import Common.View import Control.Monad import Data.Function import Data.Maybe import Test.QuickCheck ----------------------------------------------------------- --- Transformations -- | Abstract data type for representing transformations data Transformation a = Function (a -> [a]) | RewriteRule (RewriteRule a) (a -> [a]) | forall b . Abstraction (ArgumentList b) (a -> Maybe b) (b -> Transformation a) | forall b c . LiftView (View a (b, c)) (Transformation b) | Recognizer (a -> a -> Maybe ArgValues) (Transformation a) instance Apply Transformation where applyAll (Function f) = f applyAll (RewriteRule _ f) = f applyAll (Abstraction _ f g) = \a -> maybe [] (\b -> applyAll (g b) a) (f a) applyAll (LiftView v t) = \a -> [ build v (b, c) | (b0, c) <- matchM v a, b <- applyAll t b0 ] applyAll (Recognizer _ t) = applyAll t -- | Turn a function (which returns its result in the Maybe monad) into a transformation makeTrans :: (a -> Maybe a) -> Transformation a makeTrans f = makeTransList (maybe [] return . f) -- | Turn a function (which returns a list of results) into a transformation makeTransList :: (a -> [a]) -> Transformation a makeTransList = Function -- | Turn a rewrite rule into a transformation makeRewriteTrans :: RewriteRule a -> Transformation a makeRewriteTrans r = RewriteRule r (rewriteM r) ----------------------------------------------------------- --- Arguments -- | A data type for describing an argument of a parameterized transformation data ArgDescr a = ArgDescr { labelArgument :: String -- ^ Label that is shown to the user when asked to supply the argument , defaultArgument :: Maybe a -- ^ Default value that can be used , parseArgument :: String -> Maybe a -- ^ A parser , showArgument :: a -> String -- ^ A pretty-printer , termViewArgument :: View Term a -- ^ Conversion to/from term , genArgument :: Gen a -- ^ An arbitrary argument generator } -- | An argument descriptor, paired with a value data ArgValue = forall a . ArgValue (ArgDescr a) a -- | List of argument values type ArgValues = [ArgValue] instance Show ArgValue where show (ArgValue descr a) = showArgument descr a instance Eq ArgValue where ArgValue d1 a1 == ArgValue d2 a2 = build (termViewArgument d1) a1 == build (termViewArgument d2) a2 -- | Constructor function for an argument descriptor that uses the Show and Read type classes defaultArgDescr :: (Show a, Read a, IsTerm a, Arbitrary a) => String -> ArgDescr a defaultArgDescr descr = ArgDescr descr Nothing readM show termView arbitrary -- | A type class for types which have an argument descriptor class Arbitrary a => Argument a where makeArgDescr :: String -> ArgDescr a -- ^ The first argument is the label of the argument descriptor instance Argument Int where makeArgDescr = defaultArgDescr -- | Parameterization with one argument using the provided label supply1 :: Argument x => String -> (a -> Maybe x) -> (x -> Transformation a) -> Transformation a supply1 s f t = let args = Single (makeArgDescr s) in Abstraction args f t -- | Parameterization with two arguments using the provided labels supply2 :: (Argument x, Argument y) => (String, String) -> (a -> Maybe (x, y)) -> (x -> y -> Transformation a) -> Transformation a supply2 (s1, s2) f t = let args = Pair (Single (makeArgDescr s1)) (Single (makeArgDescr s2)) in Abstraction args f (uncurry t) -- | Parameterization with three arguments using the provided labels supply3 :: (Argument x, Argument y, Argument z) => (String, String, String) -> (a -> Maybe (x, y, z)) -> (x -> y -> z -> Transformation a) -> Transformation a supply3 (s1, s2, s3) f t = let args = Pair (Single (makeArgDescr s1)) (Pair (Single (makeArgDescr s2)) (Single (makeArgDescr s3))) nest (a, b, c) = (a, (b, c)) in Abstraction args (fmap nest . f) (\(a, (b, c)) -> t a b c) -- | Checks whether a rule is parameterized hasArguments :: Rule a -> Bool hasArguments = not . null . getDescriptors -- | Returns a list of argument descriptors getDescriptors :: Rule a -> [Some ArgDescr] getDescriptors r = case transformations r of [t] -> rec t _ -> [] where rec :: Transformation a -> [Some ArgDescr] rec trans = case trans of Abstraction args _ _ -> someArguments args LiftView _ t -> rec t Recognizer _ t -> rec t _ -> [] -- | Returns a list of pretty-printed expected arguments. -- Nothing indicates that there are no such arguments (or the arguments -- are not applicable for the current value) expectedArguments :: Rule a -> a -> Maybe ArgValues expectedArguments r = case transformations r of [t] -> rec t _ -> const Nothing where rec :: Transformation a -> a -> Maybe ArgValues rec trans a = case trans of Abstraction args f _ -> fmap (argumentValues args) (f a) LiftView v t -> do (b, _) <- match v a rec t b Recognizer _ t -> rec t a _ -> Nothing -- | Transform a rule and use a list of pretty-printed arguments. Nothing indicates that the arguments are -- invalid (not parsable), or that the wrong number of arguments was supplied useArguments :: [String] -> Rule a -> Maybe (Rule a) useArguments list r = case transformations r of [t] -> do new <- make t return r {transformations = [new]} _ -> Nothing where make :: Transformation a -> Maybe (Transformation a) make trans = case trans of Abstraction args _ g -> fmap g (parseArguments args list) LiftView v t -> fmap (LiftView v) (make t) Recognizer f t -> fmap (Recognizer f) (make t) _ -> Nothing ----------------------------------------------------------- --- Internal machinery for arguments data ArgumentList a where Single :: ArgDescr a -> ArgumentList a Pair :: ArgumentList a -> ArgumentList b -> ArgumentList (a, b) parseArguments :: ArgumentList a -> [String] -> Maybe a parseArguments (Single a) [x] = parseArgument a x parseArguments (Pair a b) xs = let (ys, zs) = splitAt (numberOfArguments a) xs in liftM2 (,) (parseArguments a ys) (parseArguments b zs) parseArguments _ _ = Nothing someArguments :: ArgumentList a -> [Some ArgDescr] someArguments (Single a) = [Some a] someArguments (Pair a b) = someArguments a ++ someArguments b argumentValues :: ArgumentList a -> a -> ArgValues argumentValues (Single a) x = [ArgValue a x] argumentValues (Pair a b) (x, y) = argumentValues a x ++ argumentValues b y numberOfArguments :: ArgumentList a -> Int numberOfArguments = length . someArguments ----------------------------------------------------------- --- Rules -- | Abstract data type for representing rules data Rule a = Rule { ruleId :: Id -- ^ Unique identifier of the rule , transformations :: [Transformation a] , afterwards :: a -> a , isBuggyRule :: Bool -- ^ Inspect whether or not the rule is buggy (unsound) , isMinorRule :: Bool -- ^ Returns whether or not the rule is minor (i.e., an administrative step that is automatically performed by the system) , isFinalRule :: Bool -- ^ Final (clean-up) step in derivation , ruleSiblings :: [Id] } instance Show (Rule a) where show = showId instance Eq (Rule a) where r1 == r2 = ruleId r1 == ruleId r2 instance Ord (Rule a) where compare = compareId instance Apply Rule where applyAll r a = do t <- transformations r b <- applyAll t a return (afterwards r b) instance HasId (Rule a) where getId = ruleId changeId f r = r { ruleId = f (ruleId r) } instance (Arbitrary a, CoArbitrary a) => Arbitrary (Rule a) where arbitrary = liftM3 make arbitrary arbitrary arbitrary where make minor n f | minor = minorRule $ makeSimpleRule n f | otherwise = makeSimpleRule (n :: Id) f -- | Returns whether or not the rule is major (i.e., not minor) isMajorRule :: Rule a -> Bool isMajorRule = not . isMinorRule isRewriteRule :: Rule a -> Bool isRewriteRule = not . null . getRewriteRules siblingOf :: HasId b => b -> Rule a -> Rule a siblingOf sib r = r { ruleSiblings = getId sib : ruleSiblings r } ruleList :: (IsId n, RuleBuilder f a) => n -> [f] -> Rule a ruleList n = makeRuleList a . map (makeRewriteTrans . rewriteRule a) where a = newId n rule :: (IsId n, RuleBuilder f a) => n -> f -> Rule a rule n = makeRule a . makeRewriteTrans . rewriteRule a where a = newId n -- | Turn a transformation into a rule: the first argument is the rule's name makeRule :: IsId n => n -> Transformation a -> Rule a makeRule n = makeRuleList n . return -- | Turn a list of transformations into a single rule: the first argument is the rule's name makeRuleList :: IsId n => n -> [Transformation a] -> Rule a makeRuleList n ts = Rule (newId n) ts id False False False [] -- | Turn a function (which returns its result in the Maybe monad) into a rule: the first argument is the rule's name makeSimpleRule :: IsId n => n -> (a -> Maybe a) -> Rule a makeSimpleRule n = makeRule n . makeTrans -- | Turn a function (which returns a list of results) into a rule: the first argument is the rule's name makeSimpleRuleList :: IsId n => n -> (a -> [a]) -> Rule a makeSimpleRuleList n = makeRule n . makeTransList -- | A special (minor) rule that always returns the identity idRule :: Rule a idRule = minorRule $ makeSimpleRule "Identity" return -- | A special (minor) rule that checks a predicate (and returns the identity -- if the predicate holds) checkRule :: (a -> Bool) -> Rule a checkRule p = minorRule $ makeSimpleRule "Check" $ \a -> if p a then Just a else Nothing -- | A special (minor) rule that is never applicable (i.e., this rule always fails) emptyRule :: Rule a emptyRule = minorRule $ makeSimpleRule "Empty" (const Nothing) -- | Mark the rule as minor (by default, rules are not minor) minorRule :: Rule a -> Rule a minorRule r = r {isMinorRule = True} -- | Mark the rule as buggy (by default, rules are supposed to be sound) buggyRule :: Rule a -> Rule a buggyRule r = r {isBuggyRule = True} -- | Mark the rule as final (by default, false). Final rules are used as a -- final step in the derivation, to get the term in the expected form finalRule :: Rule a -> Rule a finalRule r = r {isFinalRule = True} -- | Perform the function after the rule has been fired doAfter :: (a -> a) -> Rule a -> Rule a doAfter f r = r {afterwards = f . afterwards r} getRewriteRules :: Rule a -> [(Some RewriteRule, Bool)] getRewriteRules r = concatMap f (transformations r) where f :: Transformation a -> [(Some RewriteRule, Bool)] f trans = case trans of RewriteRule rr _ -> [(Some rr, not $ isBuggyRule r)] LiftView _ t -> f t _ -> [] ruleRecognizer :: (a -> a -> Bool) -> Rule a -> a -> a -> Maybe ArgValues ruleRecognizer eq r a b = msum [ transRecognizer eq t a b | t <- transformations r ] transRecognizer :: (a -> a -> Bool) -> Transformation a -> a -> a -> Maybe ArgValues transRecognizer eq trans a b = case trans of Recognizer f t -> f a b `mplus` transRecognizer eq t a b LiftView v t -> msum [ transRecognizer (eq `on` f) t av bv | (av, c) <- matchM v a , (bv, _) <- matchM v b , let f z = build v (z, c) ] `mplus` noArg (any (`eq` b) (applyAll trans a)) -- is this really needed? _ -> noArg $ any (`eq` b) (applyAll trans a) where noArg c = if c then Just [] else Nothing useRecognizer :: (a -> a -> Maybe ArgValues) -> Transformation a -> Transformation a useRecognizer = Recognizer useSimpleRecognizer :: (a -> a -> Bool) -> Transformation a -> Transformation a useSimpleRecognizer p = useRecognizer $ \x y -> guard (p x y) >> return [] ----------------------------------------------------------- --- Lifting liftTrans :: View a b -> Transformation b -> Transformation a liftTrans v = liftTransIn (v &&& identity) liftTransIn :: View a (b, c) -> Transformation b -> Transformation a liftTransIn = LiftView liftRule :: View a b -> Rule b -> Rule a liftRule v = liftRuleIn (v &&& identity) liftRuleIn :: View a (b, c) -> Rule b -> Rule a liftRuleIn v r = r { transformations = map (liftTransIn v) (transformations r) , afterwards = simplifyWith (mapFirst (afterwards r)) v } ----------------------------------------------------------- --- QuickCheck -- | Check the soundness of a rule: the equality function is passed explicitly testRule :: (Arbitrary a, Show a) => (a -> a -> Bool) -> Rule a -> IO () testRule eq r = quickCheck (propRule eq r arbitrary) -- | Check the soundness of a rule and use a "smart generator" for this. The smart generator -- behaves differently on transformations constructed with a (|-), and for these transformations, -- the left-hand side patterns are used (meta variables are instantiated with random terms) propRuleSmart :: Show a => (a -> a -> Bool) -> Rule a -> Gen a -> Property propRuleSmart eq r = propRule eq r . smartGen r propRule :: Show a => (a -> a -> Bool) -> Rule a -> Gen a -> Property propRule eq r gen = forAll gen $ \a -> forAll (smartApplyRule r a) $ \ma -> isJust ma ==> (a `eq` fromJust ma) smartGen :: Rule a -> Gen a -> Gen a smartGen r gen = frequency [(2, gen), (1, smart)] where smart = gen >>= \a -> oneof (gen : mapMaybe (smartGenTrans a) (transformations r)) smartGenTrans :: a -> Transformation a -> Maybe (Gen a) smartGenTrans a trans = case trans of RewriteRule r _ -> return (smartGenerator r) LiftView v t -> do (b, c) <- matchM v a gen <- smartGenTrans b t return $ liftM (\n -> build v (n, c)) gen _ -> Nothing smartApplyRule :: Rule a -> a -> Gen (Maybe a) smartApplyRule r a = do xss <- mapM (`smartApplyTrans` a) (transformations r) case concat xss of [] -> return Nothing xs -> elements $ map Just xs smartApplyTrans :: Transformation a -> a -> Gen [a] smartApplyTrans trans a = case trans of Abstraction args _ g -> smartArgs args >>= \b -> smartApplyTrans (g b) a _ -> return (applyAll trans a) smartArgs :: ArgumentList a -> Gen a smartArgs (Single a) = genArgument a smartArgs (Pair a b) = liftM2 (,) (smartArgs a) (smartArgs b)