packages feed

telegram-bot-api-7.4.3: src/Telegram/Bot/API/Internal/TH.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase     #-}
{-# LANGUAGE CPP            #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Telegram.Bot.API.Internal.TH (makeDefault) where

import Language.Haskell.TH
import Control.Monad.State
import Data.Maybe (catMaybes)
import Control.Applicative(liftA2)

makeDefault :: Name -> Q [Dec]
makeDefault typeN = do
  info <- reify typeN
  case info of
    TyConI dec -> case dec of
      -- no sence to handle other declarations
      DataD _ _ _ _ [con] _  | Just x <- getConInfo con -> makeDefaultFromCon x
      NewtypeD _ _ _ _ con _ | Just x <- getConInfo con -> makeDefaultFromCon x
      _ -> error "declaration not supported"
    _ -> error "not a type constructor name"
  where
    defName = constructDefName typeN
    defNameP = varP defName

    makeDefaultFromCon (conN, tys) = let
      type' = constructType typeN tys
      expr = construcExpr conN tys
      -- ghc disallows quote of form [d| $name :: some type |]
      sig = pure <$> sigD defName type'

      in sig <> [d|
        $defNameP = $expr
      |]

constructDefName :: Name -> Name
constructDefName typeN = mkName ("def" <> trimReq typeStr)
  where
    typeStr = nameBase typeN

    trimReq "Request" = []
    trimReq (x:xs) = x : trimReq xs
    trimReq [] = []


construcExpr :: Name -> [Type] -> Q Exp
construcExpr conN tys = let
  mVars = flip evalState 0 $ traverse
    (\ty -> if isMaybeTy ty then pure Nothing else Just <$> newNameI)
    tys

  vars = catMaybes mVars

  argExps = map (\case
    Nothing -> conE 'Nothing
    Just na -> varE na) mVars

  in lamE (map varP vars) (foldl appE (conE conN) argExps)

constructType :: Name -> [Type] -> Q Type
constructType typeN tys = foldr arrAp baseTy (filter (not . isMaybeTy) tys)
  where
    baseTy = conT typeN
    arrAp a = appT (appT arrowT (pure a))


-- Predicates over TH

getConInfo :: Con -> Maybe (Name, [Type])
getConInfo (NormalC name tys) = Just (name, map snd tys)
getConInfo (RecC name tys) = Just (name, map (\(_,_,x) -> x) tys)
getConInfo _= Nothing

isMaybeTy :: Type -> Bool
isMaybeTy (AppT (ConT m) _) = m == ''Maybe
isMaybeTy _ = False

-- State heplers

newInd :: State Int Int
newInd = do
  x <- get
  modify (+1)
  pure x

newNameI :: State Int Name
newNameI = do
  i <- newInd
  pure $ mkName ("a" <> show i)

-- Instance Monoid for TH of ghc < 8.6
#if !MIN_VERSION_template_haskell(2,17,0)

instance Semigroup a => Semigroup (Q a) where
  (<>) = liftA2 (<>)

instance Monoid a => Monoid (Q a) where
  mempty = pure mempty

#endif