packages feed

g2-0.2.0.0: src/G2/Equiv/EquivADT.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}

module G2.Equiv.EquivADT (
    proofObligations
  , Obligation (..)
  , unAppNoTicks
  ) where

import G2.Language
import qualified G2.Language.ExprEnv as E
import qualified G2.Language.Typing as T
import qualified Data.HashSet as HS

import G2.Execution.NormalForms
import G2.Equiv.G2Calls

import GHC.Generics (Generic)
import Data.Data
import Data.Hashable
import Data.Maybe

-- The information that comes before the Expr pair is used for checking
-- the validity of guarded coinduction and also for counterexample
-- summarization in the event of a SAT output.
-- earlier DataCons in the list are farther out
-- the first Int tag indicates which argument of the constructor this was
-- the second one indicates the total number of arguments for that constructor
-- if there are lambdas, we handle them in Verifier
data Obligation = Ob [(DataCon, Int, Int)] Expr Expr
                  deriving (Show, Eq, Read, Generic, Typeable, Data)

instance Hashable Obligation

proofObligations :: HS.HashSet Name
                 -> State t
                 -> State t
                 -> Expr
                 -> Expr
                 ->  Maybe (HS.HashSet Obligation)
proofObligations ns s1 s2 e1 e2 =
  exprPairing ns s1 s2 e1 e2 HS.empty [] []

removeTicks :: Expr -> Expr
removeTicks (Tick _ e) = removeTicks e
removeTicks e = e

removeAllTicks :: Expr -> Expr
removeAllTicks = modifyASTs removeTicks

unAppNoTicks :: Expr -> [Expr]
unAppNoTicks e =
  let e_list = unApp e
  in case e_list of
    e':t -> (removeTicks e'):t
    _ -> e_list

exprPairing :: HS.HashSet Name -- ^ vars that should not be inlined on either side
            -> State t
            -> State t
            -> Expr
            -> Expr
            -> HS.HashSet Obligation -- ^ accumulator for output obligations
            -> [Name] -- ^ variables inlined previously on the LHS
            -> [Name] -- ^ variables inlined previously on the RHS
            -> Maybe (HS.HashSet Obligation)
exprPairing ns s1@(State {expr_env = h1}) s2@(State {expr_env = h2}) e1 e2 pairs n1 n2 =
  case (e1, e2) of
    _ | e1 == e2 -> Just pairs
    -- ignore all Ticks
    (Tick t1 e1', Tick t2 e2') | labeledErrorName t1 == labeledErrorName t2 -> exprPairing ns s1 s2 e1' e2' pairs n1 n2
    (Tick t e1', _) | isNothing $ labeledErrorName t -> exprPairing ns s1 s2 e1' e2 pairs n1 n2
    (_, Tick t e2') | isNothing $ labeledErrorName t -> exprPairing ns s1 s2 e1 e2' pairs n1 n2
    -- catch mismatches between labeled errors and other SWHNF expressions
    (Tick _ _, _) | isExprValueForm h2 (removeAllTicks e2) -> Nothing
    (_, Tick _ _) | isExprValueForm h1 (removeAllTicks e1) -> Nothing
    -- We have two error labels that are different from each other
    (Tick _ _, Tick _ _) -> Nothing
    -- keeping track of inlined vars prevents looping
    (Var i1, Var i2) | (idName i1) `elem` n1
                     , (idName i2) `elem` n2 -> Just $ HS.insert (Ob [] e1 e2) pairs
                     -- reject distinct polymorphic variables as inequivalent
                     -- this works for function variables too
                     | E.isSymbolic (idName i1) h1
                     , E.isSymbolic (idName i2) h2
                     , idName i1 /= idName i2
                     , not (concretizable $ T.typeOf e1) -> Nothing
    (Var i, _) | E.isSymbolic (idName i) h1 -> Just $ HS.insert (Ob [] e1 e2) pairs
               | m <- idName i
               , not $ m `elem` ns
               , Just e <- E.lookup m h1 -> exprPairing ns s1 s2 e e2 pairs (m:n1) n2
               | not $ (idName i) `elem` ns -> error "unmapped variable"
    (_, Var i) | E.isSymbolic (idName i) h2 -> Just $ HS.insert (Ob [] e1 e2) pairs
               | m <- idName i
               , not $ m `elem` ns
               , Just e <- E.lookup m h2 -> exprPairing ns s1 s2 e1 e pairs n1 (m:n2)
               | not $ (idName i) `elem` ns -> error $ "unmapped variable" ++ show (idName i)
    (Prim p1 _, Prim p2 _) | p1 == Error || p1 == Undefined
                           , p2 == Error || p2 == Undefined -> Just pairs
    -- extra cases for avoiding Error problems
    (Prim p _, _) | (p == Error || p == Undefined)
                  , isExprValueForm h2 (removeAllTicks e2) -> Nothing
    (_, Prim p _) | (p == Error || p == Undefined)
                  , isExprValueForm h1 (removeAllTicks e1) -> Nothing
    (Lit l1, Lit l2) | l1 == l2 -> Just pairs
                     | otherwise -> Nothing
    -- assume that all types line up between the two expressions
    (Type _, Type _) -> Just pairs
    -- See note in `moreRestrictive` regarding comparing DataCons
    _
        | (Data d@(DataCon d1 _)):l1 <- unAppNoTicks e1
        , (Data (DataCon d2 _)):l2 <- unAppNoTicks e2 ->
            if d1 == d2 then
                let ep = uncurry (exprPairing ns s1 s2)
                    ep' hs p = ep p hs n1 n2
                    l = zip l1 l2
                    extend i (Ob ds e1_ e2_) = Ob ((d, i, length l):ds) e1_ e2_
                    make_exts (i, l_pair) = case ep' HS.empty l_pair of
                      Nothing -> Nothing
                      Just hs -> Just $ map (extend i) $ HS.toList hs
                    hl = map make_exts $ zip [0..] l
                in
                if any isNothing hl
                then Nothing
                else Just $ HS.union pairs $ HS.fromList $ concat (map fromJust hl)
                else Nothing
        | otherwise -> Just $ HS.insert (Ob [] e1 e2) pairs