-- Copyright (C) 2002-2003 David Roundy -- Copyright (C) 2009 Ganesh Sittampalam -- -- This program is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2, or (at your option) -- any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program; see the file COPYING. If not, write to -- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -- Boston, MA 02110-1301, USA. {-# OPTIONS_GHC -fno-warn-orphans #-} module Darcs.Patch.Permutations ( removeFL , removeRL , removeCommon , commuteWhatWeCanFL , commuteWhatWeCanRL , genCommuteWhatWeCanRL , genCommuteWhatWeCanFL , partitionFL , partitionRL , partitionFL' , partitionRL' , simpleHeadPermutationsFL , headPermutationsRL , headPermutationsFL , permutationsRL , removeSubsequenceFL , removeSubsequenceRL , partitionConflictingFL , (=\~/=) , (=/~\=) , nubFL ) where import Darcs.Prelude import Data.List ( nubBy ) import Data.Maybe ( mapMaybe ) import Darcs.Patch.Commute ( Commute, commute, commuteFL, commuteRL ) import Darcs.Patch.CommuteFn ( CommuteFn ) import Darcs.Patch.Merge ( CleanMerge(..), cleanMergeFL ) import Darcs.Patch.Witnesses.Eq ( Eq2(..), EqCheck(..), isIsEq ) import Darcs.Patch.Witnesses.Ordered ( FL(..), RL(..), (:>)(..), (:\/:)(..), (:/\:)(..) , (+<+), (+>+) , lengthFL, lengthRL , reverseFL, reverseRL ) import Darcs.Patch.Witnesses.Sealed ( Sealed(..) ) -- | Split an 'FL' according to a predicate, using commutation as necessary, -- into those that satisfy the predicate and can be commuted to the left, and -- those that do not satisfy it and can be commuted to the right. Whatever -- remains stays in the middle. -- -- Note that the predicate @p@ should be invariant under commutation: -- if @commute(x:>y)==Just(y':>x')@ then @p x == p x' && p y == p y'@. partitionFL :: Commute p => (forall wU wV . p wU wV -> Bool) -- ^predicate; if true we would like the patch in the "left" list -> FL p wX wY -- ^input 'FL' -> (FL p :> FL p :> FL p) wX wY -- ^"left", "middle" and "right" partitionFL keepleft ps = case partitionFL' keepleft NilRL NilRL ps of left :> middle :> right -> left :> reverseRL middle :> reverseRL right -- optimise by using an accumulating parameter to track all the "left" -- patches that we've found so far; also do not reverse the result lists partitionFL' :: Commute p => (forall wU wV . p wU wV -> Bool) -> RL p wA wB -- the "middle" patches found so far -> RL p wB wC -- the "right" patches found so far -> FL p wC wD -> (FL p :> RL p :> RL p) wA wD partitionFL' _ middle right NilFL = NilFL :> middle :> right partitionFL' keepleft middle right (p :>: ps) | keepleft p = case commuteWhatWeCanRL (right :> p) of (NilRL :> p' :> right') -> case commuteRL (middle :> p') of Just (p'' :> middle') -> case partitionFL' keepleft middle' right' ps of (a :> b :> c) -> p'' :>: a :> b :> c Nothing -> partitionFL' keepleft (middle :<: p') right' ps (tomiddle :> p' :> right') -> partitionFL' keepleft (middle +<+ tomiddle :<: p') right' ps | otherwise = partitionFL' keepleft middle (right :<: p) ps -- | Split an 'RL' according to a predicate, using commutation as necessary, -- into those that satisfy the predicate and can be commuted to the right, and -- those that do not satisfy it and can be commuted to the left. Whatever -- remains stays in the middle. -- -- Note that the predicate @p@ should be invariant under commutation: -- if @commute(x:>y)==Just(y':>x')@ then @p x == p x' && p y == p y'@. partitionRL' :: forall p wX wY. Commute p => (forall wU wV . p wU wV -> Bool) -> RL p wX wY -> (FL p :> FL p :> RL p) wX wY partitionRL' predicate input = go input NilFL NilFL where go :: RL p wA wB -- input RL -> FL p wB wC -- the "left" patches found so far -> FL p wC wD -- the "middle" patches found so far -> (FL p :> FL p :> RL p) wA wD go NilRL left middle = left :> middle :> NilRL go (ps :<: p) left middle | predicate p = case commuteWhatWeCanFL (p :> left) of (left' :> p' :> NilFL) -> case commuteFL (p' :> middle) of Just (middle' :> p'') -> case go ps left' middle' of (a :> b :> c) -> a :> b :> c :<: p'' Nothing -> go ps left' (p' :>: middle) (left' :> p' :> tomiddle) -> go ps left' (p' :>: tomiddle +>+ middle) | otherwise = go ps (p :>: left) middle -- | Split an 'RL' according to a predicate, using commutation as necessary, -- into those that satisfy the predicate and can be commuted to the right, and -- those that don't, i.e. either do not satisfy the predicate or cannot be -- commuted to the right. -- -- Note that the predicate @p@ should be invariant under commutation: -- if @commute(x:>y)==Just(y':>x')@ then @p x == p x' && p y == p y'@. partitionRL :: forall p wX wY. Commute p => (forall wU wV . p wU wV -> Bool) -- ^predicate; if true we would like the patch in the "right" list -> RL p wX wY -- ^input 'RL' -> (RL p :> RL p) wX wY -- ^"left" and "right" results partitionRL keepright = go . (:> NilFL) where go :: (RL p :> FL p) wA wB -> (RL p :> RL p) wA wB go (NilRL :> qs) = (reverseFL qs :> NilRL) go (ps :<: p :> qs) | keepright p , Just (qs' :> p') <- commuteFL (p :> qs) = case go (ps :> qs') of a :> b -> a :> b :<: p' | otherwise = go (ps :> p :>: qs) commuteWhatWeCanFL :: Commute p => (p :> FL p) wX wY -> (FL p :> p :> FL p) wX wY commuteWhatWeCanFL = genCommuteWhatWeCanFL commute genCommuteWhatWeCanFL :: Commute q => CommuteFn p q -> (p :> FL q) wX wY -> (FL q :> p :> FL q) wX wY genCommuteWhatWeCanFL com (p :> x :>: xs) = case com (p :> x) of Nothing -> case commuteWhatWeCanFL (x :> xs) of xs1 :> x' :> xs2 -> case genCommuteWhatWeCanFL com (p :> xs1) of xs1' :> p' :> xs2' -> xs1' :> p' :> xs2' +>+ x' :>: xs2 Just (x' :> p') -> case genCommuteWhatWeCanFL com (p' :> xs) of a :> p'' :> c -> x' :>: a :> p'' :> c genCommuteWhatWeCanFL _ (y :> NilFL) = NilFL :> y :> NilFL commuteWhatWeCanRL :: Commute p => (RL p :> p) wX wY -> (RL p :> p :> RL p) wX wY commuteWhatWeCanRL = genCommuteWhatWeCanRL commute genCommuteWhatWeCanRL :: Commute p => CommuteFn p q -> (RL p :> q) wX wY -> (RL p :> q :> RL p) wX wY genCommuteWhatWeCanRL com (xs :<: x :> p) = case com (x :> p) of Nothing -> case commuteWhatWeCanRL (xs :> x) of xs1 :> x' :> xs2 -> case genCommuteWhatWeCanRL com (xs2 :> p) of xs1' :> p' :> xs2' -> xs1 :<: x' +<+ xs1' :> p' :> xs2' Just (p' :> x') -> case genCommuteWhatWeCanRL com (xs :> p') of xs1 :> p'' :> xs2 -> xs1 :> p'' :> xs2 :<: x' genCommuteWhatWeCanRL _ (NilRL :> y) = NilRL :> y :> NilRL removeCommon :: (Eq2 p, Commute p) => (FL p :\/: FL p) wX wY -> (FL p :\/: FL p) wX wY removeCommon (xs :\/: NilFL) = xs :\/: NilFL removeCommon (NilFL :\/: xs) = NilFL :\/: xs removeCommon (xs :\/: ys) = rc xs (headPermutationsFL ys) where rc :: (Eq2 p, Commute p) => FL p wX wY -> [(p:>FL p) wX wZ] -> (FL p :\/: FL p) wY wZ rc nms ((n:>ns):_) | Just ms <- removeFL n nms = removeCommon (ms :\/: ns) rc ms [n:>ns] = ms :\/: n:>:ns rc ms (_:nss) = rc ms nss rc _ [] = error "impossible case" -- because we already checked for NilFL case -- | 'removeFL' @x xs@ removes @x@ from @xs@ if @x@ can be commuted to its head. -- Otherwise it returns 'Nothing' removeFL :: (Eq2 p, Commute p) => p wX wY -> FL p wX wZ -> Maybe (FL p wY wZ) removeFL x xs = r x $ headPermutationsFL xs where r :: (Eq2 p, Commute p) => p wX wY -> [(p:>FL p) wX wZ] -> Maybe (FL p wY wZ) r _ [] = Nothing r z ((z':>zs):zss) | IsEq <- z =\/= z' = Just zs | otherwise = r z zss -- | 'removeRL' is like 'removeFL' except with 'RL' removeRL :: (Eq2 p, Commute p) => p wY wZ -> RL p wX wZ -> Maybe (RL p wX wY) removeRL x xs = r x $ headPermutationsRL xs where r :: (Eq2 p, Commute p) => p wY wZ -> [RL p wX wZ] -> Maybe (RL p wX wY) r z ((zs:<:z'):zss) | IsEq <- z =/\= z' = Just zs | otherwise = r z zss r _ _ = Nothing -- | 'removeSubsequenceFL' @ab abc@ returns @Just c'@ where all the patches in -- @ab@ have been commuted out of it, if possible. If this is not possible -- for any reason (the set of patches @ab@ is not actually a subset of @abc@, -- or they can't be commuted out) we return 'Nothing'. removeSubsequenceFL :: (Eq2 p, Commute p) => FL p wA wB -> FL p wA wC -> Maybe (FL p wB wC) removeSubsequenceFL a b | lengthFL a > lengthFL b = Nothing | otherwise = rsFL a b where rsFL :: (Eq2 p, Commute p) => FL p wA wB -> FL p wA wC -> Maybe (FL p wB wC) rsFL NilFL ys = Just ys rsFL (x:>:xs) yys = removeFL x yys >>= removeSubsequenceFL xs -- | 'removeSubsequenceRL' is like @removeSubsequenceFL@ except that it works -- on 'RL' removeSubsequenceRL :: (Eq2 p, Commute p) => RL p wAb wAbc -> RL p wA wAbc -> Maybe (RL p wA wAb) removeSubsequenceRL a b | lengthRL a > lengthRL b = Nothing | otherwise = rsRL a b where rsRL :: (Eq2 p, Commute p) => RL p wAb wAbc -> RL p wA wAbc -> Maybe (RL p wA wAb) rsRL NilRL ys = Just ys rsRL (xs:<:x) yys = removeRL x yys >>= removeSubsequenceRL xs -- | This is a minor variant of 'headPermutationsFL' with each permutation -- is simply returned as a 'FL' simpleHeadPermutationsFL :: Commute p => FL p wX wY -> [FL p wX wY] simpleHeadPermutationsFL ps = map (\ (x:>xs) -> x:>:xs) $ headPermutationsFL ps -- | 'headPermutationsFL' @p:>:ps@ returns all the permutations of the list -- in which one element of @ps@ is commuted past @p@ -- -- Suppose we have a sequence of patches -- -- > X h a y s-t-c k -- -- Suppose furthermore that the patch @c@ depends on @t@, which in turn -- depends on @s@. This function will return -- -- > X :> h a y s t c k -- > h :> X a y s t c k -- > a :> X h y s t c k -- > y :> X h a s t c k -- > s :> X h a y t c k -- > k :> X h a y s t c headPermutationsFL :: Commute p => FL p wX wY -> [(p :> FL p) wX wY] headPermutationsFL NilFL = [] headPermutationsFL (p:>:ps) = (p:>ps) : mapMaybe (swapfirstFL.(p:>)) (headPermutationsFL ps) where swapfirstFL (p1:>p2:>xs) = do p2':>p1' <- commute (p1:>p2) Just $ p2':>p1':>:xs -- | 'headPermutationsRL' is like 'headPermutationsFL', except that we -- operate on an 'RL' (in other words, we are pushing things to the end of a -- patch sequence instead of to the beginning). headPermutationsRL :: Commute p => RL p wX wY -> [RL p wX wY] headPermutationsRL NilRL = [] headPermutationsRL (ps:<:p) = (ps:<:p) : mapMaybe (swapfirstRL.(:<:p)) (headPermutationsRL ps) where swapfirstRL (xs:<:p2:<:p1) = do p1':>p2' <- commute (p2:>p1) Just $ xs:<:p1':<:p2' swapfirstRL _ = Nothing -- | All permutations of an 'RL'. permutationsRL :: Commute p => RL p wX wY -> [RL p wX wY] permutationsRL ps = ps : [qs' :<: q | qs :<: q <- headPermutationsRL ps, qs' <- permutationsRL qs] -- | This commutes patches in the RHS to bring them into the same -- order as the LHS. (=\~/=) :: forall p wA wB wC . (Commute p, Eq2 p) => FL p wA wB -> FL p wA wC -> EqCheck wB wC a =\~/= b | lengthFL a /= lengthFL b = NotEq | otherwise = cmpSameLength a b where cmpSameLength :: FL p wX wY -> FL p wX wZ -> EqCheck wY wZ cmpSameLength (x :>: xs) x_ys | Just ys <- removeFL x x_ys = cmpSameLength xs ys cmpSameLength NilFL NilFL = IsEq cmpSameLength _ _ = NotEq -- | This commutes patches in the RHS to bring them into the same -- order as the LHS. (=/~\=) :: forall p wA wB wC . (Commute p, Eq2 p) => RL p wA wC -> RL p wB wC -> EqCheck wA wB a =/~\= b | lengthRL a /= lengthRL b = NotEq | otherwise = cmpSameLength a b where cmpSameLength :: RL p wX wZ -> RL p wY wZ -> EqCheck wX wY cmpSameLength (xs :<: x) ys_x | Just ys <- removeRL x ys_x = cmpSameLength xs ys cmpSameLength NilRL NilRL = IsEq cmpSameLength _ _ = NotEq -- | A variant of 'nub' that is based on '=\~/= i.e. ignores (internal) ordering. nubFL :: (Commute p, Eq2 p) => [Sealed (FL p wX)] -> [Sealed (FL p wX)] nubFL = nubBy eqSealedFL where eqSealedFL (Sealed ps) (Sealed qs) = isIsEq (ps =\~/= qs) -- | Partition a list into the patches that merge cleanly with the given -- patch and those that don't (including dependencies) partitionConflictingFL :: forall p wX wY wZ . (Commute p, CleanMerge p) => FL p wX wY -> FL p wX wZ -> (FL p :> FL p) wX wY partitionConflictingFL = go NilRL NilRL where go :: RL p wA wB -> RL p wB wC -> FL p wC wD -> FL p wB w -> (FL p :> FL p) wA wD go clean dirty NilFL _ = reverseRL clean :> reverseRL dirty go clean dirty (x:>:xs) ys | Just (x' :> dirty') <- commuteRL (dirty :> x) , Just (ys' :/\: _) <- cleanMergeFL (x' :\/: ys) = go (clean :<: x') dirty' xs ys' | otherwise = go clean (dirty :<: x) xs ys