packages feed

funcons-tools-0.1.0.0: src/Funcons/Patterns.hs

{-# LANGUAGE LambdaCase #-}

module Funcons.Patterns where

import Funcons.MSOS
import Funcons.Types
import Funcons.Substitution
import Funcons.Exceptions

import Control.Monad (foldM)
import Data.Function (on)
import Data.List (sortBy)
import Data.Monoid
import Data.Text (unpack)
import qualified Data.BitVector as BV

-- pattern matching
type Matcher a = [a] -> Int -> Env -> Rewrite [(Int, Env)]
type SeqVarInfo = (MetaVar, SeqSortOp, Maybe FTerm)

singleMatcher :: (a -> b -> Env -> Rewrite Env) -> b -> Matcher a
singleMatcher p pat str k env = case drop k str of
    []  -> return []
    f:_ -> eval_catch (p f pat env) >>= \case
            Left ie | failsRule ie  -> return []
                    | otherwise     -> rewrite_rethrow ie
            Right env'              -> return [(k+1,env')]

seqMatcher :: (a -> Maybe FTerm -> Env -> Rewrite Bool) -> ([a] -> Levelled) 
                -> SeqVarInfo -> Matcher a
seqMatcher p level (var, op, mty) str k env = case op of
    QuestionMarkOp -> makeResults ((<=1) . length)
    PlusOp         -> case str of  
                        [] -> return []
                        _  -> makeResults ((>=1) . length)
    StarOp         -> makeResults (const True)
    where makeResults filter_op = do
            furthest <- takeWhileM (\a -> p a mty env) (drop k str)
            return (map ins (filter filter_op $ ordered_subsequences furthest))
            where
              ins fs  = (k+length fs, envInsert var (level fs) env)

              takeWhileM :: (a -> Rewrite Bool) -> [a] -> Rewrite [a]
              takeWhileM _ [] = return []
              takeWhileM p (x:xs) = eval_catch (p x) >>= \case
                Right True          -> (x:) <$> takeWhileM p xs
                Right False         -> return []
                Left ie | failsRule ie  -> return []
                        | otherwise     -> rewrite_rethrow ie

