module Distribution.Types.VersionInterval (
VersionIntervals,
unVersionIntervals,
toVersionIntervals,
fromVersionIntervals,
normaliseVersionRange2,
relaxLastInterval,
relaxHeadInterval,
asVersionIntervals,
VersionInterval (..),
LowerBound(..),
UpperBound(..),
Bound(..),
invariantVersionIntervals,
) where
import Control.Applicative (liftA2)
import Control.Exception (assert)
import Distribution.Compat.Prelude
import Prelude ()
import Distribution.Types.Version
import Distribution.Types.VersionRange.Internal
newtype VersionIntervals = VersionIntervals [VersionInterval]
deriving (Eq, Show, Typeable)
unVersionIntervals :: VersionIntervals -> [VersionInterval]
unVersionIntervals (VersionIntervals is) = is
data VersionInterval = VersionInterval !LowerBound !UpperBound deriving (Eq, Show)
data LowerBound = LowerBound !Version !Bound deriving (Eq, Show)
data UpperBound = NoUpperBound | UpperBound !Version !Bound deriving (Eq, Show)
data Bound = ExclusiveBound | InclusiveBound deriving (Eq, Show)
zeroLowerBound :: LowerBound
zeroLowerBound = LowerBound version0 InclusiveBound
isVersion0 :: Version -> Bool
isVersion0 = (==) version0
stage1 :: VersionRange -> [VersionInterval]
stage1 = cataVersionRange alg where
alg (ThisVersionF v) = [VersionInterval (LowerBound v InclusiveBound) (UpperBound v InclusiveBound)]
alg (LaterVersionF v) = [VersionInterval (LowerBound v ExclusiveBound) NoUpperBound]
alg (OrLaterVersionF v) = [VersionInterval (LowerBound v InclusiveBound) NoUpperBound]
alg (EarlierVersionF v)
| isVersion0 v = []
| otherwise = [VersionInterval zeroLowerBound (UpperBound v ExclusiveBound)]
alg (OrEarlierVersionF v) = [VersionInterval zeroLowerBound (UpperBound v InclusiveBound)]
alg (MajorBoundVersionF v) = [VersionInterval (LowerBound v InclusiveBound) (UpperBound (majorUpperBound v) ExclusiveBound)]
alg (UnionVersionRangesF v1 v2) = v1 ++ v2
alg (IntersectVersionRangesF v1 v2) = mapMaybe nonEmptyInterval $ liftA2 intersectInterval (stage2and3 v1) (stage2and3 v2)
nonEmptyInterval :: VersionInterval -> Maybe VersionInterval
nonEmptyInterval i | nonEmptyVI i = Just i
nonEmptyInterval _ = Nothing
stage2 :: [VersionInterval] -> [VersionInterval]
stage2 = sortBy lowerboundCmp
lowerboundCmp :: VersionInterval -> VersionInterval -> Ordering
lowerboundCmp (VersionInterval (LowerBound v vb) _) (VersionInterval (LowerBound u ub) _) =
compare v u `mappend` compareBound vb ub
where
compareBound :: Bound -> Bound -> Ordering
compareBound InclusiveBound InclusiveBound = EQ
compareBound InclusiveBound ExclusiveBound = LT
compareBound ExclusiveBound InclusiveBound = GT
compareBound ExclusiveBound ExclusiveBound = EQ
postprocess :: [VersionInterval] -> VersionIntervals
postprocess = checkInvariant . VersionIntervals . stage2and3
stage2and3 :: [VersionInterval] -> [VersionInterval]
stage2and3 = stage3 . stage2
stage3 :: [VersionInterval] -> [VersionInterval]
stage3 [] = []
stage3 (VersionInterval lb ub : rest) = stage3go lb ub rest
stage3go :: LowerBound -> UpperBound -> [VersionInterval] -> [VersionInterval]
stage3go !lb NoUpperBound _ = [VersionInterval lb NoUpperBound]
stage3go !lb !ub [] = [VersionInterval lb ub]
stage3go !lb !ub (VersionInterval lb' ub' : rest')
| doesNotTouch ub lb' = VersionInterval lb ub : stage3go lb' ub' rest'
| otherwise = stage3go lb (unionUpper ub ub') rest'
intersectInterval :: VersionInterval -> VersionInterval -> VersionInterval
intersectInterval (VersionInterval lv uv) (VersionInterval lu uu) =
VersionInterval (intersectLower lv lu) (intersectUpper uv uu)
intersectLower :: LowerBound -> LowerBound -> LowerBound
intersectLower (LowerBound v vb) (LowerBound u ub) = case compare v u of
EQ -> LowerBound v (intersectBound vb ub)
LT -> LowerBound u ub
GT -> LowerBound v vb
intersectUpper :: UpperBound -> UpperBound -> UpperBound
intersectUpper NoUpperBound b = b
intersectUpper b NoUpperBound = b
intersectUpper (UpperBound v vb) (UpperBound u ub) = case compare v u of
EQ -> UpperBound v (intersectBound vb ub)
LT -> UpperBound v vb
GT -> UpperBound u ub
intersectBound :: Bound -> Bound -> Bound
intersectBound InclusiveBound InclusiveBound = InclusiveBound
intersectBound _ _ = ExclusiveBound
unionUpper :: UpperBound -> UpperBound -> UpperBound
unionUpper NoUpperBound _ = NoUpperBound
unionUpper _ NoUpperBound = NoUpperBound
unionUpper (UpperBound v vb) (UpperBound u ub) = case compare v u of
EQ -> UpperBound v (unionBound vb ub)
LT -> UpperBound u ub
GT -> UpperBound v vb
unionBound :: Bound -> Bound -> Bound
unionBound ExclusiveBound ExclusiveBound = ExclusiveBound
unionBound _ _ = InclusiveBound
asVersionIntervals :: VersionRange -> [VersionInterval]
asVersionIntervals = unVersionIntervals . toVersionIntervals
doesNotTouch :: UpperBound -> LowerBound -> Bool
doesNotTouch NoUpperBound _ = False
doesNotTouch (UpperBound u ub) (LowerBound l lb) =
(u < l) || (u == l && ub == ExclusiveBound && lb == ExclusiveBound)
invariantVersionIntervals :: VersionIntervals -> Bool
invariantVersionIntervals (VersionIntervals intervals) =
all validInterval intervals &&
all doesNotTouch' adjacentIntervals
where
doesNotTouch' :: (VersionInterval, VersionInterval) -> Bool
doesNotTouch' (VersionInterval _ u, VersionInterval l' _) = doesNotTouch u l'
adjacentIntervals :: [(VersionInterval, VersionInterval)]
adjacentIntervals = case intervals of
[] -> []
(_:tl) -> zip intervals tl
checkInvariant :: VersionIntervals -> VersionIntervals
checkInvariant is = assert (invariantVersionIntervals is) is
validInterval :: VersionInterval -> Bool
validInterval i@(VersionInterval l u) = validLower l && validUpper u && nonEmptyVI i
where
validLower (LowerBound v _) = validVersion v
validUpper NoUpperBound = True
validUpper (UpperBound v _) = validVersion v
nonEmptyVI :: VersionInterval -> Bool
nonEmptyVI (VersionInterval _ NoUpperBound) = True
nonEmptyVI (VersionInterval (LowerBound l lb) (UpperBound u ub)) =
(l < u) || (l == u && lb == InclusiveBound && ub == InclusiveBound)
toVersionIntervals :: VersionRange -> VersionIntervals
toVersionIntervals = postprocess . stage1
fromVersionIntervals :: VersionIntervals -> VersionRange
fromVersionIntervals (VersionIntervals []) = noVersion
fromVersionIntervals (VersionIntervals (x:xs)) = foldr1 unionVersionRanges (fmap intervalToVersionRange (x:|xs))
intervalToVersionRange :: VersionInterval -> VersionRange
intervalToVersionRange (VersionInterval (LowerBound v vb) upper') = case upper' of
NoUpperBound
-> lowerBound
UpperBound u ub
| vb == InclusiveBound
, ub == InclusiveBound
, v == u
-> thisVersion v
UpperBound u ub -> withLowerBound (makeUpperBound u ub)
where
lowerBound :: VersionRange
lowerBound = case vb of
InclusiveBound -> orLaterVersion v
ExclusiveBound -> laterVersion v
withLowerBound :: VersionRange -> VersionRange
withLowerBound vr
| isVersion0 v, vb == InclusiveBound = vr
| otherwise = intersectVersionRanges lowerBound vr
makeUpperBound :: Version -> Bound -> VersionRange
makeUpperBound u InclusiveBound = orEarlierVersion u
makeUpperBound u ExclusiveBound = earlierVersion u
normaliseVersionRange2 :: VersionRange -> VersionRange
normaliseVersionRange2 = fromVersionIntervals . toVersionIntervals
relaxLastInterval :: VersionIntervals -> VersionIntervals
relaxLastInterval (VersionIntervals xs) = VersionIntervals (relaxLastInterval' xs)
where
relaxLastInterval' [] = []
relaxLastInterval' [VersionInterval l _] = [VersionInterval l NoUpperBound]
relaxLastInterval' (i:is) = i : relaxLastInterval' is
relaxHeadInterval :: VersionIntervals -> VersionIntervals
relaxHeadInterval (VersionIntervals xs) = VersionIntervals (relaxHeadInterval' xs)
where
relaxHeadInterval' [] = []
relaxHeadInterval' (VersionInterval _ u : is) = VersionInterval zeroLowerBound u : is