packages feed

crucible-symio-0.1: src/Data/Parameterized/IntervalsMap.hs

-----------------------------------------------------------------------
-- |
-- Module           : Data.IntervalsMap
-- Description      : Nested intervals
-- Copyright        : (c) Galois, Inc 2020
-- License          : BSD3
-- Maintainer       : Daniel Matichuk <dmatichuk@galois.com>
-- Stability        : provisional
------------------------------------------------------------------------

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FunctionalDependencies #-}

module Data.Parameterized.IntervalsMap
  ( IntervalF(..)
  , mkIntervalF
  , Intervals(..)
  , IntervalsMap
  , intersecting
  , unionWith
  , unionWithM
  , singleton
  , insertWith
  , insertWithM
  , intersectionWith
  , mapMIntersecting
  , fromList
  , toList
  , empty
  , IM.Interval(..)
  , mergeIntervalsF
  , mergeWithM
  , AsOrd(..)
  ) where


import           Data.Kind ( Type )
import           Data.Maybe (catMaybes)

import           Data.IntervalMap.Strict ( IntervalMap )
import qualified Data.IntervalMap.Strict as IM
import qualified Data.IntervalMap.Interval as IM
import qualified Data.IntervalMap.Generic.Strict as IMG

import           Data.Parameterized.Classes
import qualified Data.Parameterized.Context as Ctx

newtype AsOrd f tp where
  AsOrd :: { unAsOrd :: f tp } -> AsOrd f tp

instance TestEquality f => Eq (AsOrd f tp) where
  (AsOrd a) == (AsOrd b) = case testEquality a b of
    Just Refl -> True
    _ -> False

instance OrdF f => Ord (AsOrd f tp) where
  compare (AsOrd a) (AsOrd b) = toOrdering $ compareF a b

newtype IntervalF f tp where
  IntervalF :: IM.Interval (AsOrd f tp) -> IntervalF f tp

mkIntervalF ::
  IM.Interval (f tp) -> IntervalF f tp
mkIntervalF ival = IntervalF $ fmap AsOrd ival

instance TestEquality f => TestEquality (IntervalF f) where
  testEquality (IntervalF i1) (IntervalF i2) = case testEquality (unAsOrd (IM.lowerBound i1)) (unAsOrd (IM.lowerBound i2)) of
    Just Refl | i1 == i2 -> Just Refl
    _ -> Nothing

deriving instance TestEquality f => Eq (IntervalF f tp)

deriving instance OrdF f => Ord (IntervalF f tp)

newtype Intervals f ctx = Intervals (Ctx.Assignment (IntervalF f) ctx)

deriving instance TestEquality f => Eq (Intervals f ctx)

instance OrdF f => Ord (Intervals f ctx) where
  compare (Intervals (rest1 Ctx.:> a1)) (Intervals (rest2 Ctx.:> a2)) =
    compare a1 a2 <> compare (Intervals rest1) (Intervals rest2)
  compare (Intervals Ctx.Empty) (Intervals Ctx.Empty) = EQ

data IntervalsMap (f :: k -> Type) (ctx :: Ctx.Ctx k) tp where
  IntervalsMapCons ::
    IntervalMap (AsOrd f idx) (IntervalsMap f ctx tp) ->
    IntervalsMap f (ctx Ctx.::> idx) tp
  IntervalsMapHead :: tp -> IntervalsMap f Ctx.EmptyCtx tp

instance Functor (IntervalsMap f ctx) where
  fmap f ims = case ims of
    IntervalsMapCons ims' -> IntervalsMapCons (fmap (fmap f) ims')
    IntervalsMapHead v -> IntervalsMapHead $ f v

