{- Pattern match compilation + exhaustiveness check Ideas came mainly from the following sources: - "GADTs meet their match" - "The implementation of functional programming languages", Chapter 5, "Efficient compilation of pattern matching" -} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} import Data.List import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import Data.Either import Control.Arrow ((***)) import Control.Monad.Identity import Control.Monad.State import Control.Monad.Writer hiding (Alt) import Text.Show.Pretty (ppShow) -------------------------------------------------------------------------------- Name generator monad type NewName = StateT Int newName :: Monad m => NewName m String newName = state $ \i -> ("v" ++ show i, i + 1) -------------------------------------------------------------------------------- data types type Loc = Int -- location info type VarName = String type ConName = String ------------------------------- source data structure {- match a b c with (x:y) (f x -> Just ((>3) -> True)) v@(_:_)@v'@(g -> True) | x > 4 -> ... | Just (z: v) <- h y, Nothing <- h' z, h'' v -> ... | ... where ... ... -} {- match a with x: y | [] <- a -> ... x ... y ... z ... v ... -} data Match = Match [VarName] [Clause] -- match expression (generalized case expression) data Clause = Clause Loc [ParPat] GuardTree type ParPat = [Pat] -- parallel patterns like v@(f -> [])@(Just x) data Pat = PVar VarName | Con ConName [ParPat] | ViewPat Exp ParPat deriving Show data GuardTree = GuardNode Loc Exp ConName [ParPat] GuardTree | Where Binds GuardTree | Alts [GuardTree] | GuardLeaf Loc Exp deriving Show type Binds = [(VarName, Exp)] unWhereAlts :: GuardTree -> Maybe (Binds, [GuardTree]) unWhereAlts = f [] where f acc = \case Where bs t -> f (acc ++ bs) t Alts xs -> g acc xs x -> Just (acc, [x]) g acc [] = Nothing g acc (x: xs) = case unWhereAlts x of Nothing -> g acc xs Just (wh, ts) -> Just (acc ++ wh, ts ++ xs) guardNode :: Loc -> Exp -> ParPat -> GuardTree -> GuardTree guardNode i v [] e = e guardNode i v (w: ws) e = case w of PVar x -> guardNode i v (subst x v ws) $ subst x v e -- don't use let instead ViewPat f p -> guardNode i (ViewApp f v) p $ guardNode i v ws e Con s ps' -> GuardNode i v s ps' $ guardNode i v ws e guardNodes :: Loc -> [(Exp, ParPat)] -> GuardTree -> GuardTree guardNodes _ [] l = l guardNodes i ((v, ws): vs) e = guardNode i v ws $ guardNodes i vs e type CasesInfo = [(Exp, Either (String, [String]) (Exp, Exp))] type InfoWriter = Writer ([Loc], [Loc], [CasesInfo]) ------------------------------- target data structures data Exp = IdExp (Map VarName Exp) Loc -- arbitrary expression | Undefined | Otherwise | Case Exp [Alt] | Var VarName | ViewApp Exp Exp | Let Binds Exp deriving (Show, Eq) where_ [] = id where_ bs = Let bs data Alt = Alt ConName [VarName] Exp deriving (Show, Eq) getId = \case IdExp _ i -> i data Info = Uncovered [ParPat] | Inaccessible Int | Removable Int | Shared Int Int deriving Show -------------------------------------------------------------------------------- conversions between data structures matchToGuardTree :: Match -> GuardTree matchToGuardTree (Match vs cs) = Alts $ flip map cs $ \(Clause i ps rhs) -> guardNodes i (zip (map Var vs) ps) rhs guardTreeToCases :: CasesInfo -> GuardTree -> NewName InfoWriter Exp guardTreeToCases seq t = case unWhereAlts t of Nothing -> tell ([], [], [seq]) >> return Undefined Just (wh, GuardLeaf i e: _) -> tell ([i], [], []) >> return (where_ wh e) Just (wh, cs@(GuardNode i f s _ _: _)) -> do tell ([], [i], []) where_ wh . Case f <$> sequence [ do ns <- replicateM cv newName fmap (Alt cn ns) $ guardTreeToCases ((f, Left (cn, ns)): appAdd f seq) $ Alts $ map (filterGuardTree f cn ns) cs | (cn, cv) <- fromJust $ find ((s `elem`) . map fst) contable ] e -> error $ "gtc: " ++ show e where appAdd (ViewApp f v) x = (v, Right (f, ViewApp f v)): appAdd v x appAdd _ x = x filterGuardTree :: Exp -> ConName -> [VarName] -> GuardTree -> GuardTree filterGuardTree f s ns = \case Where bs t -> Where bs $ filterGuardTree f s ns t -- TODO: shadowing Alts ts -> Alts $ map (filterGuardTree f s ns) ts GuardLeaf i e -> GuardLeaf i e GuardNode i f' s' ps gs | f /= f' -> GuardNode i f' s' ps $ filterGuardTree f s ns gs | s == s' -> filterGuardTree f s ns $ guardNodes i (zip (map Var ns) ps) gs | otherwise -> Alts [] mkInfo :: Int -> InfoWriter ([VarName], Exp) -> (Exp, [Info]) mkInfo i (runWriter -> ((ns, e'), (is, nub -> js, us))) = ( e' , [ (if n > 1 then Shared n else if j `elem` js then Inaccessible else Removable) j | j <- [1..i], let n = length $ filter (==j) is, n /= 1 ] ++ map (Uncovered . mkPat (map Var ns)) us ) where mkPat :: [Exp] -> CasesInfo -> [ParPat] mkPat ns ls = map f ns where f v' = mconcat [either (\(s, vs) -> [Con s $ map (f . Var) vs]) (\(s, v) -> [ViewPat s $ f v]) ps | (v, ps) <- ls, v == v'] tester :: [[ParPat]] -> IO () tester cs@(ps: _) = putStrLn . ppShow . mkInfo (length cs) . flip evalStateT 1 $ do vs <- replicateM (length ps) newName let gs = matchToGuardTree $ Match vs $ zipWith (\a i -> Clause i a $ GuardLeaf i $ IdExp mempty i) cs [1..] (,) vs <$> guardTreeToCases [] gs -------------------------------------------------------------------------------- substitution class Subst a where subst :: VarName -> Exp -> a -> a substs :: (Subst b) => Binds -> b -> b substs rs g = foldr (uncurry subst) g rs instance Subst a => Subst [a] where subst a b = map (subst a b) instance (Subst a, Subst b) => Subst (a, b) where subst a b (c, d) = (subst a b c, subst a b d) instance Subst Exp where subst a b = \case Var v | v == a -> b | otherwise -> Var v ViewApp f x -> ViewApp (subst a b f) (subst a b x) IdExp m i -> IdExp (Map.insert a b $ subst a b <$> m) i instance Subst Pat where subst as v = \case Con s ps -> Con s $ map (subst as v) ps ViewPat f p -> ViewPat (subst as v f) $ subst as v p PVar x -> PVar x instance Subst GuardTree where subst a b = \case Alts ts -> Alts $ subst a b ts Where bs e -> Where (map (id *** subst a b) bs) $ subst a b e GuardNode i e y z x -> GuardNode i (subst a b e) y (subst a b z) $ subst a b x GuardLeaf i e -> GuardLeaf i (subst a b e) -------------------------------------------------------------------------------- constructors contable = [ ["Nil" # 0, "Cons" # 2] , ["False" # 0, "True" # 0] , ["Nothing" # 0, "Just" # 1] ] where (#) = (,) pattern Nil = Con' "Nil" [] pattern Cons a b = Con' "Cons" [a, b] pattern T = Con' "True" [] pattern F = Con' "False" [] pattern No = Con' "Nothing" [] pattern Ju a = Con' "Just" [a] pattern W = [] pattern V v = [PVar v] pattern Con' s ps = [Con s ps] pattern Vi f p = [ViewPat (Var f) p] pattern Guard e = (e, T) -------------------------------------------------------------------------------- test cases diagonal_test = tester [ [W, T, F] , [F, W, T] , [T, F, W] ] seq_test = tester [ [W, F] , [T, F] , [W, W] ] reverseTwo_test = tester [ [Cons (V "x") (Cons (V "y") Nil)] , [V "xs"] ] xor_test = tester [ [V "x", F] , [F, T] , [T, T] ] unwieldy_test = tester [ [Nil, Nil] , [V "xs", V "ys"] ] last_test = tester [ [Cons (V "x") Nil] , [Cons (V "y") (V "xs")] ] last_test' = tester [ [Cons (V "x") Nil] , [Cons (V "y") (Cons (V "x") (V "xs"))] ] zipWith_test = tester [ [V "g", Nil, W] , [V "f", W, Nil] , [V "f", Cons (V "x") (V "xs"), Cons (V "y") (V "ys")] ] zipWith_test' = tester [ [V "f", Cons (V "x") (V "xs"), Cons (V "y") (V "ys")] , [V "g", W, W] ] zipWith_test'' = tester [ [V "f", Cons (V "x") (V "xs"), Cons (V "y") (V "ys")] , [V "g", Nil, Nil] ] uncovered_test = tester [ [Cons (V "x") $ Cons (V "y") $ Cons (V "z") (V "v")] ] view_test = tester [ [Vi "f" (Cons (V "y") (V "s"))] ] view_test' = tester [ [Vi "f" (Cons (Vi "g" $ Ju (V "y")) (V "s"))] , [Vi "h" T] ] view_test'' = tester [ [V "x", [ViewPat (ViewApp (Var "f") (Var "x")) (T `mappend` V "q")] `mappend` V "z"] ] -- TODO: prevent V "q" expansion guard_test = tester [ [V "x" `mappend` Vi "graterThan5" T] , [V "x"] ]