packages feed

ideas-0.7: src/Common/Rewriting/Difference.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)
--
-- Compute the difference of two terms generically, taking associativity
-- into account.
--
-----------------------------------------------------------------------------
module Common.Rewriting.Difference 
   ( difference, differenceEqual, differenceMode
   ) where

import Common.Rewriting.Group
import Common.Rewriting.Term
import Common.Rewriting.RewriteRule
import Common.View
import Common.Utils (safeHead)
import Control.Monad
import Common.Uniplate
import Data.Maybe

differenceMode :: (Rewrite a, Uniplate a) 
               => (a -> a -> Bool) -> Bool -> a -> a -> Maybe (a, a)
differenceMode eq b =
   if b then differenceEqual eq else difference

-- | This function returns the difference, except that the 
-- returned terms should be logically equivalent. Nothing can signal that
-- there is no difference, or that the terms to start with are not equivalent.
differenceEqual :: (Rewrite a, Uniplate a) 
                => (a -> a -> Bool) -> a -> a -> Maybe (a, a)
differenceEqual eq p q = do
   guard (eq p q)
   diff eq p q

difference :: (Rewrite a, Uniplate a) => a -> a -> Maybe (a, a)
difference = diff (\_ _ -> True)

shallowEq :: IsTerm a => a -> a -> Bool
shallowEq a b = 
   let f  = liftM fst . getFunction
       ta = toTerm a
       tb = toTerm b 
   in fromMaybe (ta == tb) $ liftM2 (==) (f ta) (f tb)

findOperator :: [Magma a] -> a -> Maybe (Magma a)
findOperator ops a = safeHead $ filter (`isOperator` a) ops
 where isOperator op = isJust . match (magmaView op)

-- local implementation function
diff :: (Rewrite a, Uniplate a) => (a -> a -> Bool) -> a -> a -> Maybe (a, a)
diff eq = rec
 where
   rec p q
      | shallowEq p q =
           case findOperator operators p of
              Just op | isAssociative op && not (isCommutative op) -> do
                 ps <- match (magmaListView op) p
                 qs <- match (magmaListView op) q
                 diffA op ps qs
              _ -> diffList (children p) (children q)
      | otherwise = Just (p, q)

   diffList xs ys
      | length xs /= length ys = Nothing
      | otherwise = 
           case catMaybes (zipWith rec xs ys) of
              [p] -> Just p
              _   -> Nothing
           
   diffA op = curry (make . uncurry rev . f . uncurry rev . f)
    where
      f (p:ps, q:qs) | not (null ps || null qs) && 
                       isNothing (rec p q) && 
                       equal ps qs = 
         f (ps, qs)
      f pair = pair
      
      equal ps qs = builder ps `eq` builder qs
      rev   ps qs = (reverse ps, reverse qs)
      builder     = build (magmaListView op)
      make pair   = 
         case pair of 
            ([p], [q]) -> rec p q
            (ps, qs)   -> Just (builder ps, builder qs)