matching :: [a] -> [Matcher a] -> Env -> Rewrite Env
matching str ps env = do 
    matches <- (seqms ps) str 0 env 
    let rule_fail = PatternMismatch ("Pattern match failed: " ++ show (map fst matches))
    case matches of
        [] -> rewrite_throw rule_fail
        [(_,env')] -> return env'
        _  -> internal ("ambiguity not resolved") 
    where   m = length str
        
            seqms :: [Matcher a] -> Matcher a
            seqms = foldr seqlongest lastMatcher

            -- sequencing of matchers specifically to disambiguate safely
            lastMatcher :: Matcher a
            lastMatcher _ k env | k == m       = return [(k,env)]
                                | otherwise    = return []

            seqlongest :: Matcher a -> Matcher a -> Matcher a
            seqlongest p q str k env = do
                matches <- p str k env
                -- implement `longest match' such that it always returns at least one
                -- pattern match (if at least one exists).
                -- (in combination with (`seqm` lastMatcher) it will always
                -- produce exactly one match)
                -- Strategy: try all `pivots' from largest to smallest and `use'
                -- the first that does not yield an empty result list
                foldM tryLargest [] (sortBy (((flip compare) `on` fst)) matches)
             where  tryLargest acc (r, env)
                        | null acc  = q str r env
                        | otherwise = return acc

ordered_subsequences :: [a] -> [[a]]
ordered_subsequences xs = ordered_subsequences' xs []
    where   ordered_subsequences' [] acc = [acc]
            ordered_subsequences' (x:xs) acc = acc : ordered_subsequences' xs (acc++[x])


-- | Patterns for matching funcon terms ('FTerm').
data FPattern   = PValue VPattern
                | PMetaVar MetaVar 
                | PSeqVar MetaVar SeqSortOp
                | PAnnotated FPattern FTerm 
                | PWildCard

f2vPattern :: FPattern -> VPattern
f2vPattern (PValue v) = v
f2vPattern (PMetaVar var) = VPMetaVar var
f2vPattern (PSeqVar var op) = VPSeqVar var op
f2vPattern (PAnnotated fp t) = VPAnnotated (f2vPattern fp) t
f2vPattern PWildCard = VPWildCard

-- | Patterns for matching values ('Values').
data VPattern   = PADT Name [VPattern]
                | VPWildCard
                | PEmptySet
                | PTuple [VPattern]
                | PList [VPattern]
                | VPMetaVar MetaVar 
                | VPAnnotated VPattern FTerm 
                | VPSeqVar MetaVar SeqSortOp
                | VPLit Values 

-- | Variant of 'vsMatch' that is lifted into the 'MSOS' monad.
lifted_vsMatch str pats env = liftRewrite $ vsMatch str pats env
-- | Matching values with value patterns patterns.
-- If the list of patterns is a singleton list, then 'vsMatch' attempts
-- to match the values as a tuple against the pattern as well.
vsMatch :: [Values] -> [VPattern] -> Env -> Rewrite Env 
vsMatch str pats env = case pats of
    [pat]   -> do
        e_ie_env <- eval_catch (strict_vsMatch str [pat] env)
        case e_ie_env of
            Right env' -> return env'
            Left ie | failsRule ie -> vMatch (safe_tuple_val str) pat env
                    | otherwise -> rewrite_rethrow ie
    _       -> strict_vsMatch str pats env

-- | Match stricly values with patterns.
strict_vsMatch :: [Values] -> [VPattern] -> Env -> Rewrite Env
strict_vsMatch str pats env = matching str matchers env
        where   matchers = map (toMatcher vMatch vpSeqVarInfo) pats
                toMatcher prop minfo pat = case minfo pat of
                    Just info   -> seqMatcher isInMaybeTermType ValuesTerm info
                    Nothing     -> singleMatcher prop pat 

-- | Variant of 'premiseStep' that applies substitute and pattern-matching.
premise :: FTerm -> FPattern -> Env -> MSOS Env
premise x pat env = do
    f <- liftRewrite (substitute x env)
    case isVal f of
        True -> msos_throw (SideCondFail "attempting to step a value")
        False -> do f' <- premiseStep f 
                    liftRewrite $ (fMatch f' pat env)

-- | Variant of 'fsMatch' that is lifted into the 'MSOS' monad.
-- If all given terms are values, then 'vsMatch' is used instead.
lifted_fsMatch str pats env = liftRewrite $ fsMatch str pats env
-- | Match a sequence of terms to a sequence of patterns.
fsMatch = fsMatchStrictness False
strict_fsMatch = fsMatchStrictness True
fsMatchStrictness :: Bool -> [Funcons] -> [FPattern] -> Env -> Rewrite Env
fsMatchStrictness strict str pats env 
    -- if all the given funcons are values, then perform value matching instead.
    | not strict && all isVal str = vsMatch (map downcastValue str) (map f2vPattern pats) env
    | otherwise     = matching str matchers env
    where   matchers = map (toMatcher fMatch fpSeqVarInfo) pats
            toMatcher prop minfo pat = case minfo pat of
                Just info   -> seqMatcher (\_ _ _ -> return True) FunconsTerm info
                Nothing     -> singleMatcher prop pat

fMatch :: Funcons -> FPattern -> Env -> Rewrite Env 
fMatch _ PWildCard env = return env
fMatch f (PMetaVar var) env = return (envInsert var (FunconTerm f) env)
fMatch f (PAnnotated pat term) env = do
    ty <- subsAndRewrite term env
    let fail = rewrite_throw (PatternMismatch ("pattern annotation check failed: " ++ show ty))
    rewriteFuncons f >>= \case  
        ValTerm v   -> do   b <- isIn v ty
                            if b then vMatch v (f2vPattern pat) env
                                 else fail   
        otherwise   -> fail 
-- * a sequence variable can match the singleton sequence 
fMatch f pat@(PSeqVar _ _) env = fsMatch [f] [pat] env
-- if the pattern is a value attempt evaluation by rewrite
fMatch f (PValue pat) env = rewriteFuncons f >>= 
    \case   ValTerm v -> vMatch v pat env
            CompTerm _ _ -> rewrite_throw --important, should remain last 
                (PatternMismatch ("could not rewrite to value: " ++ showFuncons f))

lifted_vMaybeMatch mv mp env = liftRewrite $ vMaybeMatch mv mp env
vMaybeMatch :: Maybe Values -> Maybe VPattern -> Env -> Rewrite Env
vMaybeMatch Nothing Nothing env = return env
vMaybeMatch (Just v) (Just p) env = vMatch v p env
vMaybeMatch _ _ env = rewrite_throw (PatternMismatch ("vMaybeMatch")) 

lifted_vMatch v p env = liftRewrite $ vMatch v p env
vMatch :: Values -> VPattern -> Env -> Rewrite Env
vMatch _ (VPWildCard) env = return env
vMatch v (VPMetaVar var) env = return (envInsert var (ValueTerm v) env)
vMatch (Set s) PEmptySet env | null s = return env
vMatch EmptyTuple (PTuple pats) env = vsMatch [] pats env
vMatch (NonEmptyTuple v1 v2 vs) (PTuple pats) env = vsMatch (v1:v2:vs) pats env
vMatch (ADTVal str vs) (PADT con pats) env = adtMatch str con vs pats env
-- strict because we do not want to match the sequence "inside" the list
vMatch (List vs) (PList ps) env = strict_vsMatch vs ps env 
vMatch v (VPAnnotated pat term) env = do
    ty <- subsAndRewrite term env
    isIn v ty >>= \case
        True  -> vMatch v pat env
        False -> rewrite_throw (PatternMismatch ("pattern annotation check failed: " ++ show ty))
vMatch v (VPLit v2) env | v == v2 = return env
-- special treatment for sequence variables:
-- * a (single) sequence variable can match a tuple
vMatch EmptyTuple pat@(VPSeqVar _ _) env = vsMatch [] [pat] env
vMatch (NonEmptyTuple v1 v2 vs) pat@(VPSeqVar _ _) env = vsMatch (v1:v2:vs) [pat] env
-- * a sequence variable can match the singleton sequence
vMatch v pat@(VPSeqVar _ _) env = vsMatch [v] [pat] env
-- * a single value can match a tuple of patterns if it contains sequences
vMatch v (PTuple pats) env = vsMatch [v] pats env 
vMatch v _ _ = rewrite_throw (PatternMismatch ("failed to match"))


adtMatch :: Name -> Name -> [Values] -> [VPattern] -> Env -> Rewrite Env
adtMatch con pat_con vs pats env 
    | con /= pat_con = rewrite_throw (PatternMismatch ("failed to match constructors: (" ++ show (con,pat_con) ++ ")"))
    | otherwise = vsMatch vs pats env


fpSeqVarInfo :: FPattern -> Maybe SeqVarInfo
fpSeqVarInfo (PSeqVar var op) = Just (var, op, Nothing)
fpSeqVarInfo (PAnnotated (PSeqVar var op) _) = Just (var, op, Nothing)
fpSeqVarInfo _ = Nothing

vpSeqVarInfo :: VPattern -> Maybe SeqVarInfo 
vpSeqVarInfo (VPSeqVar var op) = Just (var, op, Nothing)
vpSeqVarInfo (VPAnnotated (VPSeqVar var op) term) = Just (var, op, Just term)
vpSeqVarInfo _ = Nothing


-- | CSB supports five kinds of side conditions.
-- Each of the side conditions are explained below.
-- When a side condition is not accepted an exception is thrown that 
-- is caught by the backtrackign procedure 'evalRules'.
-- A value is a /ground value/ if it is not a thunk (and not composed out of
--  thunks).
data SideCondition  
    -- | /T1 == T2/. Accepted only when /T1/ and /T2/ rewrite to /equal/ ground values.
    = SCEquality FTerm FTerm 
    -- | /T1 =\/= T2/. Accepted only when /T1/ and /T2/ rewrite to /unequal/ ground values.
    | SCInequality FTerm FTerm 
    -- | /T1 : T2/. Accepted only when /T2/ rewrites to a type and /T1/ rewrites to a value of that type.
    | SCIsInSort FTerm FTerm
    -- | /~(T1 : T2)/. Accepted only when /T2/ rewrites to a type and /T1/ rewrites to a value /not/ of that type.
    | SCNotInSort FTerm FTerm
    -- | /T = P/. Accepted only when /T/ rewrites to a value that matches the pattern /P/. (May produce new bindings in 'Env').
    | SCPatternMatch FTerm VPattern

-- | Variant of 'sideCondition' that is lifted into the 'MSOS' monad.
lifted_sideCondition sc env = liftRewrite $ sideCondition sc env

-- | Executes a side condition, given an 'Env' environment, throwing possible exceptions, and 
-- possibly extending the environment.
sideCondition :: SideCondition -> Env -> Rewrite Env
sideCondition cs env = case cs of
    SCEquality term1 term2 -> 
        prop "equality condition" (\a b -> return (a === b)) term1 term2 env
    SCInequality term1 term2 ->     
        prop "inequality condition" (\a b -> return (a =/= b))term1 term2 env
    SCIsInSort term1 term2 -> prop "type-check condition" isIn term1 term2 env
    SCNotInSort term1 term2 -> 
        prop "type-check condition" (\a b -> isIn a b >>= return . not) term1 term2 env
    SCPatternMatch term vpat -> do
        -- special treatment of pattern-matching condition 
        f <- substitute term env
        eval_catch (rewriteFuncons f) >>= \case
            Right (ValTerm v)       -> vMatch v vpat env
            Right (CompTerm lf _)   -> fMatch lf pat env
            Left (_,_,PartialOp _)  -> fMatch f pat env
            Left ie                 -> rewrite_rethrow ie
      where pat = case vpat of
                      VPMetaVar var   -> PMetaVar var 
                      value_pat       -> PValue value_pat
  where prop msg op term1 term2 env = do
            v1 <- subsAndRewrite term1 env
            v2 <- subsAndRewrite term2 env
            b <- op v1 v2
            if b then return env
                 else rewrite_throw (SideCondFail (msg ++ " fails"))

-- piggy back on 
matchTypeParams :: [Types] -> [TypeParam] -> Rewrite Env
matchTypeParams tys tparams = 
    let param_pats = map mkPattern tparams
         where mkPattern (Nothing, kind)  = VPAnnotated VPWildCard kind
               mkPattern (Just var, kind) = VPAnnotated (VPMetaVar var) kind
    in vsMatch (map typeVal tys) param_pats emptyEnv 


-- type checking
isInMaybeTermType :: Values -> (Maybe FTerm) -> Env -> Rewrite Bool
isInMaybeTermType v Nothing _ = return True
isInMaybeTermType v (Just term) env = 
    subsAndRewrite term env >>= isIn v

isIn :: Values -> Values -> Rewrite Bool
isIn v mty = case castType mty of
    Nothing -> sortErr (FValue mty) "rhs of annotation is not a type"
    Just ty -> isInType v ty

isInType :: Values -> Types -> Rewrite Bool
isInType v (ADT nm tys) = do
    DataTypeMembers tparams alts <- typeEnvLookup nm
    env <- matchTypeParams tys tparams 
    or <$> mapM (isInAlt env) alts 
 where isInAlt env (DataTypeInclusion ty_term) = do 
            subsAndRewrite ty_term env >>= isIn v 
       isInAlt env (DataTypeConstructor cons ty_term) = case v of
            ADTVal cons' arg | cons' == cons ->
                subsAndRewrite ty_term env >>= isIn (safe_tuple_val arg)
            _ -> return False
isInType (ADTVal _ _) ADTs = return True
isInType (Atom _) Atoms = return True
isInType (Ascii _) AsciiCharacters = return True
isInType (Bit bv) (Bits n) = return (BV.size bv == n)
isInType v (BoundedIntegers m n) 
    | Int i <- upcastIntegers v = return (i >= m && i <= n)
isInType (ComputationType (ComputesFromType _ _)) ComputationTypes = return True
isInType (ComputationType (ComputesType _)) ComputationTypes = return True
isInType _ EmptyType = return False
isInType (IEEE_Float_32 _) (IEEEFloats Binary32) = return True
isInType (IEEE_Float_64 _) (IEEEFloats Binary64) = return True
isInType v Integers | Int _ <- upcastIntegers v = return True
isInType (List _) (Lists _) = return True 
isInType (Map _) (Maps _ _) = return True
isInType (Multiset _) (Multisets _) = return True 
isInType v Naturals | Nat _ <- upcastNaturals v = return True
isInType v Rationals | Rational _ <- upcastRationals v = return True 
isInType (Set _) (Sets _) = return True
isInType (String _) Strings = return True
isInType (Thunk _) (Thunks _) = return True
isInType v (Tuples ttparams) = case v of
    EmptyTuple              -> isInTupleType [] ttparams
    NonEmptyTuple v1 v2 vs  -> isInTupleType (v1:v2:vs) ttparams
    _                       -> isInTupleType [v] ttparams
isInType (ComputationType (Type _)) Types = return True
isInType v UnicodeCharacters | Char _ <- upcastUnicode v = return True
isInType v (Union ty1 ty2) = (||) <$> isInType v ty1 <*> isInType v ty2
isInType _ Values = return True
isInType (Vector _) (Vectors _) = return True
isInType _ _ = return False

isInTupleType :: [Values] -> [TTParam] -> Rewrite Bool
isInTupleType vs ttparams = 
    eval_catch (vsMatch vs (map mkPattern ttparams) emptyEnv) >>= \case
        Right env' -> return True
        Left (_,_,PatternMismatch _) -> return False
        Left ie -> rewrite_rethrow ie 
    where mkPattern (ty, mop) = VPAnnotated ty_pat (TFuncon ty_funcon)
            where ty_pat = case mop of 
                                Nothing -> VPMetaVar "Dummy"
                                Just op -> VPSeqVar "Dummy" op
                  ty_funcon = type_ ty

typeEnvLookup :: Name -> Rewrite DataTypeMembers 
typeEnvLookup con = Rewrite $ \ctxt st -> 
    case typeLookup con (ty_env ctxt) of
      Nothing -> (Left (evalctxt2exception(Internal "type lookup failed") ctxt)
                        , st, mempty)
      Just members -> (Right members, st, mempty)

-- | 
-- Parameterisable evaluation function function for types.
rewriteType :: Name -> [Values] -> Rewrite Rewritten
rewriteType nm vs 
    | all isType_ vs = 
        rewritten (ComputationType(Type(ADT nm (map downcastValueType vs))))
    | otherwise = sortErr (applyFuncon nm (map FValue vs)) 
                    ("argument of type " <> unpack nm <> " is not a type")