grisette-0.3.0.0: src/Grisette/Core/Data/Union.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# 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
import Data.Functor.Classes
import Data.Hashable
import GHC.Generics
import Grisette.Core.Data.Class.Bool
import Grisette.Core.Data.Class.Mergeable
import Grisette.Core.Data.Class.SimpleMergeable
import Grisette.Core.Data.Class.Solvable
import {-# SOURCE #-} Grisette.IR.SymPrim.Data.SymPrim
import Language.Haskell.TH.Syntax
-- | The default union implementation.
data Union a
= -- | A single value
Single a
| -- | A if value
If
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 (Single a) (Single b) = e a b
liftEq e (If l1 i1 c1 t1 f1) (If 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 (Single a) = _a a
liftRnf _a (If a bo b l r) = _a a `seq` rnf bo `seq` rnf b `seq` liftRnf _a l `seq` liftRnf _a r
-- | Build 'If' with leftmost cache correctly maintained.
--
-- Usually you should never directly try to build a 'If' 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 = If (leftMost t) inv cond t f
{-# INLINE ifWithLeftMost #-}
instance UnionPrjOp Union where
singleView (Single a) = Just a
singleView _ = Nothing
{-# INLINE singleView #-}
ifView (If _ _ cond ifTrue ifFalse) = Just (cond, ifTrue, ifFalse)
ifView _ = Nothing
{-# INLINE ifView #-}
leftMost (Single a) = a
leftMost (If 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 = Single
{-# INLINE single #-}
unionIf = ifWithLeftMost False
{-# INLINE unionIf #-}
mrgIfWithStrategy = ifWithStrategy
{-# INLINE mrgIfWithStrategy #-}
mrgSingleWithStrategy _ = Single
{-# INLINE mrgSingleWithStrategy #-}
instance Show1 Union where
liftShowsPrec sp _ i (Single a) = showsUnaryWith sp "Single" i a
liftShowsPrec sp sl i (If _ _ 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 (Hashable a) => Hashable (Union a) where
s `hashWithSalt` (Single a) = s `hashWithSalt` (0 :: Int) `hashWithSalt` a
s `hashWithSalt` (If _ _ c l r) = s `hashWithSalt` (1 :: Int) `hashWithSalt` c `hashWithSalt` l `hashWithSalt` r
instance (AllSyms a) => AllSyms (Union a) where
allSymsS (Single v) = allSymsS v
allSymsS (If _ _ 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 (If _ 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 'If' value.
--
-- The merged invariant will be maintained in the result.
ifWithStrategy ::
MergingStrategy a ->
SymBool ->
Union a ->
Union a ->
Union a
ifWithStrategy strategy cond t@(If _ False _ _ _) f = ifWithStrategy strategy cond (fullReconstruct strategy t) f
ifWithStrategy strategy cond t f@(If _ 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 (If _ True condTrue tt _) f
| cond == condTrue = ifWithStrategyInv strategy cond tt f
-- {| nots cond == condTrue || cond == nots condTrue = ifWithStrategyInv strategy cond ft f
ifWithStrategyInv strategy cond t (If _ True condFalse _ ff)
| cond == condFalse = ifWithStrategyInv strategy cond t ff
-- {| nots cond == condTrue || cond == nots condTrue = ifWithStrategyInv strategy cond t tf -- buggy here condTrue
ifWithStrategyInv (SimpleStrategy m) cond (Single l) (Single r) = Single $ m cond l r
ifWithStrategyInv strategy@(SortedStrategy idxFun substrategy) cond ifTrue ifFalse = case (ifTrue, ifFalse) of
(Single _, Single _) -> ssIf cond ifTrue ifFalse
(Single _, If {}) -> sgIf cond ifTrue ifFalse
(If {}, Single _) -> gsIf cond ifTrue ifFalse
_ -> ggIf cond ifTrue ifFalse
where
ssIf cond' ifTrue' ifFalse'
| idxt < idxf = ifWithLeftMost True cond' ifTrue' ifFalse'
| idxt == idxf = ifWithStrategyInv (substrategy idxt) cond' ifTrue' ifFalse'
| otherwise = ifWithLeftMost True (nots cond') ifFalse' ifTrue'
where
idxt = idxFun $ leftMost ifTrue'
idxf = idxFun $ leftMost ifFalse'
{-# INLINE ssIf #-}
sgIf cond' ifTrue' ifFalse'@(If _ True condf ft ff)
| idxft == idxff = ssIf 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 (nots cond' &&~ condf) ft (ifWithStrategyInv strategy cond' ifTrue' ff)
where
idxft = idxFun $ leftMost ft
idxff = idxFun $ leftMost ff
idxt = idxFun $ leftMost ifTrue'
sgIf _ _ _ = undefined
{-# INLINE sgIf #-}
gsIf cond' ifTrue'@(If _ True condt tt tf) ifFalse'
| idxtt == idxtf = ssIf cond' ifTrue' ifFalse'
| idxtt < idxf = ifWithLeftMost True (cond' &&~ condt) tt $ ifWithStrategyInv strategy cond' tf ifFalse'
| idxtt == idxf = ifWithLeftMost True (nots cond' ||~ condt) (ifWithStrategyInv (substrategy idxf) cond' tt ifFalse') tf
| otherwise = ifWithLeftMost True (nots cond') ifFalse' ifTrue'
where
idxtt = idxFun $ leftMost tt
idxtf = idxFun $ leftMost tf
idxf = idxFun $ leftMost ifFalse'
gsIf _ _ _ = undefined
{-# INLINE gsIf #-}
ggIf cond' ifTrue'@(If _ True condt tt tf) ifFalse'@(If _ True condf ft ff)
| idxtt == idxtf = sgIf cond' ifTrue' ifFalse'
| idxft == idxff = gsIf cond' ifTrue' ifFalse'
| idxtt < idxft = ifWithLeftMost True (cond' &&~ condt) tt $ ifWithStrategyInv strategy cond' tf ifFalse'
| idxtt == idxft =
let newCond = ites cond' condt condf
newIfTrue = ifWithStrategyInv (substrategy idxtt) cond' tt ft
newIfFalse = ifWithStrategyInv strategy cond' tf ff
in ifWithLeftMost True newCond newIfTrue newIfFalse
| otherwise = ifWithLeftMost True (nots 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
ggIf _ _ _ = undefined
{-# INLINE ggIf #-}
ifWithStrategyInv NoStrategy cond ifTrue ifFalse = ifWithLeftMost True cond ifTrue ifFalse
ifWithStrategyInv _ _ _ _ = error "Invariant violated"
{-# INLINE ifWithStrategyInv #-}