{-# LANGUAGE GADTs #-} {-# LANGUAGE PackageImports #-} -- | Test-only normalization utilities migrated from former -- Test.MockCat.Internal.ParamSpec.Normalize. These are not part of the -- public API and may change freely. Copied here because only the -- property tests rely on the canonical form & merging behaviour. module Support.ParamSpecNormalize ( NormalParamSpec(..) , normalise , normaliseMany , normaliseManyInt , combine , toParamSpec , acceptsParamSpec , acceptsNormal ) where import qualified Data.HashSet as HS import Data.Hashable (Hashable) import Support.ParamSpec import Data.List (sortOn) -- | Canonical internal form (test-only). data NormalParamSpec a where NExact :: a -> NormalParamSpec a NEnum :: [a] -> NormalParamSpec a NRangeInt :: Int -> Int -> NormalParamSpec Int NUnion :: [NormalParamSpec a] -> NormalParamSpec a NAny :: NormalParamSpec a NOpaquePred :: (a -> Bool) -> String -> NormalParamSpec a instance Show a => Show (NormalParamSpec a) where show (NExact a) = "NExact " ++ show a show (NEnum xs) = "NEnum " ++ show xs show (NRangeInt l h) = "NRangeInt " ++ show (l,h) show (NUnion xs) = "NUnion " ++ show (map show xs) show NAny = "NAny" show (NOpaquePred _ l) = "NOpaquePred " ++ show l instance Eq a => Eq (NormalParamSpec a) where NExact a == NExact b = a == b NEnum xs == NEnum ys = xs == ys NRangeInt a b == NRangeInt c d = a == c && b == d NUnion xs == NUnion ys = xs == ys NAny == NAny = True NOpaquePred _ la == NOpaquePred _ lb = la == lb _ == _ = False normalise :: Hashable a => ParamSpec a -> NormalParamSpec a normalise ps = case ps of PSExact a -> NExact a PSAny -> NAny PSRangeInt l h -> range l h PSPredicate f l -> NOpaquePred f l PSEnum xs -> enumNorm xs where range l h | l == h = NExact l | l < h = NRangeInt l h | otherwise = NRangeInt h l enumNorm [] = NEnum [] enumNorm xs = let uniq = dedupStable xs in case uniq of [] -> NEnum [] [x] -> NExact x (x:y:rs) -> NEnum (x:y:rs) normaliseMany :: Hashable a => [ParamSpec a] -> NormalParamSpec a normaliseMany [] = NEnum [] normaliseMany [p] = normalise p normaliseMany ps = combine (map normalise ps) combine :: [NormalParamSpec a] -> NormalParamSpec a combine specs | any isAny specs = NAny | otherwise = case flatten specs of [] -> NEnum [] [x] -> x xs -> NUnion xs where isAny NAny = True; isAny _ = False flatten [] = [] flatten (NUnion xs:ys) = flatten xs ++ flatten ys flatten (x:xs) = x : flatten xs normaliseManyInt :: [ParamSpec Int] -> NormalParamSpec Int normaliseManyInt ps = finalize merged others where ns = map normalise ps flat = flatten ns (ranges, others) = partitionInt flat merged = mergeRanges ranges flatten [] = [] flatten (NUnion xs:ys) = flatten xs ++ flatten ys flatten (x:xs) = x:flatten xs partitionInt [] = ([],[]) partitionInt (n:ns') = case n of NRangeInt l h -> let (rs,os) = partitionInt ns' in ( (l,h):rs, os) NExact v -> let (rs,os) = partitionInt ns' in ( (v,v):rs, os) _ -> let (rs,os) = partitionInt ns' in ( rs, n:os) mergeRanges [] = [] mergeRanges rs = go (sortOn fst (map norm rs)) where norm (l,h) = if l<=h then (l,h) else (h,l) go [] = [] go [x] = [x] go ((l1,h1):(l2,h2):rest) | h1 + 1 < l2 = (l1,h1) : go ((l2,h2):rest) | otherwise = go ((l1, max h1 h2):rest) finalize [] [] = NEnum [] finalize [(l,h)] [] | l==h = NExact l finalize [(l,h)] [] = NRangeInt l h finalize rs os = let rangeNodes = map (\(l,h) -> if l==h then NExact l else NRangeInt l h) rs parts = rangeNodes ++ os in case parts of [] -> NEnum [] [x] -> x xs -> NUnion xs toParamSpec :: NormalParamSpec a -> ParamSpec a toParamSpec n = case n of NExact a -> PSExact a NEnum xs -> PSEnum xs NUnion _ -> PSAny NAny -> PSAny NOpaquePred f l -> PSPredicate f l NRangeInt l h -> PSRangeInt l h acceptsParamSpec :: Eq a => ParamSpec a -> a -> Bool acceptsParamSpec ps x = case ps of PSExact a -> x == a PSEnum as -> any (== x) as PSRangeInt l h -> let (lo,hi) = if l <= h then (l,h) else (h,l) in lo <= x && x <= hi PSAny -> True PSPredicate f _ -> f x acceptsNormal :: Eq a => NormalParamSpec a -> a -> Bool acceptsNormal n x = case n of NExact a -> x == a NEnum as -> any (== x) as NUnion as -> any (\s -> acceptsNormal s x) as NAny -> True NOpaquePred f _ -> f x NRangeInt l h -> l <= x && x <= h dedupStable :: Hashable a => [a] -> [a] dedupStable = go HS.empty where go _ [] = [] go seen (z:zs) | HS.member z seen = go seen zs | otherwise = z : go (HS.insert z seen) zs