instance Foldable (IntervalsMap f ctx) where
  foldMap f (IntervalsMapCons ims') = foldMap (foldMap f) ims'
  foldMap f (IntervalsMapHead v) = f v

instance Traversable (IntervalsMap f ctx) where
  traverse f (IntervalsMapCons ims') = IntervalsMapCons <$> traverse (traverse f) ims'
  traverse f (IntervalsMapHead v) = IntervalsMapHead <$> f v

intersecting ::
  OrdF f =>
  IntervalsMap f ctx tp ->
  Intervals f ctx ->
  IntervalsMap f ctx tp
intersecting (IntervalsMapCons ims) (Intervals (rest Ctx.:> IntervalF k)) =
  let
    top = IM.intersecting ims k
  in IntervalsMapCons $ fmap (\ims' -> intersecting ims' (Intervals rest)) top
intersecting v (Intervals Ctx.Empty) = v

fromList ::
  OrdF f =>
  [(Intervals f (ctx Ctx.::> a), tp)] ->
  IntervalsMap f (ctx Ctx.::> a) tp
fromList es = foldr (unionWith (\l _ -> l)) empty (map (uncurry singleton) es)

toList ::
  IntervalsMap f ctx tp ->
  [(Intervals f ctx, tp)]
toList (IntervalsMapCons ims) =
  concat $ map (\(k, es) -> addTo k (toList es)) $ (IM.toList ims)
  where
    addTo :: IM.Interval (AsOrd f a) -> [(Intervals f ctx, tp)] -> [(Intervals f (ctx Ctx.::> a), tp)]
    addTo ival = map (\(Intervals ivalf, a) -> (Intervals $ ivalf Ctx.:> IntervalF ival, a))
toList (IntervalsMapHead v) = [(Intervals Ctx.empty, v)]

unionWith ::
  OrdF f =>
  (a -> a -> a) ->
  IntervalsMap f ctx a ->
  IntervalsMap f ctx a ->
  IntervalsMap f ctx a
unionWith f (IntervalsMapCons ims1) (IntervalsMapCons ims2) =
  IntervalsMapCons $ IM.unionWith (unionWith f) ims1 ims2
unionWith f (IntervalsMapHead v1) (IntervalsMapHead v2) = IntervalsMapHead $ f v1 v2

unionWithM ::
  forall f m a ctx.
  OrdF f =>
  Monad m =>
  (a -> a -> m a) ->
  IntervalsMap f ctx a ->
  IntervalsMap f ctx a ->
  m (IntervalsMap f ctx a)
unionWithM f ims1 ims2 = sequenceA $ unionWith go (fmap return ims1) (fmap return ims2)
  where
    go :: m a -> m a -> m a
    go f1 f2 = do
      v1 <- f1
      v2 <- f2
      f v1 v2

data MergeResult a b =
    MergeLeft a
  | MergeRight b
  | MergeCombined a b

mergeWithM ::
  forall f m a b c ctx.
  OrdF f =>
  Monad m =>
  (a -> m c) ->
  (b -> m c) ->
  (a -> b -> m c) ->
  IntervalsMap f ctx a ->
  IntervalsMap f ctx b ->
  m (IntervalsMap f ctx c)
mergeWithM inLeft inRight combine ims1 ims2 = do
  traverse eval $ unionWith go (fmap MergeLeft ims1) (fmap MergeRight ims2)
  where
    eval :: MergeResult a b -> m c
    eval (MergeLeft a) = inLeft a
    eval (MergeRight b) = inRight b
    eval (MergeCombined a b) = combine a b

    go :: MergeResult a b -> MergeResult a b -> MergeResult a b
    go (MergeLeft f1) (MergeRight f2) = MergeCombined f1 f2
    go _ _ = error "mergeWithM: unexpected MergeResult"


singleton ::
  Intervals f ctx ->
  tp ->
  IntervalsMap f ctx tp
singleton (Intervals (rest Ctx.:> IntervalF k)) v = IntervalsMapCons $ IM.singleton k (singleton (Intervals rest) v)
singleton (Intervals Ctx.Empty) v = IntervalsMapHead v

empty :: IntervalsMap f (ctx Ctx.::> a) tp
empty = IntervalsMapCons IM.empty

insertWith ::
  OrdF f =>
  (tp -> tp -> tp) ->
  Intervals f ctx ->
  tp ->
  IntervalsMap f ctx tp ->
  IntervalsMap f ctx tp
insertWith f k v = unionWith f (singleton k v)


insertWithM ::
  forall m f ctx tp.
  Monad m =>
  OrdF f =>
  (tp -> tp -> m tp) ->
  Intervals f ctx ->
  tp ->
  IntervalsMap f ctx tp ->
  m (IntervalsMap f ctx tp)
insertWithM f k v ims = sequenceA $ insertWith go k (return v) (fmap return ims)
  where
    go :: m tp -> m tp -> m tp
    go f1 f2 = do
      v1 <- f1
      v2 <- f2
      f v1 v2

intersectionWith ::
  OrdF f =>
  (a -> b -> c) ->
  IntervalsMap f ctx a ->
  IntervalsMap f ctx b ->
  IntervalsMap f ctx c
intersectionWith f (IntervalsMapCons ims1) (IntervalsMapCons ims2) =
  IntervalsMapCons $ IM.intersectionWith (intersectionWith f) ims1 ims2
intersectionWith f (IntervalsMapHead v1) (IntervalsMapHead v2) = IntervalsMapHead $ f v1 v2

mapMIntersecting' ::
  Monad m =>
  OrdF f =>
  Intervals f ctx ->
  (tp -> m (Maybe tp)) ->
  IntervalsMap f ctx tp ->
  m (Maybe (IntervalsMap f ctx tp))
mapMIntersecting' (Intervals (rest Ctx.:> IntervalF k)) f (IntervalsMapCons ims) = do
  ims' <- mapMIntersectingBase k (\_ -> mapMIntersecting' (Intervals rest) f) ims
  case IM.size ims' of
    0 -> return Nothing
    _ -> return $ Just (IntervalsMapCons ims')
mapMIntersecting' (Intervals Ctx.Empty) f (IntervalsMapHead v) = fmap IntervalsMapHead <$> f v

-- | Adjust entries which intersect the given interval
mapMIntersecting ::
  Monad m =>
  OrdF f =>
  Intervals f (ctx Ctx.::> a) ->
  (tp -> m (Maybe tp)) ->
  IntervalsMap f (ctx Ctx.::> a) tp ->
  m (IntervalsMap f (ctx Ctx.::> a) tp)
mapMIntersecting i f ims = mapMIntersecting' i f ims >>= \case
  Just ims' -> return ims'
  Nothing -> return $ IntervalsMapCons IM.empty


mapMIntersectingBase ::
  forall k v e m.
  Monad m =>
  IMG.Interval k e =>
  Ord k =>
  k ->
  (k -> v -> m (Maybe v)) ->
  IMG.IntervalMap k v ->
  m (IMG.IntervalMap k v)
mapMIntersectingBase k f im = do
  let (pref, inter, suf) = IM.splitIntersecting im k
  case IM.size inter of
    0 -> return im
    _ -> do
      im' <- catMaybes <$> mapM go (IM.toAscList inter)
      return $ IM.fromDistinctAscList (IM.toAscList pref ++ im' ++ IM.toAscList suf)
  where
    go :: (k, v) -> m (Maybe (k, v))
    go (k', v) = f k' v >>= \case
      Just v' -> return $ Just (k', v')
      Nothing -> return Nothing


mergeIntervals ::
  Ord a =>
  IM.Interval a ->
  IM.Interval a ->
  IM.Interval a
mergeIntervals i1 i2 = case (leftClosed, rightClosed) of
  (True, True) -> IM.ClosedInterval lower upper
  (False, True) -> IM.IntervalOC lower upper
  (True, False) -> IM.IntervalCO lower upper
  (False, False) -> IM.OpenInterval lower upper
  where
    leftClosed = (IM.leftClosed i1 && lo1 <= lo2) || (IM.leftClosed i2 && lo2 <= lo1)
    rightClosed = (IM.rightClosed i1 && hi2 <= hi1) || (IM.rightClosed i2 && hi1 <= hi2)
    lo1 = IM.lowerBound i1
    lo2 = IM.lowerBound i2
    hi1 = IM.upperBound i1
    hi2 = IM.upperBound i2
    lower = min lo1 lo2
    upper = max hi1 hi2

mergeIntervalsF ::
  OrdF f =>
  IntervalF f a ->
  IntervalF f a ->
  IntervalF f a
mergeIntervalsF (IntervalF i1) (IntervalF i2) = IntervalF (mergeIntervals i1 i2)