{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Patch.DMapWithMove where
import Data.Patch.Class
import Data.Patch.MapWithMove (PatchMapWithMove (..))
import qualified Data.Patch.MapWithMove as MapWithMove
import Data.Constraint.Extras
import Data.Dependent.Map (DMap, DSum (..), GCompare (..))
import qualified Data.Dependent.Map as DMap
import Data.Functor.Constant
import Data.Functor.Misc
import Data.Functor.Product
import Data.GADT.Compare (GEq (..))
import Data.GADT.Show (GShow, gshow)
import qualified Data.Map as Map
import Data.Maybe
import Data.Semigroup (Semigroup (..), (<>))
import Data.Some (Some(Some))
import Data.These
newtype PatchDMapWithMove k v = PatchDMapWithMove (DMap k (NodeInfo k v))
data NodeInfo k v a = NodeInfo
{ _nodeInfo_from :: !(From k v a)
, _nodeInfo_to :: !(To k a)
}
deriving (Show)
data From (k :: a -> *) (v :: a -> *) :: a -> * where
From_Insert :: v a -> From k v a
From_Delete :: From k v a
From_Move :: !(k a) -> From k v a
deriving (Show, Read, Eq, Ord)
type To = ComposeMaybe
validPatchDMapWithMove :: forall k v. (GCompare k, GShow k) => DMap k (NodeInfo k v) -> Bool
validPatchDMapWithMove = not . null . validationErrorsForPatchDMapWithMove
validationErrorsForPatchDMapWithMove :: forall k v. (GCompare k, GShow k) => DMap k (NodeInfo k v) -> [String]
validationErrorsForPatchDMapWithMove m =
noSelfMoves <> movesBalanced
where
noSelfMoves = mapMaybe selfMove . DMap.toAscList $ m
selfMove (dst :=> NodeInfo (From_Move src) _) | Just _ <- dst `geq` src = Just $ "self move of key " <> gshow src <> " at destination side"
selfMove (src :=> NodeInfo _ (ComposeMaybe (Just dst))) | Just _ <- src `geq` dst = Just $ "self move of key " <> gshow dst <> " at source side"
selfMove _ = Nothing
movesBalanced = mapMaybe unbalancedMove . DMap.toAscList $ m
unbalancedMove (dst :=> NodeInfo (From_Move src) _) =
case DMap.lookup src m of
Nothing -> Just $ "unbalanced move at destination key " <> gshow dst <> " supposedly from " <> gshow src <> " but source key is not in the patch"
Just (NodeInfo _ (ComposeMaybe (Just dst'))) ->
if isNothing (dst' `geq` dst)
then Just $ "unbalanced move at destination key " <> gshow dst <> " from " <> gshow src <> " is going to " <> gshow dst' <> " instead"
else Nothing
_ ->
Just $ "unbalanced move at destination key " <> gshow dst <> " supposedly from " <> gshow src <> " but source key has no move to key"
unbalancedMove (src :=> NodeInfo _ (ComposeMaybe (Just dst))) =
case DMap.lookup dst m of
Nothing -> Just $ " unbalanced move at source key " <> gshow src <> " supposedly going to " <> gshow dst <> " but destination key is not in the patch"
Just (NodeInfo (From_Move src') _) ->
if isNothing (src' `geq` src)
then Just $ "unbalanced move at source key " <> gshow src <> " to " <> gshow dst <> " is coming from " <> gshow src' <> " instead"
else Nothing
_ ->
Just $ "unbalanced move at source key " <> gshow src <> " supposedly going to " <> gshow dst <> " but destination key is not moving"
unbalancedMove _ = Nothing
instance (GEq k, Has' Eq k (NodeInfo k v)) => Eq (PatchDMapWithMove k v) where
PatchDMapWithMove a == PatchDMapWithMove b = a == b
data Pair1 f g a = Pair1 (f a) (g a)
data Fixup k v a
= Fixup_Delete
| Fixup_Update (These (From k v a) (To k a))
instance GCompare k => Semigroup (PatchDMapWithMove k v) where
PatchDMapWithMove ma <> PatchDMapWithMove mb = PatchDMapWithMove m
where
connections = DMap.toList $ DMap.intersectionWithKey (\_ a b -> Pair1 (_nodeInfo_to a) (_nodeInfo_from b)) ma mb
h :: DSum k (Pair1 (ComposeMaybe k) (From k v)) -> [DSum k (Fixup k v)]
h (_ :=> Pair1 (ComposeMaybe mToAfter) editBefore) = case (mToAfter, editBefore) of
(Just toAfter, From_Move fromBefore)
| isJust $ fromBefore `geq` toAfter
-> [toAfter :=> Fixup_Delete]
| otherwise
-> [ toAfter :=> Fixup_Update (This editBefore)
, fromBefore :=> Fixup_Update (That (ComposeMaybe mToAfter))
]
(Nothing, From_Move fromBefore) -> [fromBefore :=> Fixup_Update (That (ComposeMaybe mToAfter))]
(Just toAfter, _) -> [toAfter :=> Fixup_Update (This editBefore)]
(Nothing, _) -> []
mergeFixups _ Fixup_Delete Fixup_Delete = Fixup_Delete
mergeFixups _ (Fixup_Update a) (Fixup_Update b)
| This x <- a, That y <- b
= Fixup_Update $ These x y
| That y <- a, This x <- b
= Fixup_Update $ These x y
mergeFixups _ _ _ = error "PatchDMapWithMove: incompatible fixups"
fixups = DMap.fromListWithKey mergeFixups $ concatMap h connections
combineNodeInfos _ nia nib = NodeInfo
{ _nodeInfo_from = _nodeInfo_from nia
, _nodeInfo_to = _nodeInfo_to nib
}
applyFixup _ ni = \case
Fixup_Delete -> Nothing
Fixup_Update u -> Just $ NodeInfo
{ _nodeInfo_from = fromMaybe (_nodeInfo_from ni) $ getHere u
, _nodeInfo_to = fromMaybe (_nodeInfo_to ni) $ getThere u
}
m = DMap.differenceWithKey applyFixup (DMap.unionWithKey combineNodeInfos ma mb) fixups
getHere :: These a b -> Maybe a
getHere = \case
This a -> Just a
These a _ -> Just a
That _ -> Nothing
getThere :: These a b -> Maybe b
getThere = \case
This _ -> Nothing
These _ b -> Just b
That b -> Just b
instance GCompare k => Monoid (PatchDMapWithMove k v) where
mempty = PatchDMapWithMove mempty
mappend = (<>)
insertDMapKey :: k a -> v a -> PatchDMapWithMove k v
insertDMapKey k v =
PatchDMapWithMove . DMap.singleton k $ NodeInfo (From_Insert v) (ComposeMaybe Nothing)
moveDMapKey :: GCompare k => k a -> k a -> PatchDMapWithMove k v
moveDMapKey src dst = case src `geq` dst of
Nothing -> PatchDMapWithMove $ DMap.fromList
[ dst :=> NodeInfo (From_Move src) (ComposeMaybe Nothing)
, src :=> NodeInfo From_Delete (ComposeMaybe $ Just dst)
]
Just _ -> mempty
swapDMapKey :: GCompare k => k a -> k a -> PatchDMapWithMove k v
swapDMapKey src dst = case src `geq` dst of
Nothing -> PatchDMapWithMove $ DMap.fromList
[ dst :=> NodeInfo (From_Move src) (ComposeMaybe $ Just src)
, src :=> NodeInfo (From_Move dst) (ComposeMaybe $ Just dst)
]
Just _ -> mempty
deleteDMapKey :: k a -> PatchDMapWithMove k v
deleteDMapKey k = PatchDMapWithMove $ DMap.singleton k $ NodeInfo From_Delete $ ComposeMaybe Nothing
unPatchDMapWithMove :: PatchDMapWithMove k v -> DMap k (NodeInfo k v)
unPatchDMapWithMove (PatchDMapWithMove p) = p
unsafePatchDMapWithMove :: DMap k (NodeInfo k v) -> PatchDMapWithMove k v
unsafePatchDMapWithMove = PatchDMapWithMove
patchDMapWithMove :: (GCompare k, GShow k) => DMap k (NodeInfo k v) -> Either [String] (PatchDMapWithMove k v)
patchDMapWithMove dm =
case validationErrorsForPatchDMapWithMove dm of
[] -> Right $ unsafePatchDMapWithMove dm
errs -> Left errs
mapPatchDMapWithMove :: forall k v v'. (forall a. v a -> v' a) -> PatchDMapWithMove k v -> PatchDMapWithMove k v'
mapPatchDMapWithMove f (PatchDMapWithMove p) = PatchDMapWithMove $
DMap.map (\ni -> ni { _nodeInfo_from = g $ _nodeInfo_from ni }) p
where g :: forall a. From k v a -> From k v' a
g = \case
From_Insert v -> From_Insert $ f v
From_Delete -> From_Delete
From_Move k -> From_Move k
traversePatchDMapWithMove :: forall m k v v'. Applicative m => (forall a. v a -> m (v' a)) -> PatchDMapWithMove k v -> m (PatchDMapWithMove k v')
traversePatchDMapWithMove f = traversePatchDMapWithMoveWithKey $ const f
traversePatchDMapWithMoveWithKey :: forall m k v v'. Applicative m => (forall a. k a -> v a -> m (v' a)) -> PatchDMapWithMove k v -> m (PatchDMapWithMove k v')
traversePatchDMapWithMoveWithKey f (PatchDMapWithMove p) = PatchDMapWithMove <$> DMap.traverseWithKey (nodeInfoMapFromM . g) p
where g :: forall a. k a -> From k v a -> m (From k v' a)
g k = \case
From_Insert v -> From_Insert <$> f k v
From_Delete -> pure From_Delete
From_Move fromKey -> pure $ From_Move fromKey
nodeInfoMapFrom :: (From k v a -> From k v' a) -> NodeInfo k v a -> NodeInfo k v' a
nodeInfoMapFrom f ni = ni { _nodeInfo_from = f $ _nodeInfo_from ni }
nodeInfoMapFromM :: Functor f => (From k v a -> f (From k v' a)) -> NodeInfo k v a -> f (NodeInfo k v' a)
nodeInfoMapFromM f ni = fmap (\result -> ni { _nodeInfo_from = result }) $ f $ _nodeInfo_from ni
weakenPatchDMapWithMoveWith :: forall k v v'. (forall a. v a -> v') -> PatchDMapWithMove k v -> PatchMapWithMove (Some k) v'
weakenPatchDMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ weakenDMapWith g p
where g :: forall a. NodeInfo k v a -> MapWithMove.NodeInfo (Some k) v'
g ni = MapWithMove.NodeInfo
{ MapWithMove._nodeInfo_from = case _nodeInfo_from ni of
From_Insert v -> MapWithMove.From_Insert $ f v
From_Delete -> MapWithMove.From_Delete
From_Move k -> MapWithMove.From_Move $ Some k
, MapWithMove._nodeInfo_to = Some <$> getComposeMaybe (_nodeInfo_to ni)
}
patchDMapWithMoveToPatchMapWithMoveWith :: forall k v v' a. (v a -> v') -> PatchDMapWithMove (Const2 k a) v -> PatchMapWithMove k v'
patchDMapWithMoveToPatchMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ dmapToMapWith g p
where g :: NodeInfo (Const2 k a) v a -> MapWithMove.NodeInfo k v'
g ni = MapWithMove.NodeInfo
{ MapWithMove._nodeInfo_from = case _nodeInfo_from ni of
From_Insert v -> MapWithMove.From_Insert $ f v
From_Delete -> MapWithMove.From_Delete
From_Move (Const2 k) -> MapWithMove.From_Move k
, MapWithMove._nodeInfo_to = unConst2 <$> getComposeMaybe (_nodeInfo_to ni)
}
const2PatchDMapWithMoveWith :: forall k v v' a. (v -> v' a) -> PatchMapWithMove k v -> PatchDMapWithMove (Const2 k a) v'
const2PatchDMapWithMoveWith f (PatchMapWithMove p) = PatchDMapWithMove $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
where g :: (k, MapWithMove.NodeInfo k v) -> DSum (Const2 k a) (NodeInfo (Const2 k a) v')
g (k, ni) = Const2 k :=> NodeInfo
{ _nodeInfo_from = case MapWithMove._nodeInfo_from ni of
MapWithMove.From_Insert v -> From_Insert $ f v
MapWithMove.From_Delete -> From_Delete
MapWithMove.From_Move fromKey -> From_Move $ Const2 fromKey
, _nodeInfo_to = ComposeMaybe $ Const2 <$> MapWithMove._nodeInfo_to ni
}
instance GCompare k => Patch (PatchDMapWithMove k v) where
type PatchTarget (PatchDMapWithMove k v) = DMap k v
apply (PatchDMapWithMove p) old = Just $! insertions `DMap.union` (old `DMap.difference` deletions)
where insertions = DMap.mapMaybeWithKey insertFunc p
insertFunc :: forall a. k a -> NodeInfo k v a -> Maybe (v a)
insertFunc _ ni = case _nodeInfo_from ni of
From_Insert v -> Just v
From_Move k -> DMap.lookup k old
From_Delete -> Nothing
deletions = DMap.mapMaybeWithKey deleteFunc p
deleteFunc :: forall a. k a -> NodeInfo k v a -> Maybe (Constant () a)
deleteFunc _ ni = case _nodeInfo_from ni of
From_Delete -> Just $ Constant ()
_ -> Nothing
getDeletionsAndMoves :: GCompare k => PatchDMapWithMove k v -> DMap k v' -> DMap k (Product v' (ComposeMaybe k))
getDeletionsAndMoves (PatchDMapWithMove p) m = DMap.intersectionWithKey f m p
where f _ v ni = Pair v $ _nodeInfo_to ni