packages feed

free-foil-0.0.2: src/Control/Monad/Foil/TH/MkToFoil.hs

{-# OPTIONS_GHC -fno-warn-type-defaults #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE QuasiQuotes           #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
module Control.Monad.Foil.TH.MkToFoil (mkToFoil) where

import           Language.Haskell.TH
import Language.Haskell.TH.Syntax (addModFinalizer)

import qualified Control.Monad.Foil  as Foil
import           Data.Maybe          (catMaybes)
import Data.Map (Map)
import qualified Data.Map as Map
import Control.Monad.Foil.TH.Util

-- | Generate conversion functions from raw to scope-safe representation.
mkToFoil
  :: Name -- ^ Type name for raw terms.
  -> Name -- ^ Type name for raw variable identifiers.
  -> Name -- ^ Type name for raw scoped terms.
  -> Name -- ^ Type name for raw patterns.
  -> Q [Dec]
mkToFoil termT nameT scopeT patternT = do
  extendScopeFoilPattenD <- mkExtendScopeFoilPattern nameT patternT
  withRefreshedFoilPatternD <- mkWithRefreshedFoilPattern nameT patternT
  toFoilTermD <- mkToFoilTerm termT nameT scopeT patternT
  return (
    extendScopeFoilPattenD ++
    withRefreshedFoilPatternD ++
    toFoilTermD
    )

-- | Generate a function to extend scope with variables from a given pattern.
mkExtendScopeFoilPattern
  :: Name -- ^ Type name for raw variable identifiers.
  -> Name -- ^ Type name for raw patterns.
  -> Q [Dec]
mkExtendScopeFoilPattern nameT patternT = do
  n <- newName "n"
  l <- newName "l"
  TyConI (DataD _ctx _name patternTVars _kind patternCons _deriv) <- reify patternT

  let extendScopePatternSignature =
        SigD extendScopePatternFunName $
          ForallT ([ PlainTV name SpecifiedSpec | name <- (map tvarName patternTVars ++ [n, l]) ]) []
            (AppT (AppT ArrowT
              (PeelConT foilPatternT [ VarT name | name <- (map tvarName patternTVars ++ [n, l]) ]))
              (AppT (AppT ArrowT
                (AppT (ConT ''Foil.Scope) (VarT n)))
                (AppT (ConT ''Foil.Scope) (VarT l))))

  composefun <- [e| (.) |]
  idfun <- [e| id |]
  extendScopeFun <- [e| Foil.extendScope |]

  addModFinalizer $ putDoc (DeclDoc extendScopePatternFunName)
    "Extend a scope with the names bound by the given pattern.\nThis is a more flexible version of 'Control.Monad.Foil.extendScope'."

  return
    [ extendScopePatternSignature
    , extendScopePatternBody extendScopeFun composefun idfun patternCons
    ]
  where
    foilPatternT = mkName ("Foil" ++ nameBase patternT)

    extendScopePatternFunName = mkName ("extendScopeFoil" ++ nameBase patternT)
    extendScopePatternBody extendScopeFun composefun idfun patternCons = FunD extendScopePatternFunName
      [Clause [VarP p] (NormalB (CaseE (VarE p) (map toMatch patternCons))) []]
      where
        p = mkName "pattern"
        toMatch (NormalC conName conParams) =
          Match (ConP foilConName [] conParamPatterns) (NormalB conMatchBody) []
          where
            foilConName = mkName ("Foil" ++ nameBase conName)
            conParamPatterns = map toConParamPattern conParamVars
            conMatchExts = map snd (catMaybes conParamVars)
            conMatchBody = foldr (\f g -> InfixE (Just g) composefun (Just f)) idfun conMatchExts

            toConParamPattern Nothing = WildP
            toConParamPattern (Just (x, _f)) = VarP x

            conParamVars = zipWith mkConParamVar conParams [1..]

            mkConParamVar :: BangType -> Int -> Maybe (Name, Exp)
            mkConParamVar (_bang, PeelConT tyName _tyParams) i
              | tyName == nameT    = Just (x, AppE extendScopeFun (VarE x))
              | tyName == patternT = Just (x, AppE (VarE extendScopePatternFunName) (VarE x))
              where
                x = mkName ("x" <> show i)
            mkConParamVar (_bang, _type) _i = Nothing
        toMatch RecC{} = error "Record constructors (RecC) are not supported yet!"
        toMatch InfixC{} = error "Infix constructors (InfixC) are not supported yet!"
        toMatch ForallC{} = error "Existential constructors (ForallC) are not supported yet!"
        toMatch GadtC{} = error "GADT constructors (GadtC) are not supported yet!"
        toMatch RecGadtC{} = error "Record GADT constructors (RecGadtC) are not supported yet!"

-- | Generate a function to extend scope with variables from a given pattern.
mkWithRefreshedFoilPattern
  :: Name -- ^ Type name for raw variable identifiers.
  -> Name -- ^ Type name for raw patterns.
  -> Q [Dec]
mkWithRefreshedFoilPattern nameT patternT = do
  o <- newName "o"
  o' <- newName "o'"
  e <- newName "e"
  n <- newName "n"
  l <- newName "l"
  r <- newName "r"
  TyConI (DataD _ctx _name patternTVars _kind patternCons _deriv) <- reify patternT

  let withRefreshedFoilPatternSignature =
        SigD withRefreshedFoilPatternFunName $
          ForallT
            [ PlainTV name SpecifiedSpec | name <- map tvarName patternTVars ++ [o, e, n, l, r] ]
            [ AppT (ConT ''Foil.Distinct) (VarT o)
            , AppT (ConT ''Foil.InjectName) (VarT e)
            , AppT (ConT ''Foil.Sinkable) (VarT e)
            ]
            (AppT (AppT ArrowT (PeelConT ''Foil.Scope [VarT o]))
              (AppT (AppT ArrowT (PeelConT foilPatternT (map (VarT . tvarName) patternTVars ++ [VarT n, VarT l])))
                (AppT (AppT ArrowT
                  (ForallT [PlainTV o' SpecifiedSpec] [PeelConT ''Foil.DExt [VarT o, VarT o']]
                    (AppT (AppT ArrowT (AppT (AppT ArrowT (PeelConT ''Foil.Substitution [VarT e, VarT n, VarT o])) (PeelConT ''Foil.Substitution [VarT e, VarT l, VarT o'])))
                      (AppT (AppT ArrowT (PeelConT foilPatternT (map (VarT . tvarName) patternTVars ++ [VarT o, VarT o'])))
                        (VarT r)))))
                  (VarT r))))

        --           [t| ( Foil.Distinct o, Foil.InjectName e, Foil.Sinkable e )
        --     => Foil.Scope o
        --     -> $(return (PeelConT foilPatternT (map (VarT . tyName patternTVars)))) n l
        --     -> (forall o'. Foil.DExt o o'
        --           => (Foil.Substitution e n o -> Foil.Substitution e l o')
        --           -> $(return (ConT foilPatternT)) o o'
        --           -> r)
        --     -> r
        -- |]

  composefun <- [e| (.) |]
  addRenameFun <- [e| Foil.addRename |]
  nameOfFun <- [e| Foil.nameOf |]
  sinkFun <- [e| Foil.sink |]
  withRefreshedFun <- [e| Foil.withRefreshed |]
  extendScopeFun <- [e| Foil.extendScope |]

  addModFinalizer $ putDoc (DeclDoc withRefreshedFoilPatternFunName)
    "Refresh (if needed) bound variables introduced in a pattern.\nThis is a more flexible version of 'Control.Monad.Foil.withRefreshed'."

  return
    [ withRefreshedFoilPatternSignature
    , withRefreshedFoilPatternBody composefun addRenameFun nameOfFun sinkFun withRefreshedFun extendScopeFun patternCons
    ]
  where
    foilPatternT = mkName ("Foil" ++ nameBase patternT)

    extendScopePatternFunName = mkName ("extendScopeFoil" ++ nameBase patternT)
    extendScopePatternFun = VarE extendScopePatternFunName

    withRefreshedFoilPatternFunName = mkName ("withRefreshedFoil" ++ nameBase patternT)
    withRefreshedFoilPatternFun = VarE withRefreshedFoilPatternFunName

    withRefreshedFoilPatternBody composefun addRenameFun nameOfFun sinkFun withRefreshedFun extendScopeFun patternCons = FunD withRefreshedFoilPatternFunName
      [Clause [VarP scope, VarP pattern, VarP cont] (NormalB (CaseE (VarE pattern) (map toMatch patternCons))) []]
      where
        scope = mkName "scope"
        pattern = mkName "pattern"
        cont = mkName "cont"

        toMatch (NormalC conName params) =
          Match (ConP foilConName [] conParamPatterns) (NormalB conMatchBody) []
          where
            conMatchBody = go 1 (VarE scope) sinkFun (ConE foilConName) params

            go _i _scope' f p [] = AppE (AppE (VarE cont) f) p
            go i scope' f p ((_bang, PeelConT tyName _tyParams) : conParams)
              | tyName == nameT =
                  AppE
                    (AppE (AppE withRefreshedFun scope') (AppE nameOfFun (VarE xi)))
                    (LamE [VarP xi']
                      (LetE [ValD (VarP scopei) (NormalB (AppE (AppE extendScopeFun (VarE xi')) scope')) []]
                        (go (i+1) (VarE scopei) (InfixE (Just fi) composefun (Just f)) (AppE p (VarE xi')) conParams)))
              | tyName == patternT =
                  AppE
                    (AppE (AppE withRefreshedFoilPatternFun scope') (VarE xi))
                    (LamE [VarP xsubst, VarP xi']
                      (LetE [ValD (VarP scopei) (NormalB (AppE (AppE extendScopePatternFun (VarE xi')) scope')) []]
                        (go (i+1) (VarE scopei) (InfixE (Just (VarE xsubst)) composefun (Just f)) (AppE p (VarE xi')) conParams)))
              where
                xi = mkName ("x" <> show i)
                xi' = mkName ("x" <> show i <> "'")
                scopei = mkName ("scope" <> show i)
                xsubst = mkName ("subst" <> show i)
                subst = mkName "subst"
                fi = LamE [VarP subst]
                      (AppE (AppE (AppE addRenameFun
                        (VarE subst))
                        (VarE xi))
                        (AppE nameOfFun (VarE xi')))
            go i scope' f p (_ : conParams) =
              go (i + 1) scope' f (AppE p (VarE xi)) conParams
              where
                xi = mkName ("x" <> show i)

            foilConName = mkName ("Foil" ++ nameBase conName)
            conParamPatterns = map VarP conParamVars

            conParamVars = zipWith mkConParamVar params [1..]

            mkConParamVar :: BangType -> Int -> Name
            mkConParamVar _ i = mkName ("x" <> show i)

        toMatch RecC{} = error "Record constructors (RecC) are not supported yet!"
        toMatch InfixC{} = error "Infix constructors (InfixC) are not supported yet!"
        toMatch ForallC{} = error "Existential constructors (ForallC) are not supported yet!"
        toMatch GadtC{} = error "GADT constructors (GadtC) are not supported yet!"
        toMatch RecGadtC{} = error "Record GADT constructors (RecGadtC) are not supported yet!"

-- | Generate a conversion function from raw terms to scope-safe terms.
mkToFoilTerm
  :: Name -- ^ Type name for raw terms.
  -> Name -- ^ Type name for raw variable identifiers.
  -> Name -- ^ Type name for raw scoped terms.
  -> Name -- ^ Type name for raw patterns.
  -> Q [Dec]
mkToFoilTerm termT nameT scopeT patternT = do
  n <- newName "n"
  let ntype = return (VarT n)
  r <- newName "r"
  let rtype = return (VarT r)
  TyConI (DataD _ctx _name patternTVars _kind patternCons _deriv) <- reify patternT
  TyConI (DataD _ctx _name scopeTVars _kind scopeCons _deriv) <- reify scopeT
  TyConI (DataD _ctx _name termTVars _kind termCons _deriv) <- reify termT

  toFoilTermSignature <-
    SigD toFoilTermT <$>
      [t| Foil.Distinct $ntype
          => Foil.Scope $ntype
          -> Map $(return (ConT nameT)) (Foil.Name $ntype)
          -> $(return (PeelConT termT (map (VarT . tvarName) termTVars )))
          -> $(return (PeelConT foilTermT (map (VarT . tvarName) termTVars))) $ntype
        |]
  toFoilScopedSignature <-
    SigD toFoilScopedT <$>
      [t| Foil.Distinct $ntype
          => Foil.Scope $ntype
          -> Map $(return (ConT nameT)) (Foil.Name $ntype)
          -> $(return (PeelConT scopeT (map (VarT . tvarName) scopeTVars)))
          -> $(return (PeelConT foilScopeT (map (VarT . tvarName) scopeTVars))) $ntype
        |]
  toFoilPatternSignature <-
    SigD toFoilPatternT <$>
      [t| Foil.Distinct $ntype
          => Foil.Scope $ntype
          -> Map $(return (ConT nameT)) (Foil.Name $ntype)
          -> $(return (PeelConT patternT (map (VarT . tvarName) patternTVars)))
          -> (forall l. Foil.DExt $ntype l => $(return (PeelConT foilPatternT (map (VarT . tvarName) patternTVars))) $ntype l -> Map $(return (ConT nameT)) (Foil.Name l) -> $rtype)
          -> $rtype
        |]

  addModFinalizer $ putDoc (DeclDoc toFoilTermT)
    "Convert a raw term into a scope-safe term."
  addModFinalizer $ putDoc (DeclDoc toFoilPatternT)
    "Convert a raw pattern into a scope-safe pattern."
  addModFinalizer $ putDoc (DeclDoc toFoilScopedT)
    "Convert a raw scoped term into a scope-safe scoped term."

  return
    [ toFoilTermSignature
    , toFoilTermBody termCons
    , toFoilPatternSignature
    , toFoilPatternBody patternCons
    , toFoilScopedSignature
    , toFoilScopedBody scopeCons
    ]
  where
    foilTermT = mkName ("Foil" ++ nameBase termT)
    foilScopeT = mkName ("Foil" ++ nameBase scopeT)
    foilPatternT = mkName ("Foil" ++ nameBase patternT)

    toFoilTermT = mkName ("toFoil" ++ nameBase termT)
    toFoilPatternT = mkName ("toFoil" ++ nameBase patternT)
    toFoilScopedT = mkName ("toFoil" ++ nameBase scopeT)

    extendScopePatternFunName = mkName ("extendScopeFoil" ++ nameBase patternT)
    extendScopePatternFun = VarE extendScopePatternFunName

    toFoilTermBody termCons = FunD toFoilTermT
      [Clause [VarP scope, VarP env, VarP term] (NormalB (CaseE (VarE term) (map toMatch termCons))) []]
      where
        scope = mkName "scope"
        env = mkName "env"
        term = mkName "term"

        toMatch (NormalC conName params) =
          Match (ConP conName [] conParamPatterns) (NormalB conMatchBody) [toFoilVarD]
          where
            toFoilVarFunName = mkName "lookupRawVar"
            toFoilVarFun = VarE toFoilVarFunName
            x = mkName "x"
            name = mkName "name"
            toFoilVarD = FunD toFoilVarFunName [Clause [VarP x]
              (NormalB (CaseE (AppE (AppE (VarE 'Map.lookup) (VarE x)) (VarE env))
                [ Match (ConP 'Just [] [VarP name]) (NormalB (VarE name)) []
                , Match (ConP 'Nothing [] []) (NormalB (AppE (VarE 'error) (LitE (StringL "undefined variable")))) []]))
              []]

            conMatchBody = go 1 (VarE scope) (VarE env) (ConE foilConName) params

            go _i _scope' _env' p [] = p
            go i scope' env' p ((_bang, PeelConT tyName _tyParams) : conParams)
              | tyName == nameT =
                  go (i+1) scope' env' (AppE p (AppE toFoilVarFun (VarE xi))) conParams
              | tyName == termT =
                  go (i+1) scope' env' (AppE p (AppE (AppE (AppE (VarE toFoilTermT) (VarE scope)) (VarE env)) (VarE xi))) conParams
              | tyName == scopeT =
                  go (i+1) scope' env' (AppE p (AppE (AppE (AppE (VarE toFoilScopedT) scope') env') (VarE xi))) conParams
              | tyName == patternT =
                  AppE
                    (AppE (AppE (AppE (VarE toFoilPatternT) scope') env') (VarE xi))
                    (LamE [VarP xi', VarP envi]
                      (LetE [ValD (VarP scopei) (NormalB (AppE (AppE extendScopePatternFun (VarE xi')) scope')) []]
                        (go (i+1) (VarE scopei) (VarE envi) (AppE p (VarE xi')) conParams)))
              where
                xi = mkName ("x" <> show i)
                xi' = mkName ("x" <> show i <> "'")
                scopei = mkName ("scope" <> show i)
                envi = mkName ("env" <> show i)
            go i scope' env' p (_ : conParams) =
              go (i + 1) scope' env' (AppE p (VarE xi)) conParams
              where
                xi = mkName ("x" <> show i)

            foilConName = mkName ("Foil" ++ nameBase conName)
            conParamPatterns = map VarP conParamVars

            conParamVars = zipWith mkConParamVar params [1..]

            mkConParamVar :: BangType -> Int -> Name
            mkConParamVar _ty i = mkName ("x" <> show i)
        toMatch RecC{} = error "Record constructors (RecC) are not supported yet!"
        toMatch InfixC{} = error "Infix constructors (InfixC) are not supported yet!"
        toMatch ForallC{} = error "Existential constructors (ForallC) are not supported yet!"
        toMatch GadtC{} = error "GADT constructors (GadtC) are not supported yet!"
        toMatch RecGadtC{} = error "Record GADT constructors (RecGadtC) are not supported yet!"

    toFoilPatternBody patternCons = FunD toFoilPatternT
      [Clause [VarP scope, VarP env, VarP pattern, VarP cont] (NormalB (CaseE (VarE pattern) (map toMatch patternCons))) []]
      where
        scope = mkName "scope"
        env = mkName "env"
        pattern = mkName "pattern"
        cont = mkName "cont"

        toMatch (NormalC conName params) =
          Match (ConP conName [] conParamPatterns) (NormalB conMatchBody) []
          where
            conMatchBody = go 1 (VarE scope) (VarE env) (ConE foilConName) params

            go _i _scope' env' p [] = AppE (AppE (VarE cont) p) env'
            go i scope' env' p ((_bang, PeelConT tyName _tyParams) : conParams)
              | tyName == nameT =
                  AppE (AppE (VarE 'Foil.withFresh) scope')
                    (LamE [VarP xi']
                      (LetE [ ValD (VarP scopei) (NormalB (AppE (AppE (VarE 'Foil.extendScope) (VarE xi')) scope')) []
                            , ValD (VarP envi) (NormalB
                                (AppE (AppE (AppE (VarE 'Map.insert) (VarE xi))
                                  (AppE (VarE 'Foil.nameOf) (VarE xi')))
                                  (InfixE (Just (VarE 'Foil.sink)) (VarE '(<$>)) (Just (VarE envi))))) []]
                        (go (i+1) (VarE scopei) (VarE envi) (AppE p (VarE xi')) conParams)))
              | tyName == patternT =
                  AppE
                    (AppE (AppE (AppE (VarE toFoilPatternT) scope') env') (VarE xi))
                    (LamE [VarP xi', VarP envi]
                      (LetE [ValD (VarP scopei) (NormalB (AppE (AppE extendScopePatternFun (VarE xi')) scope')) []]
                        (go (i+1) (VarE scopei) (VarE envi) (AppE p (VarE xi')) conParams)))
              where
                xi = mkName ("x" <> show i)
                xi' = mkName ("x" <> show i <> "'")
                scopei = mkName ("scope" <> show i)
                envi = mkName ("env" <> show i)
            go i scope' env' p (_ : conParams) =
              go (i + 1) scope' env' (AppE p (VarE xi)) conParams
              where
                xi = mkName ("x" <> show i)

            foilConName = mkName ("Foil" ++ nameBase conName)
            conParamPatterns = map VarP conParamVars

            conParamVars = zipWith mkConParamVar params [1..]

            mkConParamVar :: BangType -> Int -> Name
            mkConParamVar _ i = mkName ("x" <> show i)
        toMatch RecC{} = error "Record constructors (RecC) are not supported yet!"
        toMatch InfixC{} = error "Infix constructors (InfixC) are not supported yet!"
        toMatch ForallC{} = error "Existential constructors (ForallC) are not supported yet!"
        toMatch GadtC{} = error "GADT constructors (GadtC) are not supported yet!"
        toMatch RecGadtC{} = error "Record GADT constructors (RecGadtC) are not supported yet!"

    toFoilScopedBody scopeCons = FunD toFoilScopedT
      [Clause [VarP scope, VarP env, VarP term] (NormalB (CaseE (VarE term) (map toMatch scopeCons))) []]
      where
        scope = mkName "scope"
        env = mkName "env"
        term = mkName "term"

        toMatch (NormalC conName params) =
          Match (ConP conName [] conParamPatterns) (NormalB conMatchBody) [toFoilVarD]
          where
            toFoilVarFunName = mkName "lookupRawVar"
            toFoilVarFun = VarE toFoilVarFunName
            x = mkName "x"
            name = mkName "name"
            toFoilVarD = FunD toFoilVarFunName [Clause [VarP x]
              (NormalB (CaseE (AppE (AppE (VarE 'Map.lookup) (VarE x)) (VarE env))
                [ Match (ConP 'Just [] [VarP name]) (NormalB (VarE name)) []
                , Match (ConP 'Nothing [] []) (NormalB (AppE (VarE 'error) (LitE (StringL "undefined variable")))) []]))
              []]

            conMatchBody = go 1 (VarE scope) (VarE env) (ConE foilConName) params

            go _i _scope' _env' p [] = p
            go i scope' env' p ((_bang, PeelConT tyName _tyParams) : conParams)
              | tyName == nameT =
                  go (i+1) scope' env' (AppE p (AppE toFoilVarFun (VarE xi))) conParams
              | tyName == termT =
                  go (i+1) scope' env' (AppE p (AppE (AppE (AppE (VarE toFoilTermT) (VarE scope)) (VarE env)) (VarE xi))) conParams
              | tyName == scopeT =
                  go (i+1) scope' env' (AppE p (AppE (AppE (AppE (VarE toFoilScopedT) scope') env') (VarE xi))) conParams
              | tyName == patternT =
                  AppE
                    (AppE (AppE (AppE (VarE toFoilPatternT) scope') env') (VarE xi))
                    (LamE [VarP xi', VarP envi]
                      (LetE [ValD (VarP scopei) (NormalB (AppE (AppE extendScopePatternFun (VarE xi')) scope')) []]
                        (go (i+1) (VarE scopei) (VarE envi) (AppE p (VarE xi')) conParams)))
              where
                xi = mkName ("x" <> show i)
                xi' = mkName ("x" <> show i <> "'")
                scopei = mkName ("scope" <> show i)
                envi = mkName ("env" <> show i)
            go i scope' env' p (_ : conParams) =
              go (i + 1) scope' env' (AppE p (VarE xi)) conParams
              where
                xi = mkName ("x" <> show i)

            foilConName = mkName ("Foil" ++ nameBase conName)
            conParamPatterns = map VarP conParamVars

            conParamVars = zipWith mkConParamVar params [1..]

            mkConParamVar :: BangType -> Int -> Name
            mkConParamVar _ty i = mkName ("x" <> show i)

        toMatch RecC{} = error "Record constructors (RecC) are not supported yet!"
        toMatch InfixC{} = error "Infix constructors (InfixC) are not supported yet!"
        toMatch ForallC{} = error "Existential constructors (ForallC) are not supported yet!"
        toMatch GadtC{} = error "GADT constructors (GadtC) are not supported yet!"
        toMatch RecGadtC{} = error "Record GADT constructors (RecGadtC) are not supported yet!"