module Darcs.Patch.Ident
( Ident(..)
, SignedIdent
, PatchId
, SignedId(..)
, StorableId(..)
, IdEq2(..)
, merge2FL
, fastRemoveFL
, fastRemoveRL
, fastRemoveSubsequenceRL
, findCommonFL
, commuteToPrefix
, commuteToPostfix
, commuteWhatWeCanToPostfix
, prop_identInvariantUnderCommute
, prop_sameIdentityImpliesCommutable
, prop_equalImpliesSameIdentity
) where
import qualified Data.Set as S
import Darcs.Prelude
import Darcs.Patch.Commute ( Commute, commute, commuteFL, commuteRL )
import Darcs.Patch.Merge ( Merge, mergeFL )
import Darcs.Patch.Permutations ( partitionFL', commuteWhatWeCanFL )
import Darcs.Patch.Show ( ShowPatchFor )
import Darcs.Patch.Witnesses.Eq ( Eq2(..), EqCheck(..), isIsEq )
import Darcs.Patch.Witnesses.Ordered
( (:/\:)(..)
, (:>)(..)
, (:\/:)(..)
, FL(..)
, RL(..)
, Fork(..)
, (+<<+)
, (+>>+)
, mapFL
, mapRL
, reverseRL
)
import Darcs.Patch.Witnesses.Unsafe ( unsafeCoercePEnd, unsafeCoercePStart )
import Darcs.Util.Parser ( Parser )
import Darcs.Util.Printer ( Doc )
type family PatchId (p :: * -> * -> *)
class Ord (PatchId p) => Ident p where
ident :: p wX wY -> PatchId p
class Ord a => SignedId a where
positiveId :: a -> Bool
invertId :: a -> a
type SignedIdent p = (Ident p, SignedId (PatchId p))
class StorableId a where
readId :: Parser a
showId :: ShowPatchFor -> a -> Doc
class IdEq2 p where
(=\^/=) :: p wA wB -> p wA wC -> EqCheck wB wC
(=/^\=) :: p wA wC -> p wB wC -> EqCheck wA wB
default (=\^/=) :: Ident p => p wA wB -> p wA wC -> EqCheck wB wC
p =\^/= q = if ident p == ident q then unsafeCoercePEnd IsEq else NotEq
default (=/^\=) :: Ident p => p wA wC -> p wB wC -> EqCheck wA wB
p =/^\= q = if ident p == ident q then unsafeCoercePStart IsEq else NotEq
instance (Commute p, Ident p) => IdEq2 (FL p) where
ps =\^/= qs
| S.fromList (mapFL ident ps) == S.fromList (mapFL ident qs) = unsafeCoercePEnd IsEq
| otherwise = NotEq
ps =/^\= qs
| S.fromList (mapFL ident ps) == S.fromList (mapFL ident qs) = unsafeCoercePStart IsEq
| otherwise = NotEq
merge2FL :: (Commute p, Merge p, Ident p)
=> FL p wX wY
-> FL p wX wZ
-> (FL p :/\: FL p) wY wZ
merge2FL xs NilFL = NilFL :/\: xs
merge2FL NilFL ys = ys :/\: NilFL
merge2FL xs (y :>: ys)
| Just xs' <- fastRemoveFL y xs = merge2FL xs' ys
merge2FL (x :>: xs) ys
| Just ys' <- fastRemoveFL x ys = merge2FL xs ys'
| otherwise =
case mergeFL (x :\/: ys) of
ys' :/\: x' ->
case merge2FL xs ys' of
ys'' :/\: xs' -> ys'' :/\: (x' :>: xs')
{-# INLINABLE fastRemoveFL #-}
fastRemoveFL :: forall p wX wY wZ. (Commute p, Ident p)
=> p wX wY
-> FL p wX wZ
-> Maybe (FL p wY wZ)
fastRemoveFL a bs
| i `notElem` mapFL ident bs = Nothing
| otherwise = do
_ :> bs' <- pullout NilRL bs
Just (unsafeCoercePStart bs')
where
i = ident a
pullout :: RL p wA wB -> FL p wB wC -> Maybe ((p :> FL p) wA wC)
pullout _ NilFL = Nothing
pullout acc (x :>: xs)
| ident x == i = do
x' :> acc' <- commuteRL (acc :> x)
Just (x' :> acc' +>>+ xs)
| otherwise = pullout (acc :<: x) xs
fastRemoveRL :: forall p wX wY wZ. (Commute p, Ident p)
=> p wY wZ
-> RL p wX wZ
-> Maybe (RL p wX wY)
fastRemoveRL a bs
| i `notElem` mapRL ident bs = Nothing
| otherwise = do
bs' :> _ <- pullout bs NilFL
Just (unsafeCoercePEnd bs')
where
i = ident a
pullout :: RL p wA wB -> FL p wB wC -> Maybe ((RL p :> p) wA wC)
pullout NilRL _ = Nothing
pullout (xs :<: x) acc
| ident x == i = do
acc' :> x' <- commuteFL (x :> acc)
Just (xs +<<+ acc' :> x')
| otherwise = pullout xs (x :>: acc)
fastRemoveSubsequenceRL :: (Commute p, Ident p)
=> RL p wY wZ
-> RL p wX wZ
-> Maybe (RL p wX wY)
fastRemoveSubsequenceRL NilRL ys = Just ys
fastRemoveSubsequenceRL (xs :<: x) ys =
fastRemoveRL x ys >>= fastRemoveSubsequenceRL xs
findCommonFL :: (Commute p, Ident p)
=> FL p wX wY
-> FL p wX wZ
-> Fork (FL p) (FL p) (FL p) wX wY wZ
findCommonFL xs ys =
case commuteToPrefix commonIds xs of
Nothing -> error "failed to commute common patches (lhs)"
Just (cxs :> xs') ->
case commuteToPrefix commonIds ys of
Nothing -> error "failed to commute common patches (rhs)"
Just (cys :> ys') ->
case cxs =\^/= cys of
NotEq -> error "common patches aren't equal"
IsEq -> Fork cxs (reverseRL xs') (reverseRL ys')
where
commonIds =
S.fromList (mapFL ident xs) `S.intersection` S.fromList (mapFL ident ys)
commuteToPrefix :: (Commute p, Ident p)
=> S.Set (PatchId p) -> FL p wX wY -> Maybe ((FL p :> RL p) wX wY)
commuteToPrefix is ps
| prefix :> NilRL :> rest <-
partitionFL' ((`S.member` is) . ident) NilRL NilRL ps = Just (prefix :> rest)
| otherwise = Nothing
commuteToPostfix :: forall p wX wY. (Commute p, Ident p)
=> S.Set (PatchId p) -> RL p wX wY -> Maybe ((FL p :> RL p) wX wY)
commuteToPostfix ids patches = push ids (patches :> NilFL)
where
push :: S.Set (PatchId p) -> (RL p :> FL p) wA wB -> Maybe ((FL p :> RL p) wA wB)
push _ (NilRL :> left) = return (left :> NilRL)
push is (ps :> left)
| S.null is = return (ps +>>+ left :> NilRL)
push is (ps :<: p :> left)
| let i = ident p
, i `S.member` is = do
left' :> p' <- commuteFL (p :> left)
left'' :> right <- push (S.delete i is) (ps :> left')
return (left'' :> right :<: p')
| otherwise = push is (ps :> p :>: left)
commuteWhatWeCanToPostfix :: forall p wX wY. (Commute p, Ident p)
=> S.Set (PatchId p) -> RL p wX wY -> (FL p :> RL p) wX wY
commuteWhatWeCanToPostfix ids patches = push ids (patches :> NilFL)
where
push :: S.Set (PatchId p) -> (RL p :> FL p) wA wB -> (FL p :> RL p) wA wB
push _ (NilRL :> left) = left :> NilRL
push is (ps :> left)
| S.null is = ps +>>+ left :> NilRL
push is (ps :<: p :> left)
| let i = ident p
, i `S.member` is =
case commuteWhatWeCanFL (p :> left) of
left' :> p' :> deps ->
case push (S.delete i is) (ps :> left') of
left'' :> right -> left'' :> (right :<: p' +<<+ deps)
| otherwise = push is (ps :> p :>: left)
prop_identInvariantUnderCommute :: (Commute p, Ident p)
=> (p :> p) wX wY -> Maybe Bool
prop_identInvariantUnderCommute (p :> q) =
case commute (p :> q) of
Just (q' :> p') -> Just $ ident p == ident p' && ident q == ident q'
Nothing -> Nothing
prop_sameIdentityImpliesCommutable :: (Commute p, Eq2 p, Ident p)
=> (p :\/: (RL p :> p)) wX wY -> Maybe Bool
prop_sameIdentityImpliesCommutable (p :\/: (ps :> q))
| ident p == ident q =
case commuteRL (ps :> q) of
Just (p' :> _) -> Just $ isIsEq (p =\/= p')
Nothing -> Just False
| otherwise = Nothing
prop_equalImpliesSameIdentity :: (Eq2 p, Ident p)
=> (p :\/: p) wX wY -> Maybe Bool
prop_equalImpliesSameIdentity (p :\/: q)
| IsEq <- p =\/= q = Just $ ident p == ident q
| otherwise = Nothing