packages feed

tinytools-vty-0.1.0.0: src/Reflex/Vty/Test/Monad/Host/TH.hs

{-# LANGUAGE TemplateHaskell          #-}


module Reflex.Vty.Test.Monad.Host.TH where

import Prelude (foldl)
import           Relude                   hiding (getFirst, Type)

import Data.Char (toLower)
import Language.Haskell.TH

import           Reflex
import           Reflex.Vty.Test.Monad.Host
import           Reflex.Host.Class (EventTrigger, newEventWithTriggerRef)
import           Control.Monad.Ref

-- | reference the 't' variable your quasi-quotes. e.g. 'Event $(tv) ()'
tv :: Q Type
tv = varT $ mkName "t"
-- does this capture types in generated scope? Probably not???
--tv = do
--  Just r <- lookupTypeName "t"
--  varT r

-- | reference a specific input event
tinput :: String -> String -> Q Exp
tinput name suffix = return $ AppE (VarE $ convertNameToPrefixedNameField (mkName name) ("InputEvents_"<>suffix)) (VarE $ getAppInputEventsArgName)

-- | reference the output constructor
toutputcon :: String -> Q Exp
toutputcon name = conE $ mkName (name <> "_Output")

-- | call me to generate code
declareStuff :: String -> [(String, Q Type)] -> [(String, Q Type)] -> Q Exp -> Q [Dec]
declareStuff name' inputEventTypes outputTypes body = do
  let
    name = mkName name'
  nd <- declareNetworkData name
  ni <- declareNetworkInstance name inputEventTypes outputTypes body
  return (nd<>ni)




getAppInputEventsArgName :: Name
getAppInputEventsArgName = mkName "inputEvs_____donotusethisvariablenameforanythingelse"

declareNetworkData :: Name -> Q [Dec]
declareNetworkData name = do
  let
    k_m = InfixT StarT ''(->) StarT
  tv_t <- newName "t"
  tv_m <- newName "m"
  -- not sure if KindedTV is necessary
  return $ [DataD [] name [PlainTV tv_t (), KindedTV tv_m () k_m] Nothing [] []]

declareNetworkInstance :: Name -> [(String, Q Type)] -> [(String, Q Type)] -> Q Exp -> Q [Dec]
declareNetworkInstance name inputEventTypes outputTypes body = do
  let
    v_t = VarT $ mkName "t"
    v_m = VarT $ mkName "m"
    cxt1 = AppT (AppT (ConT $ mkName "MonadVtyApp") v_t) $ AppT (AppT (ConT $ mkName "TestGuestT") v_t) v_m
    cxt2 = AppT (AppT (ConT $ mkName "TestGuestConstraints") v_t) $ v_m
    v_potatoNetwork = AppT (AppT (ConT $ name) v_t) v_m
    --instance (MonadVtyApp t (TestGuestT t m), TestGuestConstraints t m) => ReflexVtyTestApp ([|$(VarT name)|] t m) t m where
    classinstance = AppT (AppT (AppT (ConT $ mkName "ReflexVtyTestApp") v_potatoNetwork) v_t) v_m
  -- same but using quasiquoters
  --cxt1 <- [t| $(conT $ mkName "MonadVtyApp") $(varT $ mkName "t") ($(conT $ mkName "TestGuestT") $(varT $ mkName "t") $(varT $ mkName "m")) |]

  outputs <- declareOutputs name outputTypes
  inputs <- declareInputs name inputEventTypes
  makeInputsFn <- declareMakeInputs name inputEventTypes
  bodyFn <- declareGetApp name body

  return $ [InstanceD Nothing [cxt1,cxt2] classinstance ((outputs:inputs) <> [makeInputsFn, bodyFn])]

normalBang :: Bang
normalBang = Bang NoSourceUnpackedness NoSourceStrictness


convertNameToPrefixedNameField :: Name -> String -> Name
convertNameToPrefixedNameField name suffix = r where
  prefix = case nameBase name of
    [] -> ""
    x:xs -> (toLower x):xs
  r = mkName $ "_" <> prefix <> "_" <> suffix

convertNameToPrefixedNameType :: Name -> String -> Name
convertNameToPrefixedNameType name suffix = r where
  r = mkName $ nameBase name <> "_" <> suffix

varNetwork :: Name -> Type
varNetwork name = r where
  v_t = VarT $ mkName "t"
  v_m = VarT $ mkName "m"
  r = AppT (AppT (ConT $ name) v_t) v_m

declareOutputs :: Name -> [(String, Q Type)] -> Q Dec
declareOutputs name outputTypes = do
  recs <- forM (fmap (\(x,q) -> fmap (x,) q) outputTypes) $ \nt -> do
    (n,t) <- nt
    let
      fname = convertNameToPrefixedNameField name ("Output_" <> n)
    return (fname, normalBang, t)
  let
    recname = convertNameToPrefixedNameType name "Output"
    cs = [RecC recname recs]
  return $ DataInstD [] Nothing (AppT (ConT $ mkName "VtyAppOutput") (varNetwork name)) Nothing cs []

mkvar :: String -> Q Type
mkvar = varT . mkName

declareInputs :: Name -> [(String, Q Type)] -> Q [Dec]
declareInputs name inputEventTypes = do
  recs <- forM (fmap (\(x,q) -> fmap (x,) q) inputEventTypes) $ \nt -> do
    (n, t) <- nt
    let
      infname = convertNameToPrefixedNameField name ("InputEvents_" <> n)
      trigfname = convertNameToPrefixedNameField name ("InputTriggerRefs_" <> n)
    int <- [t|Event $(tv) $(return t)|]
    trigt <- [t| Ref $(mkvar "m") (Maybe (EventTrigger $(tv) $(return t))) |]
    return ((infname, normalBang, int), (trigfname, normalBang, trigt))

  let
    (inrecs, trigrecs) = unzip recs
    incs = [RecC (convertNameToPrefixedNameType name "InputEvents") inrecs]
    trigcs = [RecC (convertNameToPrefixedNameType name "InputTriggerRefs") trigrecs]
    indi = DataInstD [] Nothing (AppT (ConT $ mkName "VtyAppInputEvents") (varNetwork name)) Nothing incs []
    trigdi = DataInstD [] Nothing (AppT (ConT $ mkName "VtyAppInputTriggerRefs") (varNetwork name)) Nothing trigcs []
  return [indi, trigdi]

declareMakeInputs :: Name -> [(String, Q Type)] -> Q Dec
declareMakeInputs name inputEventTypes = do
  varnames <- forM (fmap (\(x,q) -> fmap (x,) q) inputEventTypes) $ \nt -> do
    (n, t) <- nt
    evname <- newName "ev"
    trefname <- newName "ref"
    return (evname, trefname)
  refstmts <- forM varnames $ \(evname,trefname) -> do
    return $ BindS (TupP [VarP evname, VarP trefname]) $ VarE (mkName "newEventWithTriggerRef")
  let
    returnstmtsfst = foldl AppE (ConE $ convertNameToPrefixedNameType name "InputEvents") (fmap (VarE . fst) varnames)
    returnstmtssnd = foldl AppE (ConE $ convertNameToPrefixedNameType name "InputTriggerRefs") (fmap (VarE . snd) varnames)
  returnstmts <- [|return ($(return returnstmtsfst),$(return returnstmtssnd))|]
  let
    b = NormalB $ DoE Nothing (refstmts <> [NoBindS returnstmts])
    c = Clause [] b []
  return $ FunD (mkName "makeInputs") [c]


declareGetApp :: Name -> Q Exp -> Q Dec
declareGetApp name body' = do
  body <- body'
  let
    b = NormalB $ body
    c = Clause [VarP $ getAppInputEventsArgName] b []
  return $ FunD (mkName "getApp") [c]