grisette-0.4.0.0: src/Grisette/Core/Data/Union.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeFamilies #-}
-- |
-- Module : Grisette.Core.Data.Union
-- Copyright : (c) Sirui Lu 2021-2023
-- License : BSD-3-Clause (see the LICENSE file)
--
-- Maintainer : siruilu@cs.washington.edu
-- Stability : Experimental
-- Portability : GHC only
module Grisette.Core.Data.Union
( -- * The union data structure.
-- | Please consider using 'Grisette.Core.Control.Monad.UnionM' instead.
Union (..),
ifWithLeftMost,
ifWithStrategy,
fullReconstruct,
)
where
import Control.DeepSeq (NFData (rnf), NFData1 (liftRnf), rnf1)
import Data.Functor.Classes
( Eq1 (liftEq),
Show1 (liftShowsPrec),
showsPrec1,
showsUnaryWith,
)
import Data.Hashable (Hashable (hashWithSalt))
import GHC.Generics (Generic, Generic1)
import Grisette.Core.Data.Class.GPretty
( GPretty (gprettyPrec),
condEnclose,
)
import Grisette.Core.Data.Class.ITEOp (ITEOp (symIte))
import Grisette.Core.Data.Class.LogicalOp (LogicalOp (symNot, (.&&), (.||)))
import Grisette.Core.Data.Class.Mergeable
( Mergeable (rootStrategy),
Mergeable1 (liftRootStrategy),
MergingStrategy (NoStrategy, SimpleStrategy, SortedStrategy),
)
import Grisette.Core.Data.Class.SimpleMergeable
( SimpleMergeable (mrgIte),
SimpleMergeable1 (liftMrgIte),
UnionLike
( mergeWithStrategy,
mrgIfWithStrategy,
mrgSingleWithStrategy,
single,
unionIf
),
UnionPrjOp (ifView, leftMost, singleView),
mrgIf,
)
import Grisette.Core.Data.Class.Solvable (pattern Con)
import Grisette.IR.SymPrim.Data.SymPrim
( AllSyms (allSymsS),
SomeSym (SomeSym),
SymBool,
)
import Language.Haskell.TH.Syntax (Lift)
#if MIN_VERSION_prettyprinter(1,7,0)
import Prettyprinter (align, group, nest, vsep)
#else
import Data.Text.Prettyprint.Doc (align, group, nest, vsep)
#endif
-- | The default union implementation.
data Union a
= -- | A single value
UnionSingle a
| -- | A if value
UnionIf
a
-- ^ Cached leftmost value
!Bool
-- ^ Is merged invariant already maintained?
!SymBool
-- ^ If condition
(Union a)
-- ^ True branch
(Union a)
-- ^ False branch
deriving (Generic, Eq, Lift, Generic1)
instance Eq1 Union where
liftEq e (UnionSingle a) (UnionSingle b) = e a b
liftEq e (UnionIf l1 i1 c1 t1 f1) (UnionIf l2 i2 c2 t2 f2) =
e l1 l2 && i1 == i2 && c1 == c2 && liftEq e t1 t2 && liftEq e f1 f2
liftEq _ _ _ = False
instance (NFData a) => NFData (Union a) where
rnf = rnf1
instance NFData1 Union where
liftRnf _a (UnionSingle a) = _a a
liftRnf _a (UnionIf a bo b l r) =
_a a `seq`
rnf bo `seq`
rnf b `seq`
liftRnf _a l `seq`
liftRnf _a r
-- | Build 'UnionIf' with leftmost cache correctly maintained.
--
-- Usually you should never directly try to build a 'UnionIf' with its
-- constructor.
ifWithLeftMost :: Bool -> SymBool -> Union a -> Union a -> Union a
ifWithLeftMost _ (Con c) t f
| c = t
| otherwise = f
ifWithLeftMost inv cond t f = UnionIf (leftMost t) inv cond t f
{-# INLINE ifWithLeftMost #-}
instance UnionPrjOp Union where
singleView (UnionSingle a) = Just a
singleView _ = Nothing
{-# INLINE singleView #-}
ifView (UnionIf _ _ cond ifTrue ifFalse) = Just (cond, ifTrue, ifFalse)
ifView _ = Nothing
{-# INLINE ifView #-}
leftMost (UnionSingle a) = a
leftMost (UnionIf a _ _ _ _) = a
{-# INLINE leftMost #-}
instance (Mergeable a) => Mergeable (Union a) where
rootStrategy = SimpleStrategy $ ifWithStrategy rootStrategy
{-# INLINE rootStrategy #-}
instance Mergeable1 Union where
liftRootStrategy ms = SimpleStrategy $ ifWithStrategy ms
{-# INLINE liftRootStrategy #-}
instance (Mergeable a) => SimpleMergeable (Union a) where
mrgIte = mrgIf
instance SimpleMergeable1 Union where
liftMrgIte m = mrgIfWithStrategy (SimpleStrategy m)
instance UnionLike Union where
mergeWithStrategy = fullReconstruct
{-# INLINE mergeWithStrategy #-}
single = UnionSingle
{-# INLINE single #-}
unionIf = ifWithLeftMost False
{-# INLINE unionIf #-}
mrgIfWithStrategy = ifWithStrategy
{-# INLINE mrgIfWithStrategy #-}
mrgSingleWithStrategy _ = UnionSingle
{-# INLINE mrgSingleWithStrategy #-}
instance Show1 Union where
liftShowsPrec sp _ i (UnionSingle a) = showsUnaryWith sp "Single" i a
liftShowsPrec sp sl i (UnionIf _ _ cond t f) =
showParen (i > 10) $
showString "If"
. showChar ' '
. showsPrec 11 cond
. showChar ' '
. sp1 11 t
. showChar ' '
. sp1 11 f
where
sp1 = liftShowsPrec sp sl
instance (Show a) => Show (Union a) where
showsPrec = showsPrec1
instance (GPretty a) => GPretty (Union a) where
gprettyPrec n (UnionSingle a) = gprettyPrec n a
gprettyPrec n (UnionIf _ _ cond t f) =
group $
condEnclose (n > 10) "(" ")" $
align $
nest 2 $
vsep
[ "If",
gprettyPrec 11 cond,
gprettyPrec 11 t,
gprettyPrec 11 f
]
instance (Hashable a) => Hashable (Union a) where
s `hashWithSalt` (UnionSingle a) =
s `hashWithSalt` (0 :: Int) `hashWithSalt` a
s `hashWithSalt` (UnionIf _ _ c l r) =
s
`hashWithSalt` (1 :: Int)
`hashWithSalt` c
`hashWithSalt` l
`hashWithSalt` r
instance (AllSyms a) => AllSyms (Union a) where
allSymsS (UnionSingle v) = allSymsS v
allSymsS (UnionIf _ _ c t f) = \l -> SomeSym c : (allSymsS t . allSymsS f $ l)
-- | Fully reconstruct a 'Union' to maintain the merged invariant.
fullReconstruct :: MergingStrategy a -> Union a -> Union a
fullReconstruct strategy (UnionIf _ False cond t f) =
ifWithStrategyInv
strategy
cond
(fullReconstruct strategy t)
(fullReconstruct strategy f)
fullReconstruct _ u = u
{-# INLINE fullReconstruct #-}
-- | Use a specific strategy to build a 'UnionIf' value.
--
-- The merged invariant will be maintained in the result.
ifWithStrategy ::
MergingStrategy a ->
SymBool ->
Union a ->
Union a ->
Union a
ifWithStrategy strategy cond t@(UnionIf _ False _ _ _) f =
ifWithStrategy strategy cond (fullReconstruct strategy t) f
ifWithStrategy strategy cond t f@(UnionIf _ False _ _ _) =
ifWithStrategy strategy cond t (fullReconstruct strategy f)
ifWithStrategy strategy cond t f = ifWithStrategyInv strategy cond t f
{-# INLINE ifWithStrategy #-}
ifWithStrategyInv ::
MergingStrategy a ->
SymBool ->
Union a ->
Union a ->
Union a
ifWithStrategyInv _ (Con v) t f
| v = t
| otherwise = f
ifWithStrategyInv strategy cond (UnionIf _ True condTrue tt _) f
| cond == condTrue = ifWithStrategyInv strategy cond tt f
-- {| symNot cond == condTrue || cond == symNot condTrue = ifWithStrategyInv strategy cond ft f
ifWithStrategyInv strategy cond t (UnionIf _ True condFalse _ ff)
| cond == condFalse = ifWithStrategyInv strategy cond t ff
-- {| symNot cond == condTrue || cond == symNot condTrue = ifWithStrategyInv strategy cond t tf -- buggy here condTrue
ifWithStrategyInv (SimpleStrategy m) cond (UnionSingle l) (UnionSingle r) = UnionSingle $ m cond l r
ifWithStrategyInv strategy@(SortedStrategy idxFun substrategy) cond ifTrue ifFalse = case (ifTrue, ifFalse) of
(UnionSingle _, UnionSingle _) -> ssUnionIf cond ifTrue ifFalse
(UnionSingle _, UnionIf {}) -> sgUnionIf cond ifTrue ifFalse
(UnionIf {}, UnionSingle _) -> gsUnionIf cond ifTrue ifFalse
_ -> ggUnionIf cond ifTrue ifFalse
where
ssUnionIf cond' ifTrue' ifFalse'
| idxt < idxf = ifWithLeftMost True cond' ifTrue' ifFalse'
| idxt == idxf = ifWithStrategyInv (substrategy idxt) cond' ifTrue' ifFalse'
| otherwise = ifWithLeftMost True (symNot cond') ifFalse' ifTrue'
where
idxt = idxFun $ leftMost ifTrue'
idxf = idxFun $ leftMost ifFalse'
{-# INLINE ssUnionIf #-}
sgUnionIf cond' ifTrue' ifFalse'@(UnionIf _ True condf ft ff)
| idxft == idxff = ssUnionIf cond' ifTrue' ifFalse'
| idxt < idxft = ifWithLeftMost True cond' ifTrue' ifFalse'
| idxt == idxft = ifWithLeftMost True (cond' .|| condf) (ifWithStrategyInv (substrategy idxt) cond' ifTrue' ft) ff
| otherwise = ifWithLeftMost True (symNot cond' .&& condf) ft (ifWithStrategyInv strategy cond' ifTrue' ff)
where
idxft = idxFun $ leftMost ft
idxff = idxFun $ leftMost ff
idxt = idxFun $ leftMost ifTrue'
sgUnionIf _ _ _ = undefined
{-# INLINE sgUnionIf #-}
gsUnionIf cond' ifTrue'@(UnionIf _ True condt tt tf) ifFalse'
| idxtt == idxtf = ssUnionIf cond' ifTrue' ifFalse'
| idxtt < idxf = ifWithLeftMost True (cond' .&& condt) tt $ ifWithStrategyInv strategy cond' tf ifFalse'
| idxtt == idxf = ifWithLeftMost True (symNot cond' .|| condt) (ifWithStrategyInv (substrategy idxf) cond' tt ifFalse') tf
| otherwise = ifWithLeftMost True (symNot cond') ifFalse' ifTrue'
where
idxtt = idxFun $ leftMost tt
idxtf = idxFun $ leftMost tf
idxf = idxFun $ leftMost ifFalse'
gsUnionIf _ _ _ = undefined
{-# INLINE gsUnionIf #-}
ggUnionIf cond' ifTrue'@(UnionIf _ True condt tt tf) ifFalse'@(UnionIf _ True condf ft ff)
| idxtt == idxtf = sgUnionIf cond' ifTrue' ifFalse'
| idxft == idxff = gsUnionIf cond' ifTrue' ifFalse'
| idxtt < idxft = ifWithLeftMost True (cond' .&& condt) tt $ ifWithStrategyInv strategy cond' tf ifFalse'
| idxtt == idxft =
let newCond = symIte cond' condt condf
newUnionIfTrue = ifWithStrategyInv (substrategy idxtt) cond' tt ft
newUnionIfFalse = ifWithStrategyInv strategy cond' tf ff
in ifWithLeftMost True newCond newUnionIfTrue newUnionIfFalse
| otherwise = ifWithLeftMost True (symNot cond' .&& condf) ft $ ifWithStrategyInv strategy cond' ifTrue' ff
where
idxtt = idxFun $ leftMost tt
idxtf = idxFun $ leftMost tf
idxft = idxFun $ leftMost ft
idxff = idxFun $ leftMost ff
ggUnionIf _ _ _ = undefined
{-# INLINE ggUnionIf #-}
ifWithStrategyInv NoStrategy cond ifTrue ifFalse = ifWithLeftMost True cond ifTrue ifFalse
ifWithStrategyInv _ _ _ _ = error "Invariant violated"
{-# INLINE ifWithStrategyInv #-}