packages feed

ddc-source-tetra-0.4.3.1: DDC/Source/Tetra/Transform/Guards.hs

{-# LANGUAGE TypeFamilies, OverloadedStrings #-}

-- | Desugar guards and nested patterns to match expressions.
module DDC.Source.Tetra.Transform.Guards
        ( type S, evalState, newVar
        , desugarModule)
where
import DDC.Source.Tetra.Module
import DDC.Source.Tetra.Prim
import DDC.Source.Tetra.Exp
import Data.Monoid
import Data.Text                        (Text)
import Control.Monad
import qualified DDC.Data.SourcePos     as SP
import qualified Control.Monad.State    as S
import qualified Data.Text              as Text


-------------------------------------------------------------------------------
-- | Desugar guards and nested patterns to match expressions.
desugarModule :: Module Source -> S (Module Source)
desugarModule mm
 = do   ts'     <- mapM desugarTop $ moduleTops mm
        return  $ mm { moduleTops = ts' }


-------------------------------------------------------------------------------
-- | Desugar a top-level thing.
desugarTop    :: Top Source -> S (Top Source)
desugarTop tt
 = case tt of
        TopClause sp c  -> TopClause sp <$> desugarCl sp c
        TopData{}       -> return tt
        TopType{}       -> return tt


-------------------------------------------------------------------------------
-- | Desugar a clause.
desugarCl :: SP -> Clause -> S Clause
desugarCl _sp cc
 = case cc of
        SSig{}
         -> return cc

        SLet sp mt ps gxs
         -> do  (ps', gsParam) <- stripParamsToGuards ps
                gxs'    <- mapM (desugarGX sp >=> (return . cleanGX))
                        $  map  (wrapGuards gsParam) gxs

                return $ SLet sp mt ps' gxs'


-------------------------------------------------------------------------------
-- | Desugar an expression.
desugarX :: SP -> Exp -> S Exp
desugarX sp xx
 = case xx of
        -- Boilerplate.
        XAnnot sp' x     -> XAnnot sp' <$> desugarX sp' x
        XVar{}           -> pure xx
        XPrim{}          -> pure xx
        XCon{}           -> pure xx
        XLam  b x        -> XLam b     <$> pure x
        XLAM  b x        -> XLAM b     <$> pure x
        XApp  x1 x2      -> XApp       <$> desugarX   sp x1  <*> desugarX sp x2
        XLet  lts x      -> XLet       <$> desugarLts sp lts <*> desugarX sp x
        XCast c x        -> XCast c    <$> desugarX   sp x
        XType{}          -> pure xx
        XWitness{}       -> pure xx
        XDefix a xs      -> XDefix a   <$> mapM (desugarX sp)  xs
        XInfixOp{}       -> pure xx
        XInfixVar{}      -> pure xx
        XWhere a x cls   -> XWhere a   <$> desugarX sp x 
                                       <*> mapM (desugarCl sp) cls

        -- Desugar a case expression.
        XCase xScrut alts
         -- Simple alternatives are ones where we can determine whether they
         -- match just based on the head pattern. If all the alternatives
         -- in a case-expression are simple then we can convert directly
         -- to core-level case expressions.
         | all isSimpleAltCase alts
         -> do  xScrut' <- desugarX sp xScrut 
                alts'   <- mapM (desugarAltCase sp) alts
                return  $ XCase xScrut' alts'

         -- Complex alternatives are ones that have include a guard or some
         -- other pattern that may fail, and require us to skip to the next
         -- alternatives. These are compiled as per match expressions.
         | otherwise
         -> do  -- Desugar the scrutinee.
                xScrut' <- desugarX sp xScrut

                -- We bind the scrutinee to a new variable so we can 
                -- defer to it multiple times in the body of the match.
                (b, u)  <- newVar "xScrut"

                -- At the start of each guarded expression we match against
                -- the pattern from the original case alternative.
                gxsAlt' <- mapM (desugarGX sp >=> (return . cleanGX))
                        $  concat [ map (GGuard (GPat p (XVar u))) gxs
                                  | AAltCase p gxs <- alts]

                -- Desugar the body of each alternative.
                alts'   <- mapM (desugarAltMatch sp)
                        $  [AAltMatch gx | gx <- gxsAlt']

                -- Result contains a let-binding to bind the scrutinee,
                -- then a match expression that implements the complex
                -- case alternatives.
                pure    $ XLet (LLet (XBindVarMT b Nothing) xScrut')
                        $ XMatch sp alts'
                        $ makeXErrorDefault
                                (Text.pack    $ SP.sourcePosSource sp)
                                (fromIntegral $ SP.sourcePosLine   sp)

        -- Desugar a match expression from the source code.
        XMatch sp' alts xFail
         -> do  alts'     <- mapM (desugarAltMatch sp') alts
                xFail'    <- desugarX sp' xFail
                pure    $ XMatch sp' alts' xFail'


        -- Desugar lambda with a pattern for the parameter.
        XLamPat _a PDefault mt x
         -> XLam (XBindVarMT BNone mt) <$> desugarX sp x 

        XLamPat _a (PVar b) mt x
         -> XLam (XBindVarMT b mt)     <$> desugarX sp x

        XLamPat _a p mt x
         -> do  (b, u)  <- newVar "x"
                x'      <- desugarX sp x
                return  $  XLam  (XBindVarMT b mt)
                        $  XCase (XVar u) [ AAltCase p [GExp x'] ] 


        -- Desugar lambda case by inserting the intermediate variable.
        XLamCase _a alts
         -> do  (b, u)  <- newVar "x"
                alts'   <- mapM  (desugarAltCase sp) alts
                return  $  XLam  (XBindVarMT b Nothing)
                        $  XCase (XVar u) alts'


-- | Check if this is simple Case alternative, which means if the pattern
--   matches then we can run the expression on the right instead of needing
--   to skip to another alternative.
isSimpleAltCase :: AltCase -> Bool
isSimpleAltCase aa
 = case aa of
        AAltCase p [GExp _]  -> isSimplePat p
        _                    -> False


-- | Simple patterns can be converted directly to core.
isSimplePat :: Pat -> Bool
isSimplePat pp
 = case pp of
        PDefault        -> True
        PAt{}           -> False
        PVar{}          -> True
        PData _  ps     -> all isTrivialPat ps


-- | Trival patterns are the default one and variables,
--   and don't require an actual pattern to be matched.
isTrivialPat :: Pat -> Bool
isTrivialPat pp
 = case pp of
        PDefault        -> True
        PVar{}          -> True
        _               -> False


-------------------------------------------------------------------------------
-- | Desugar some let bindings.
desugarLts :: SP -> Lets -> S Lets
desugarLts sp lts
 = case lts of
        LLet bm x       -> LLet bm  <$> desugarX sp x

        LRec bxs
         -> do  let (bs, xs)    = unzip bxs
                xs'     <- mapM (desugarX sp) xs
                let bxs'        = zip bs xs'
                return  $ LRec bxs'

        LPrivate{}      -> pure lts

        LGroup cs       -> LGroup <$> mapM (desugarCl sp) cs


-------------------------------------------------------------------------------
-- | Desugar a guarded expression.
desugarGX :: SP -> GuardedExp -> S GuardedExp
desugarGX sp gx
 = case gx of
        GGuard (GPat p x) gxInner
         -> do  x'        <- desugarX sp x
                (g', gs') <- stripGuardToGuards (GPat p x')
                gxInner'  <- desugarGX sp gxInner
                return  $ GGuard g'
                        $ wrapGuards gs' gxInner'

        GGuard g gx'
         -> GGuard <$> desugarG sp g <*> desugarGX sp gx'

        GExp x
         -> GExp   <$> desugarX sp x


-- | Desugar a guard.
desugarG :: SP -> Guard -> S Guard
desugarG sp g
 = case g of
        GPat p x        -> GPat p <$> desugarX sp x
        GPred x         -> GPred  <$> desugarX sp x
        GDefault        -> pure GDefault


-------------------------------------------------------------------------------
-- | Desugar a case alternative.
desugarAltCase :: SP -> AltCase -> S AltCase
desugarAltCase sp (AAltCase p gxs)
 = do   gxs' <- mapM (desugarGX sp >=> (return . cleanGX)) gxs
        pure $ AAltCase p gxs'


-- | Desugar a match alternative.
desugarAltMatch :: SP -> AltMatch -> S AltMatch
desugarAltMatch sp (AAltMatch gx)
 = do   gx'  <- (desugarGX sp >=> (return . cleanGX)) gx
        pure $ AAltMatch gx'


-------------------------------------------------------------------------------
-- | Strip out patterns in the given parameter list, 
--   yielding a list of guards that implement the patterns.
stripParamsToGuards :: [Param] -> S ([Param], [Guard])
stripParamsToGuards []
 = return ([], [])

stripParamsToGuards (p:ps)
 = case p of
        MType{} 
         -> do  (ps', gs) <- stripParamsToGuards ps
                return (p : ps', gs)

        MWitness{} 
         -> do  (ps', gs) <- stripParamsToGuards ps
                return (p : ps', gs)

        MValue PDefault  _mt
         -> do  (ps', gs) <- stripParamsToGuards ps
                return (p : ps', gs)

        MValue (PVar _b) _mt
         -> do  (ps', gs) <- stripParamsToGuards ps
                return (p : ps', gs)

        MValue (PAt b p1) _mt
         -> do  (psParam', gsRest) <- stripParamsToGuards ps
                ([p1'],    gsData) <- stripPatsToGuards  [p1]
                let Just u         = takeBoundOfBind b
                return  ( MValue (PVar b) _mt : psParam'
                        , GPat p1' (XVar u) 
                                : (gsData ++ gsRest))

        MValue (PData dc psData) mt
         -> do  (psParam', gsRest) <- stripParamsToGuards ps
                (psData',  gsData) <- stripPatsToGuards   psData
                (b, u)             <- newVar "p"
                return  ( MValue (PVar b) mt : psParam'
                        , GPat (PData dc psData') (XVar u) 
                                : (gsData ++ gsRest))


-- | Strip out nested patterns from the given pattern list,
--   yielding a list of guards that implement the patterns.
stripPatsToGuards :: [Pat] -> S ([Pat], [Guard])
stripPatsToGuards []
 = return ([], [])

stripPatsToGuards (p:ps)
 = case p of
        -- Match against defaults directly.
        PDefault
         -> do  (ps', gs) <- stripPatsToGuards ps
                return (p : ps', gs)

        -- Match against vars directly.
        PVar  _b
         -> do  (ps', gs) <- stripPatsToGuards ps
                return (p : ps', gs)

        -- Strip at patterns.
        PAt b p1
         -> do  -- Strip the rest of the patterns.
                (psRest', gsRest)   <- stripPatsToGuards ps

                -- Strip nested patterns from the argument.
                ([p1'],     gsData) <- stripPatsToGuards [p1]
                let Just u      = takeBoundOfBind b

                return  ( PVar b : psRest'
                        , GPat p1' (XVar u)
                                : (gsData ++ gsRest))

        -- Strip out nested patterns in the arguments of a data constructor.
        PData dc psData
         -> do  -- Strip the rest of the patterns.
                (psRest', gsRest) <- stripPatsToGuards ps

                -- Strip nested patterns out of the arguments.
                (psData', gsData) <- stripPatsToGuards psData

                -- Make a new name to bind the value we are matching against.
                (b, u)            <- newVar "p"
                return  ( PVar b : psRest'
                        , GPat (PData dc psData') (XVar u) 
                                 : (gsData ++ gsRest) )


-- | Like `stripPatsToGuards` but we take the whole enclosing guards.
--   This gives us access to the expression being scrutinised, 
--   which we can match against directly without introducing a new variable.
stripGuardToGuards :: Guard -> S (Guard, [Guard])
stripGuardToGuards g
 = case g of
        -- Match against defaults and vars directly.
        GPat PDefault _ -> return (g, [])
        GPat PVar{} _   -> return (g, [])

        -- As we alerady have the expression being matched we don't 
        -- need to introduce a new variable to name it.
        GPat (PAt b p) x  
         -> do  ([p'], gsData) <- stripPatsToGuards [p]
                let Just u      = takeBoundOfBind b
                return  ( GPat (PVar b) x
                        , GPat p' (XVar u) : gsData)

        GPat (PData dc psData) x 
         -> do  (psData', gsData)  <- stripPatsToGuards psData
                return  ( GPat (PData dc psData') x
                        , gsData)

        GPred{}         -> return (g, [])
        GDefault{}      -> return (g, [])


-- | Wrap more guards around the outside of a guarded expression.
wrapGuards :: [Guard] -> GuardedExp -> GuardedExp
wrapGuards [] gx        = gx
wrapGuards (g : gs) gx  = GGuard g (wrapGuards gs gx)


-- | Clean out default patterns from a guarded expression.
--
--   We end up with default patterns in guards when desugaring default
--   alternatives, but they serve no purpose in the desugared code.
cleanGX :: GuardedExp -> GuardedExp
cleanGX gx
 = case gx of
        GGuard GDefault gx'     -> cleanGX gx'
        GGuard g        gx'     -> GGuard g $ cleanGX gx'
        GExp   x                -> GExp x


-------------------------------------------------------------------------------
-- | Source position.
type SP = SP.SourcePos


-- | State holding a variable name prefix and counter to 
--   create fresh variable names.
type S  = S.State (Text, Int)


-- | Evaluate a desguaring computation,
--   using the given prefix for freshly introduced variables.
evalState :: Text -> S a -> a
evalState n c
 = S.evalState c (n, 0) 


-- | Allocate a new named variable, yielding its associated bind and bound.
newVar :: Text -> S (Bind, Bound)
newVar pre
 = do   (n, i)   <- S.get
        let name = pre <> "$" <> n <> Text.pack (show i)
        S.put (n, i + 1)
        return  (BName name, UName name)