{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -Wno-orphans #-} module Data.HDiff.Patch.Merge where import Data.Functor.Sum import qualified Data.Map as M import Control.Monad.State import Control.Monad.Writer hiding (Sum) import Control.Monad.Except import Generics.MRSOP.Util import Generics.MRSOP.Base import Generics.MRSOP.Holes import Data.Exists import Data.HDiff.Patch import Data.HDiff.Change import Data.HDiff.Change.Apply import Data.HDiff.Change.Thinning import Data.HDiff.MetaVar -- * Merging Treefixes -- -- $mergingtreefixes -- -- After merging two patches, we might end up with a conflict. -- That is, two changes that can't be reconciled. -- |Hence, a conflict is simply two changes together. data Conflict :: (kon -> *) -> [[[Atom kon]]] -> Atom kon -> * where Conflict :: String -> RawPatch ki codes at -> RawPatch ki codes at -> Conflict ki codes at -- |A 'PatchC' is a patch with potential conflicts inside type PatchC ki codes ix = Holes ki codes (Sum (Conflict ki codes) (CChange ki codes)) ('I ix) -- |Tries to cast a 'PatchC' back to a 'Patch'. Naturally, -- this is only possible if the patch has no conflicts. noConflicts :: PatchC ki codes ix -> Maybe (Patch ki codes ix) noConflicts = holesMapM rmvInL where rmvInL (InL _) = Nothing rmvInL (InR x) = Just x -- |Returns the labels of the conflicts ina a patch. getConflicts :: (ShowHO ki) => PatchC ki codes ix -> [String] getConflicts = snd . runWriter . holesMapM go where go x@(InL (Conflict str _ _)) = tell [str] >> return x go x = return x -- |A merge of @p@ over @q@, denoted @p // q@, is the adaptation -- of @p@ so that it could be applied to an element in the -- image of @q@. (//) :: ( Applicable ki codes (Holes2 ki codes) , HasDatatypeInfo ki fam codes ) => Patch ki codes ix -> Patch ki codes ix -> PatchC ki codes ix p // q = holesJoin $ holesMap (uncurry' reconcile) $ holesLCP p $ q `withFreshNamesFrom` p -- |The 'reconcile' function will try to reconcile disagreeing -- patches. -- -- Precondition: before calling @reconcile p q@, make sure -- @p@ and @q@ are different. reconcile :: forall ki codes fam at . ( Applicable ki codes (Holes2 ki codes) , HasDatatypeInfo ki fam codes ) => RawPatch ki codes at -> RawPatch ki codes at -> Holes ki codes (Sum (Conflict ki codes) (CChange ki codes)) at reconcile p q -- If both patches are alpha-equivalent, we return a copy. | patchEq p q = Hole' $ InR $ makeCopyFrom (distrCChange p) -- Otherwise, this is slightly more involved, but it is intuitively simple. | otherwise = -- First we translate both patches to a 'spined change' representation. let sp = holesJoin $ holesMap (uncurry' holesLCP . unCMatch) p sq = holesJoin $ holesMap (uncurry' holesLCP . unCMatch) q -- spinedChange q in case process sp sq of CantReconcile err -> Hole' $ InL $ Conflict err p q ReturnNominator -> holesMap InR p InstDenominator v -> Hole' $ case runExcept $ transport (scIns sq) v of Left err -> InL $ Conflict (show err) p q Right r -> case utx22change r of Nothing -> InL $ Conflict "chg" p q Just res -> InR res data ProcessOutcome ki codes = ReturnNominator | InstDenominator (Subst ki codes (Holes2 ki codes)) | CantReconcile String -- |Checks whether a variable is a rawCpy, note that we need -- a map that checks occurences of this variable. rawCpy :: M.Map Int Int -> Holes2 ki codes at -> Bool rawCpy ar (Hole' v1 :*: Hole' v2) = metavarGet v1 == metavarGet v2 && M.lookup (metavarGet v1) ar == Just 1 rawCpy _ _ = False simpleCopy :: Holes2 ki codes at -> Bool simpleCopy (Hole' v1 :*: Hole' v2) = metavarGet v1 == metavarGet v2 simpleCopy _ = False isLocalIns :: Holes2 ki codes at -> Bool isLocalIns (Hole _ _ :*: HPeel _ _ _) = True isLocalIns _ = False arityMap :: Holes ki codes (MetaVarIK ki) at -> M.Map Int Int arityMap = go . holesGetHolesAnnWith' metavarGet where go [] = M.empty go (v:vs) = M.alter (Just . maybe 1 (+1)) v (go vs) instance ShowHO x => Show (Exists x) where show (Exists x) = showHO x -- |This will process two changes, represented as a spine and -- inner changes, into a potential merged patch. The result of @process sp sq@ -- is supposed to instruct how to construct a patch that -- can be applied to the image @sq@. -- -- We do so by traversing the places where both @sp@ and @sq@ differ. -- While we perform this traversal we instantiate a valuation of -- potential substitutions, which might be needed in case we -- need to adapt @sp@ to @sq@. After we are done, we know whether -- we need to adapt @sp@, return @sp@ as is, or there is a conflict. -- process :: (Applicable ki codes (Holes2 ki codes)) => HolesHoles2 ki codes at -> HolesHoles2 ki codes at -> ProcessOutcome ki codes process sp sq = case and <$> mapM (exElim $ uncurry' step1) phiD of Nothing -> CantReconcile "p1n" Just True -> if any (exElim $ uncurry' insins) phiID then CantReconcile "p1ii" else ReturnNominator Just False -> let partial = runState (runExceptT $ mapM_ (exElim $ uncurry' step2) phiID) M.empty in case partial of (Left err , _) -> CantReconcile $ "p2n: " ++ err (Right () , s) -> InstDenominator s where (delsp :*: _) = utx2distr sp phiD = holesGetHolesAnnWith' Exists $ holesLCP delsp sq phiID = holesGetHolesAnnWith' Exists $ holesLCP sp sq -- The thing is, 'chg' is a true copy only if v2 occurs only once -- within the whole of 'sq' -- counts how many times a variable appears in 'sq' varmap = arityMap (snd' (utx2distr sq)) {- m var = maybe 0 id $ M.lookup var varmap maxVar = case M.toDescList varmap of [] -> 0 ((v,_):_) -> v -} -- |Step1 checks that the own-variable mappings of the -- anti-unification of (scDel p) and q is of a specific shape. step1 :: Holes ki codes (MetaVarIK ki) at -> HolesHoles2 ki codes at -> Maybe Bool -- If the deletion context of the numerator requires an opaque -- fixed value and the denominator performs any change other -- than a copy, this is a del/mod conflict. step1 (HOpq _ _) (Hole _ chg) | simpleCopy chg = Just True | otherwise = Nothing -- If the numerator imposes no restriction in what it accepts here, -- we return true for this hole step1 (Hole _ _) _ = Just True -- If the numerator expects something specific but the denominator -- merely copies, we still return true step1 _ (Hole _ chg) = Just $ rawCpy varmap chg -- Any other situation requires further analisys. step1 _ _ = Just False -- |Step2 checks a condition for the own-variable mappints -- of the anti-unification of p and q! note this is different -- altogether from step 1!!! step2 :: (Applicable ki codes (Holes2 ki codes)) => HolesHoles2 ki codes at -> HolesHoles2 ki codes at -> ExceptT String (State (Subst ki codes (Holes2 ki codes))) () step2 pp qq = do s <- lift get let del = scDel qq case thinUTx2 (utx2distr pp) del of Left e -> throwError ("th: " ++ show e) Right pp0 -> do let pp' = uncurry' holesLCP pp0 case runExcept (pmatch' s del pp') of Left e -> throwError (show e) Right s' -> put s' >> return () insins :: HolesHoles2 ki codes at -> HolesHoles2 ki codes at -> Bool insins (Hole _ pp) (Hole _ qq) = isLocalIns pp && isLocalIns qq insins _ _ = False