packages feed

pubgrub-cabal-0.1.0.0: src/PubGrub/Cabal/Value.hs

-- | The values a 'PubGrub.Cabal.Var' takes, and sets of them.
--
-- A package variable takes an 'Instance'; a flag variable takes a 'Bool'. A
-- value is therefore a sum, 'Value'. A /set/ of values is not: the subsets of
-- @A + B@ are the pairs of a subset of @A@ and a subset of @B@, so t'ValueSet'
-- is the product of an 'InstanceSet' and a t'BoolSet'. Every set operation is
-- componentwise and total, with no case for sets \"of different kinds\" — a
-- package variable's set simply has an empty flag component, and a flag
-- variable's set an empty instance component. 'InstanceSet' is the same shape
-- one level down: the installed units and the source range are the two halves
-- of a set over @PreInstalled + Version@.
module PubGrub.Cabal.Value
    ( -- * Values
      Value (..)
    , valueInstance
      -- * Sets of values
    , BoolSet (..)
    , boolSet
    , bothValues
    , neitherValue
    , memberBool
    , ValueSet (..)
    , emptyValueSet
    , instanceSet
    , flagSet
    , singletonValue
    , memberValue
      -- * A plan
    , Resolved (..)
    , resolvedInstance
    ) where

import qualified Data.Text                  as T
import           Distribution.Types.Flag    (FlagAssignment, showFlagAssignment)
import           PubGrub.Cabal.InstanceSet  (Instance (..), InstanceSet,
                                             emptyInstanceSet, memberInstance,
                                             singletonInstance)
import           PubGrub.Cabal.PreInstalled (PreInstalledPackage)
import           PubGrub.Cabal.Version      (Version)
import           PubGrub.Pretty             (Pretty (..))
import           PubGrub.SetOps             (SetOps (..))

-- --------------------------------------------------------------------------
-- Values
-- --------------------------------------------------------------------------

-- | What a variable takes: an instance for a package, a Boolean for a flag.
data Value
    = PackageValue Instance
    | FlagValue Bool
    deriving (Eq, Ord, Show)

-- | The instance a package's value holds; nothing for a flag's.
valueInstance :: Value -> Maybe Instance
valueInstance (PackageValue i) = Just i
valueInstance (FlagValue _)    = Nothing

instance Pretty Value where
    pretty (PackageValue i)  = pretty i
    pretty (FlagValue True)  = T.pack "on"
    pretty (FlagValue False) = T.pack "off"

-- --------------------------------------------------------------------------
-- Sets
-- --------------------------------------------------------------------------

-- | A subset of @{False, True}@.
data BoolSet = BoolSet
    { allowsFalse :: Bool
    , allowsTrue  :: Bool
    }
    deriving (Eq, Ord, Show)

instance SetOps BoolSet where
    empty = neitherValue
    union (BoolSet f t) (BoolSet f' t') = BoolSet (f || f') (t || t')
    intersect (BoolSet f t) (BoolSet f' t') = BoolSet (f && f') (t && t')
    difference (BoolSet f t) (BoolSet f' t') = BoolSet (f && not f') (t && not t')
    isEmpty (BoolSet f t) = not f && not t
    isSubsetOf (BoolSet f t) (BoolSet f' t') = (f' || not f) && (t' || not t)
    isDisjoint (BoolSet f t) (BoolSet f' t') = not (f && f') && not (t && t')

-- | The set holding exactly one of a flag's values.
boolSet :: Bool -> BoolSet
boolSet True  = BoolSet False True
boolSet False = BoolSet True False

-- | The sets holding both of a flag's values, and neither.
bothValues, neitherValue :: BoolSet
bothValues = BoolSet True True
neitherValue = BoolSet False False

-- | Is the flag's value in the set?
memberBool :: Bool -> BoolSet -> Bool
memberBool True  = allowsTrue
memberBool False = allowsFalse

instance Pretty BoolSet where
    pretty (BoolSet True True)   = T.pack "on or off"
    pretty (BoolSet False True)  = T.pack "on"
    pretty (BoolSet True False)  = T.pack "off"
    pretty (BoolSet False False) = T.pack "neither"

-- | A set of values: some instances and some Booleans. Both components are
-- canonical, so the set has a lawful 'Eq' and 'Ord' and an exact 'isEmpty'.
data ValueSet = ValueSet
    { vsInstances :: InstanceSet
    , vsFlags     :: BoolSet
    }
    deriving (Eq, Ord, Show)

instance SetOps ValueSet where
    empty = emptyValueSet
    union (ValueSet a b) (ValueSet a' b') = ValueSet (a `union` a') (b `union` b')
    intersect (ValueSet a b) (ValueSet a' b') = ValueSet (a `intersect` a') (b `intersect` b')
    difference (ValueSet a b) (ValueSet a' b') = ValueSet (a `difference` a') (b `difference` b')
    isEmpty (ValueSet a b) = isEmpty a && isEmpty b
    isSubsetOf (ValueSet a b) (ValueSet a' b') = a `isSubsetOf` a' && b `isSubsetOf` b'
    isDisjoint (ValueSet a b) (ValueSet a' b') = a `isDisjoint` a' && b `isDisjoint` b'

-- | No values. @Negative emptyValueSet@ is the always-true term ⊤ for any
-- variable; @Positive emptyValueSet@ is the never-true term ⊥.
emptyValueSet :: ValueSet
emptyValueSet = ValueSet emptyInstanceSet neitherValue

-- | These instances, and no Boolean: the set a term about a package ranges
-- over.
instanceSet :: InstanceSet -> ValueSet
instanceSet s = ValueSet s neitherValue

-- | These Booleans, and no instance: the set a term about a flag ranges over.
flagSet :: BoolSet -> ValueSet
flagSet = ValueSet emptyInstanceSet

-- | The set holding exactly one value — the shape of a decision.
singletonValue :: Value -> ValueSet
singletonValue (PackageValue i) = instanceSet (singletonInstance i)
singletonValue (FlagValue b)    = flagSet (boolSet b)

-- | Is the value in the set?
memberValue :: Value -> ValueSet -> Bool
memberValue (PackageValue i) s = memberInstance i (vsInstances s)
memberValue (FlagValue b) s    = memberBool b (vsFlags s)

-- | The components that hold anything; in practice one, since a set is only
-- ever about one variable.
instance Pretty ValueSet where
    pretty (ValueSet is bs) =
        T.intercalate (T.pack " or ") ([pretty is | not (isEmpty is)] ++ [pretty bs | not (isEmpty bs)])

-- --------------------------------------------------------------------------
-- A plan
-- --------------------------------------------------------------------------

-- | One package of a plan, as a reader sees it: the solver's decisions on a
-- source release and on each of its flags, folded into one entry.
data Resolved
    = Installed PreInstalledPackage
    | Built Version FlagAssignment
    deriving (Eq, Show)

-- | The instance a plan's entry decided on, without its flags.
resolvedInstance :: Resolved -> Instance
resolvedInstance (Installed pip) = UsePreInstalled pip
resolvedInstance (Built v _)     = BuildSource v

instance Pretty Resolved where
    pretty (Installed pip) = pretty (UsePreInstalled pip)
    pretty (Built v fa)    = pretty v <> T.pack (showFlagAssignment fa)