module HaskellCI.ShVersionRange ( compilerVersionPredicate, ) where import HaskellCI.Prelude import Algebra.Lattice (bottom, top, joins) import Algebra.Heyting.Free (Free (..)) import Algebra.Lattice.Wide (Wide (..)) import qualified Algebra.Heyting.Free as F import qualified Algebra.Lattice.Wide as W import qualified Data.Set as S import qualified Distribution.Version as C import HaskellCI.Compiler -- $setup -- >>> import Distribution.Pretty (prettyShow) compilerVersionPredicate :: Set CompilerVersion -> CompilerRange -> String compilerVersionPredicate cvs cr | S.null ghcjsS = wideToString $ freeToWide ghcFree | otherwise = wideToString $ freeToWide $ (Var "$GHCJS" /\ ghcjsFree) \/ (Var "! $GHCJS" /\ ghcFree) where R hdS ghcS ghcjsS = partitionCompilerVersions cvs R hdR ghcR ghcjsR = simplifyCompilerRange cr -- GHCJS ghcjsS' = S.filter (`C.withinRange` ghcjsR) ghcjsS ghcjsFree :: Free String ghcjsFree = ghcVersionPredicate ghcjsRange ghcjsRange = case S.toList ghcjsS' of [] -> C.noVersion [_] -> C.anyVersion _ -> error "multiple GHCJS versions unsupported" -- GHC + GHC HEAD ghcFree :: Free String ghcFree = ghcVersionPredicate (ghcHeadRange \/ ghcRange) -- GHC ghcD = roundDown ghcS ghcS' = S.filter (`C.withinRange` ghcR) ghcS isMinGHC u = Just u == fmap fst (S.minView ghcD) -- if we build with GHC HEAD, than none of known versions is maxGHC. isMaxGHC u | hdS = False | otherwise = Just u == fmap fst (S.maxView ghcD) findGhc :: Version -> VersionRange findGhc v = case (S.lookupLE v ghcD, S.lookupGT v ghcD) of (Nothing, _) -> C.noVersion (Just u, Nothing) -> orLater u (Just u, Just w) -> orLater u /\ earlier w where orLater u | isMinGHC u = C.anyVersion | otherwise = C.orLaterVersion u earlier u | isMaxGHC u = C.anyVersion | otherwise = C.earlierVersion u ghcRange :: VersionRange ghcRange = foldr (\/) C.noVersion $ map findGhc $ S.toList ghcS' -- GHC HEAD ghcHeadRange :: VersionRange ghcHeadRange | hdR && hdS = C.laterVersion (S.findMax ghcS) | otherwise = C.noVersion data R a = R Bool a a deriving (Show) partitionCompilerVersions :: Set CompilerVersion -> R (Set Version) partitionCompilerVersions = foldr f (R False S.empty S.empty) where f (GHC v) (R hd ghc ghcjs) = R hd (S.insert v ghc) ghcjs f (GHCJS v) (R hd ghc ghcjs) = R hd ghc (S.insert v ghcjs) f GHCHead (R _ ghc ghcjs) = R True ghc ghcjs simplifyCompilerRange :: CompilerRange -> R VersionRange simplifyCompilerRange RangeGHC = R True C.anyVersion C.noVersion simplifyCompilerRange RangeGHCJS = R False C.noVersion C.anyVersion simplifyCompilerRange (Range vr) = R (not $ C.hasUpperBound vr) vr vr simplifyCompilerRange (RangeUnion a b) = case (simplifyCompilerRange a, simplifyCompilerRange b) of (R x y z, R u v w) -> R (x \/ u) (y \/ v) (z \/ w) simplifyCompilerRange (RangeInter a b) = case (simplifyCompilerRange a, simplifyCompilerRange b) of (R x y z, R u v w) -> R (x /\ u) (y /\ v) (z /\ w) simplifyCompilerRange (RangePoints vs) = foldr f (R False C.noVersion C.noVersion) vs where f (GHC v) (R hd ghc ghcjs) = R hd (C.thisVersion v \/ ghc) ghcjs f (GHCJS v) (R hd ghc ghcjs) = R hd ghc (C.thisVersion v \/ ghcjs) f GHCHead (R _ ghc ghcjs) = R True ghc ghcjs ghcVersionPredicate :: C.VersionRange -> Free String ghcVersionPredicate vr | equivVersionRanges C.noVersion vr = bottom | equivVersionRanges C.anyVersion vr = top | otherwise = ghcVersionPredicate' vr ghcVersionPredicate' :: C.VersionRange -> Free String ghcVersionPredicate' = conj . C.asVersionIntervals where conj = joins . map disj disj :: C.VersionInterval -> Free String disj (C.LowerBound v C.InclusiveBound, C.UpperBound u C.InclusiveBound) | v == u = Var ("[ $HCNUMVER -eq " ++ f v ++ " ]") disj (lb, C.NoUpperBound) | isInclZero lb = top | otherwise = Var (lower lb) disj (lb, C.UpperBound v b) | isInclZero lb = Var (upper v b) | otherwise = Var (lower lb) /\ Var (upper v b) isInclZero (C.LowerBound v C.InclusiveBound) = v == C.mkVersion [0] isInclZero (C.LowerBound _ C.ExclusiveBound) = False lower (C.LowerBound v C.InclusiveBound) = "[ $HCNUMVER -ge " ++ f v ++ " ]" lower (C.LowerBound v C.ExclusiveBound) = "[ $HCNUMVER -gt " ++ f v ++ " ]" upper v C.InclusiveBound = "[ $HCNUMVER -le " ++ f v ++ " ]" upper v C.ExclusiveBound = "[ $HCNUMVER -lt " ++ f v ++ " ]" f = ghcVersionToString ------------------------------------------------------------------------------- -- Utilities ------------------------------------------------------------------------------- ghcVersionToString :: C.Version -> String ghcVersionToString v = case C.versionNumbers v of [] -> "0" [x] -> show (x * 10000) [x,y] -> show (x * 10000 + y * 100) (x:y:z:_) -> show (x * 10000 + y * 100 + z) -- | Round down a first version in major series. -- -- >>> let rd = map prettyShow . S.toList . roundDown . S.fromList . map C.mkVersion -- -- >>> rd [] -- [] -- -- >>> rd [ [8,0,2] ] -- ["8.0","8.0.3"] -- -- >>> rd [ [8,0,2], [8,2,2], [8,4,4], [8,6,5], [8,8,1] ] -- ["8.0","8.2","8.4","8.6","8.8","8.8.2"] -- -- >>> rd [ [8,6,1], [8,6,2], [8,6,3], [8,6,4], [8,6,5] ] -- ["8.6","8.6.2","8.6.3","8.6.4","8.6.5","8.6.6"] -- roundDown :: Set Version -> Set Version roundDown = go S.empty . S.toList where go !acc [] = acc go !acc [v] | S.member m acc = S.insert v $ S.insert (up v) acc | otherwise = S.insert m $ S.insert (up v) acc where m = let (x,y) = ghcMajVer v in C.mkVersion [x,y] go !acc (v:vs) | S.member m acc = go (S.insert v acc) vs | otherwise = go (S.insert m acc) vs where m = let (x,y) = ghcMajVer v in C.mkVersion [x,y] up v = C.mkVersion $ case C.versionNumbers v of [] -> [1] (x:xs) -> up' x xs up' x [] = [x + 1] up' x (y:ys) = x : up' y ys wideToString :: Wide String -> String wideToString W.Bottom = "false" wideToString W.Top = "top" wideToString (W.Middle x) = x freeToWide :: Free String -> Wide String freeToWide z | z == top = top | z == bottom = bottom | otherwise = Middle (go 0 z) where go :: Int -> Free String -> String go _ (Var x) = x go _ F.Bottom = "false" go _ F.Top = "true" go d (x :/\: y) = parens (d > 3) $ go 4 x ++ " && " ++ go 3 y go d (x :\/: y) = parens (d > 2) $ go 3 x ++ " || " ++ go 2 y go d (x :=>: y) = parens (d > 2) $ "! { " ++ go 0 x ++ " ; } || " ++ go 2 y parens :: Bool -> String -> String parens True s = "{ " ++ s ++ "; }" parens False s = s ------------------------------------------------------------------------------- -- PosNeg ------------------------------------------------------------------------------- {- data PosNeg a = Pos a | Neg a deriving (Eq, Ord, Show, Functor) neg :: PosNeg a -> PosNeg a neg (Pos x) = Neg x neg (Neg x) = Pos x instance Applicative PosNeg where pure = Pos (<*>) = ap instance Monad PosNeg where return = pure Pos x >>= f = f x Neg x >>= f = neg (f x) -}