{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {- | Non-linear matching of the lhs of a rewrite rule against a neutral term. Given a lhs Δ ⊢ lhs : B and a candidate term Γ ⊢ t : A we seek a substitution Γ ⊢ σ : Δ such that Γ ⊢ B[σ] = A and Γ ⊢ lhs[σ] = t : A -} module Agda.TypeChecking.Rewriting.NonLinMatch where import Prelude hiding (null, sequence) import Control.Arrow (first, second) import Control.Monad.Trans.Maybe import Control.Monad.State import Debug.Trace import System.IO.Unsafe import Data.Maybe import Data.Functor import Data.Traversable hiding (for) import Data.IntMap (IntMap) import qualified Data.IntMap as IntMap import Data.IntSet (IntSet) import qualified Data.IntSet as IntSet import Agda.Syntax.Common (unArg) import qualified Agda.Syntax.Common as C import Agda.Syntax.Internal import Agda.TypeChecking.EtaContract import Agda.TypeChecking.Free import Agda.TypeChecking.Monad import Agda.TypeChecking.Pretty import Agda.TypeChecking.Reduce import Agda.TypeChecking.Reduce.Monad import Agda.TypeChecking.Substitute import Agda.Utils.Either import Agda.Utils.Except import Agda.Utils.Functor import Agda.Utils.Maybe import Agda.Utils.Monad import Agda.Utils.Null import Agda.Utils.Singleton #include "undefined.h" import Agda.Utils.Impossible -- | Turn a term into a non-linear pattern, treating the -- free variables as pattern variables. -- The first argument is the number of bound variables. class PatternFrom a b where patternFrom :: Int -> a -> TCM b instance (PatternFrom a b) => PatternFrom [a] [b] where patternFrom k = traverse $ patternFrom k instance (PatternFrom a b) => PatternFrom (Arg a) (Arg b) where patternFrom k = traverse $ patternFrom k instance (PatternFrom a b) => PatternFrom (Elim' a) (Elim' b) where patternFrom k = traverse $ patternFrom k instance (PatternFrom a b) => PatternFrom (Dom a) (Dom b) where patternFrom k = traverse $ patternFrom k instance (PatternFrom a b) => PatternFrom (Type' a) (Type' b) where patternFrom k = traverse $ patternFrom k instance PatternFrom Term NLPat where patternFrom k v = do v <- etaContract =<< reduce v let done = return $ PTerm v case ignoreSharing v of Var i es | i < k -> PBoundVar i <$> patternFrom k es | otherwise -> if null es then return $ PVar (i-k) else done Lam i t -> PLam i <$> patternFrom k t Lit{} -> done Def f es -> PDef f <$> patternFrom k es Con c vs -> PDef (conName c) <$> patternFrom k (Apply <$> vs) Pi a b -> PPi <$> patternFrom k a <*> patternFrom k b Sort{} -> done Level{} -> return PWild -- TODO: unLevel and continue DontCare{} -> return PWild MetaV{} -> __IMPOSSIBLE__ Shared{} -> __IMPOSSIBLE__ ExtLam{} -> __IMPOSSIBLE__ instance (PatternFrom a b) => PatternFrom (Abs a) (Abs b) where patternFrom k (Abs n x) = Abs n <$> patternFrom (k+1) x patternFrom k (NoAbs n x) = NoAbs n <$> patternFrom k x -- | Monad for non-linear matching. type NLM = ExceptT Blocked_ (StateT NLMState ReduceM) type NLMState = (Sub, PostponedEquations) liftRed :: ReduceM a -> NLM a liftRed = lift . lift instance HasOptions NLM where pragmaOptions = liftRed pragmaOptions commandLineOptions = liftRed commandLineOptions runNLM :: NLM () -> ReduceM (Either Blocked_ NLMState) runNLM nlm = do (ok,out) <- runStateT (runExceptT nlm) empty case ok of Left block -> return $ Left block Right _ -> return $ Right out traceSDocNLM :: VerboseKey -> Int -> TCM Doc -> NLM a -> NLM a traceSDocNLM k n doc = applyWhenVerboseS k n $ \ cont -> do ReduceEnv env st <- liftRed askR trace (show $ fst $ unsafePerformIO $ runTCM env st doc) cont matchingBlocked :: Blocked_ -> NLM () matchingBlocked = throwError -- | Add substitution @i |-> v@ to result of matching. tellSub :: Int -> Term -> NLM () tellSub i v = do caseMaybeM (IntMap.lookup i <$> gets fst) (modify $ first $ IntMap.insert i v) $ \v' -> do unlessM (liftRed $ equal v v') $ matchingBlocked $ NotBlocked ReallyNotBlocked () -- lies! tellEq :: Int -> Term -> Term -> NLM () tellEq k u v = traceSDocNLM "rewriting" 60 (sep [ text "adding equality between" <+> prettyTCM u , text " and " <+> prettyTCM v , text ("(with " ++ show k ++ " free variables)") ]) $ do modify $ second $ (PostponedEquation k u v:) type Sub = IntMap Term -- | Matching against a term produces a constraint -- which we have to verify after applying -- the substitution computed by matching. data PostponedEquation = PostponedEquation { eqFreeVars :: Int -- ^ Number of free variables in the equation , eqLhs :: Term -- ^ Term from pattern, living in pattern context. , eqRhs :: Term -- ^ Term from scrutinee, living in context where matching was invoked. } type PostponedEquations = [PostponedEquation] -- | Match a non-linear pattern against a neutral term, -- returning a substitution. class Match a b where match :: Int -> a -> b -> NLM () instance Match a b => Match [a] [b] where match k ps vs | length ps == length vs = zipWithM_ (match k) ps vs | otherwise = matchingBlocked $ NotBlocked ReallyNotBlocked () instance Match a b => Match (Arg a) (Arg b) where match k p v = match k (unArg p) (unArg v) instance Match a b => Match (Elim' a) (Elim' b) where match k p v = case (p, v) of (Apply p, Apply v) -> match k p v (Proj x , Proj y ) -> if x == y then return () else traceSDocNLM "rewriting" 100 (sep [ text "mismatch between projections " <+> prettyTCM x , text " and " <+> prettyTCM y ]) mzero (Apply{}, Proj{} ) -> __IMPOSSIBLE__ (Proj{} , Apply{}) -> __IMPOSSIBLE__ instance Match a b => Match (Dom a) (Dom b) where match k p v = match k (C.unDom p) (C.unDom v) instance Match a b => Match (Type' a) (Type' b) where match k p v = match k (unEl p) (unEl v) instance (Match a b, Subst b, Free b, PrettyTCM a, PrettyTCM b) => Match (Abs a) (Abs b) where match k (Abs _ p) (Abs _ v) = match (k+1) p v match k (Abs _ p) (NoAbs _ v) = match (k+1) p (raise 1 v) match k (NoAbs _ p) (Abs _ v) = if (0 `freeIn` v) then no else match k p (raise (-1) v) where no = traceSDocNLM "rewriting" 100 (sep [ text "mismatch between" <+> prettyTCM p , text " and " <+> prettyTCM v ]) mzero match k (NoAbs _ p) (NoAbs _ v) = match k p v instance Match NLPat Term where match k p v = do let yes = return () no = traceSDocNLM "rewriting" 100 (sep [ text "mismatch between" <+> prettyTCM p , text " and " <+> prettyTCM v]) mzero case p of PWild -> yes PVar i -> if null (allFreeVars v `IntSet.intersection` IntSet.fromList [0..(k-1)]) then tellSub i (raise (-k) v) else no PDef f ps -> do v <- liftRed $ constructorForm v case ignoreSharing v of Def f' es | f == f' -> matchArgs k ps es | otherwise -> no Con c vs | f == conName c -> matchArgs k ps (Apply <$> vs) | otherwise -> no MetaV m es -> do matchingBlocked $ Blocked m () _ -> no PLam i p' -> do let body = Abs (absName p') $ raise 1 v `apply` [C.Arg i (var 0)] body <- liftRed (etaContract =<< reduce' body) match k p' body PPi pa pb -> case ignoreSharing v of Pi a b -> match k pa a >> match k pb b _ -> no PBoundVar i ps -> case ignoreSharing v of Var i' es | i == i' -> matchArgs k ps es _ -> no PTerm u -> tellEq k u v where matchArgs :: Int -> [Elim' NLPat] -> Elims -> NLM () matchArgs k ps es = match k ps =<< liftRed (etaContract =<< reduce' es) makeSubstitution :: Sub -> Substitution makeSubstitution sub | IntMap.null sub = idS | otherwise = map val [0 .. highestIndex] ++# raiseS (highestIndex + 1) where highestIndex = fst $ IntMap.findMax sub -- find highest key val i = fromMaybe (var i) $ IntMap.lookup i sub checkPostponedEquations :: Substitution -> PostponedEquations -> ReduceM Bool checkPostponedEquations sub eqs = andM $ for eqs $ \ (PostponedEquation k lhs rhs) -> equal (applySubst (liftS k sub) lhs) rhs -- main function nonLinMatch :: (Match a b) => a -> b -> ReduceM (Either Blocked_ Substitution) nonLinMatch p v = do let no msg b = traceSDoc "rewriting" 100 (sep [ text "matching failed during" <+> text msg , text "blocking: " <+> text (show b) ]) $ return (Left b) caseEitherM (runNLM $ match 0 p v) (no "matching") $ \ (s, eqs) -> do let sub = makeSubstitution s traceSDoc "rewriting" 90 (text $ "sub = " ++ show sub) $ do ifM (checkPostponedEquations sub eqs) (return $ Right sub) (no "checking of postponed equations" $ NotBlocked ReallyNotBlocked ()) -- more lies -- | Untyped βη-equality, does not handle things like empty record types. equal :: Term -> Term -> ReduceM Bool equal u v = do (u, v) <- etaContract =<< normalise' (u, v) let ok = u == v if ok then return True else traceSDoc "rewriting" 100 (sep [ text "mismatch between " <+> prettyTCM u , text " and " <+> prettyTCM v ]) $ return False