packages feed

free-foil-0.2.0: src/Control/Monad/Free/Foil/TH/Signature.hs

{-# LANGUAGE LambdaCase      #-}
{-# LANGUAGE TemplateHaskell #-}
module Control.Monad.Free.Foil.TH.Signature where

import           Language.Haskell.TH
import           Language.Haskell.TH.Syntax

import           Control.Monad              (forM_)
import           Control.Monad.Foil.TH.Util
import           Data.Maybe                 (catMaybes)

-- | Generate a signature for the free foil (or free scoped monads)
-- based on a naïve recursive abstract syntax representation,
-- with clearly separated types for terms, variable identifiers, scoped terms,
-- and patterns (binders).
mkSignature
  :: 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]
mkSignature termT nameT scopeT patternT = do
  scope <- newName "scope"
  term <- newName "term"
  TyConI (DataD _ctx _name termTVars _kind termCons _deriv) <- reify termT

  signatureCons <- catMaybes <$> mapM (toSignatureCons scope term) termCons

  addModFinalizer $ putDoc (DeclDoc signatureT)
    ("/Generated/ with '" ++ show 'mkSignature ++ "'. A signature bifunctor, specifying the nodes of a syntax tree corresponding to '" ++ show termT ++ "'.")
  return
    [ DataD [] signatureT (termTVars ++ [PlainTV scope BndrReq, PlainTV term BndrReq]) Nothing signatureCons
      [DerivClause Nothing [ConT ''Functor, ConT ''Foldable, ConT ''Traversable]]
    ]
  where
    signatureT = mkName (nameBase termT ++ "Sig")

    toSignatureCons :: Name -> Name -> Con -> Q (Maybe Con)
    toSignatureCons scope term con' = case con' of
      -- treat constructors with a single variable field as variable constructor and ignore
      NormalC _conName types | or [ typeName == nameT | (_bang, PeelConT typeName _typeParams) <- types ]
        -> pure Nothing
      RecC _conName types | or [ typeName == nameT | (_name, _bang, PeelConT typeName _typeParams) <- types ]
        -> pure Nothing

      NormalC conName params -> do
        addModFinalizer $ putDoc (DeclDoc conName') ("Corresponds to '" ++ show conName ++ "'.")
        Just . NormalC conName' . catMaybes <$> mapM toSignatureParam params
        where
          conName' = mkName (nameBase conName ++ "Sig")
      RecC conName params -> do
        addModFinalizer $ putDoc (DeclDoc conName') ("Corresponds to '" ++ show conName ++ "'.")
        Just . RecC conName' . catMaybes <$> mapM toSignatureParam' params
        where
          conName' = mkName (nameBase conName ++ "Sig")
      InfixC l conName r -> do
        addModFinalizer $ putDoc (DeclDoc conName') ("Corresponds to '" ++ show conName ++ "'.")
        Just <$> (flip InfixC conName' <$> toInfixParam l <*> toInfixParam r)
        where
          conName' = mkName (nameBase conName ++ "---")
      ForallC params ctx con -> fmap (ForallC params ctx) <$> toSignatureCons scope term con
      GadtC conNames argTypes retType -> do
        let conNames' = map (\conName -> mkName (nameBase conName ++ "---")) conNames
        forM_ (zip conNames conNames') $ \(conName, conName') ->
          addModFinalizer $ putDoc (DeclDoc conName') ("Corresponds to '" ++ show conName ++ "'.")
        Just <$> (GadtC conNames' <$> (catMaybes <$> mapM toSignatureParam argTypes) <*> retType')
        where
          retType' = case retType of
            PeelConT typeName typeParams
              | typeName == termT -> return (PeelConT signatureT (typeParams ++ [VarT scope, VarT term]))
            _ -> fail "unexpected return type in a GADT constructor"
      RecGadtC conNames argTypes retType -> do
        let conNames' = map (\conName -> mkName (nameBase conName ++ "---")) conNames
        forM_ (zip conNames conNames') $ \(conName, conName') ->
          addModFinalizer $ putDoc (DeclDoc conName') ("Corresponds to '" ++ show conName ++ "'.")
        Just <$> (RecGadtC conNames' <$> (catMaybes <$> mapM toSignatureParam' argTypes) <*> retType')
        where
          retType' = case retType of
            PeelConT typeName typeParams
              | typeName == termT -> return (PeelConT signatureT (typeParams ++ [VarT scope, VarT term]))
            _ -> fail "unexpected return type in a GADT constructor"

      where
        toInfixParam (bang_, type_) = toSignatureParam (bang_, type_) >>= \case
          Nothing -> pure (bang_, VarT ''())
          Just bt -> pure bt

        toSignatureParam' (name, bang_, type_) = fmap k <$> toSignatureParam (bang_, type_)
          where
            k (x, y) = (name, x, y)

        toSignatureParam (_bang, PeelConT typeName _typeParams)
          | typeName == nameT = fail ("variable with other stuff in constructor: " ++ show con')
          | typeName == patternT = pure Nothing -- skip binders, they will be inserted automatically with each scoped term
        toSignatureParam (bang_, type_) = pure (Just (bang_, replaceScopeTermInType type_))

        replaceScopeTermInType = \case
          PeelConT typeName _typeParams
            | typeName == scopeT -> VarT scope
            | typeName == termT -> VarT term
          ForallT bndrs ctx type_ -> ForallT bndrs ctx (replaceScopeTermInType type_)
          ForallVisT bndrs type_ -> ForallVisT bndrs (replaceScopeTermInType type_)
          AppT f x -> AppT (replaceScopeTermInType f) (replaceScopeTermInType x)
          AppKindT f k -> AppKindT (replaceScopeTermInType f) k
          SigT t k -> SigT (replaceScopeTermInType t) k
          t@ConT{} -> t
          t@VarT{} -> t
          t@PromotedT{} -> t
          InfixT l op r -> InfixT (replaceScopeTermInType l) op (replaceScopeTermInType r)
          UInfixT l op r -> UInfixT (replaceScopeTermInType l) op (replaceScopeTermInType r)
          PromotedInfixT l op r -> PromotedInfixT (replaceScopeTermInType l) op (replaceScopeTermInType r)
          PromotedUInfixT l op r -> PromotedUInfixT (replaceScopeTermInType l) op (replaceScopeTermInType r)
          ParensT t -> ParensT (replaceScopeTermInType t)
          t@TupleT{} -> t
          t@UnboxedTupleT{} -> t
          t@UnboxedSumT{} -> t
          t@ArrowT{} -> t
          t@MulArrowT{} -> t
          t@EqualityT{} -> t
          t@ListT{} -> t
          t@PromotedTupleT{} -> t
          t@PromotedNilT{} -> t
          t@PromotedConsT{} -> t
          t@StarT{} -> t
          t@ConstraintT{} -> t
          t@LitT{} -> t
          t@WildCardT{} -> t
          ImplicitParamT s t -> ImplicitParamT s (replaceScopeTermInType t)