large-anon-0.2: src/Data/Record/Anon/Internal/Plugin/Source.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ViewPatterns #-}
module Data.Record.Anon.Internal.Plugin.Source (sourcePlugin) where
import Control.Monad
import Control.Monad.Trans
import Data.Generics (everywhereM, mkM)
import Data.Record.Anon.Internal.Plugin.Source.FreshT
import Data.Record.Anon.Internal.Plugin.Source.GhcShim
import Data.Record.Anon.Internal.Plugin.Source.Names
import Data.Record.Anon.Internal.Plugin.Source.Options
{-------------------------------------------------------------------------------
Top-level
-------------------------------------------------------------------------------}
sourcePlugin :: [String] -> HsParsedModule -> Hsc HsParsedModule
sourcePlugin rawOpts
parsed@HsParsedModule{
hpm_module = L l modl@HsModule{hsmodDecls = decls}
} = do
decls' <- runFreshHsc $
everywhereM
(mkM $ transformExpr opts)
decls
return $ parsed {
hpm_module = L l $ modl { hsmodDecls = decls' }
}
where
opts :: Options
opts = parseOpts rawOpts
transformExpr :: Options -> LHsExpr GhcPs -> FreshT Hsc (LHsExpr GhcPs)
transformExpr options@Options{debug} e@(reLoc -> L l expr)
| RecordCon _ext (L _ nm) (HsRecFields flds dotdot) <- expr
, Unqual nm' <- nm
, Nothing <- dotdot
, Just mode <- parseMode (occNameString nm')
, Just flds' <- mapM getField flds
= do names <- lift $ getLargeAnonNames mode
e' <- anonRec options names l flds'
when debug $ lift $ issueWarning l (debugMsg e')
return e'
| otherwise
= return e
where
getField ::
LHsRecField GhcPs (LHsExpr GhcPs)
-> Maybe (FastString, LHsExpr GhcPs)
#if __GLASGOW_HASKELL__ < 904
getField (L _ (HsRecField
{ hsRecFieldLbl = L _ fieldOcc
, hsRecFieldArg = arg
, hsRecPun = pun }))
#else
getField (L _ (HsFieldBind
{ hfbLHS = L _ fieldOcc
, hfbRHS = arg
, hfbPun = pun }))
#endif
| FieldOcc _ (L _ nm) <- fieldOcc
, Unqual nm' <- nm
, not pun
= Just (occNameFS nm', arg)
| otherwise
= Nothing
debugMsg :: LHsExpr GhcPs -> SDoc
debugMsg expr = pprSetDepth AllTheWay $ vcat [
text "large-records: splicing in the following expression:"
, ppr expr
]
{-------------------------------------------------------------------------------
Main translation
-------------------------------------------------------------------------------}
anonRec ::
Options
-> LargeAnonNames
-> SrcSpan
-> [(FastString, LHsExpr GhcPs)]
-> FreshT Hsc (LHsExpr GhcPs)
anonRec Options{typelet, noapply} names@LargeAnonNames{..} l = \fields ->
applyDiff =<< go fields
where
go :: [(FastString, LHsExpr GhcPs)] -> FreshT Hsc (LHsExpr GhcPs)
go fields
| null fields = do
return $ mkVar l largeAnon_empty
| not typelet = do
lift $ recordWithoutTypelet names l fields
| otherwise = do
p <- freshVar l "p"
fields' <- mapM (\(n, e) -> (n,e,) <$> freshVar l "xs") fields
lift $ recordWithTypelet names l p fields'
applyDiff :: LHsExpr GhcPs -> FreshT Hsc (LHsExpr GhcPs)
applyDiff e
| noapply = return e
| otherwise = return $ mkVar l largeAnon_applyPending `mkHsApp` e
recordWithoutTypelet ::
LargeAnonNames
-> SrcSpan
-> [(FastString, LHsExpr GhcPs)]
-> Hsc (LHsExpr GhcPs)
recordWithoutTypelet LargeAnonNames{..} l = \fields -> do
return $ go fields
where
go :: [(FastString, LHsExpr GhcPs)] -> LHsExpr GhcPs
go [] = mkVar l largeAnon_empty
go ((n,e):fs) = mkVar l largeAnon_insert `mkHsApps` [mkLabel l n, e, go fs]
-- | Experimental support for typelet
--
-- See documentation of 'letRecordT' and 'letInsertAs'.
recordWithTypelet ::
LargeAnonNames
-> SrcSpan
-> RdrName -- ^ Fresh var for the proxy
-> [(FastString, LHsExpr GhcPs, RdrName)] -- ^ Fresh var for each insert
-> Hsc (LHsExpr GhcPs)
recordWithTypelet LargeAnonNames{..} l p = \fields -> do
return $
mkHsApp (mkVar l largeAnon_letRecordT) $
simpleLam p $ mkHsApp (mkVar l typelet_castEqual) $
go (mkVar l largeAnon_empty) $ reverse fields
where
go ::
LHsExpr GhcPs
-> [(FastString, LHsExpr GhcPs, RdrName)]
-> LHsExpr GhcPs
go prev [] = mkHsApp (mkVar l typelet_castEqual) prev
go prev ((n,e,x):fs) = mkHsApps (mkVar l largeAnon_letInsertAs) [
mkVar l p
, mkLabel l n
, e
, prev
, simpleLam x $ go (mkVar l x) fs
]
where
{-------------------------------------------------------------------------------
Auxiliary
-------------------------------------------------------------------------------}
mkVar :: SrcSpan -> RdrName -> LHsExpr GhcPs
mkVar l name = reLocA $ L l $ HsVar defExt (reLocA $ L l name)
-- | Construct simple lambda
--
-- Constructs lambda of the form
--
-- > \x -> e
simpleLam :: RdrName -> LHsExpr GhcPs -> LHsExpr GhcPs
simpleLam x body = mkHsLam [nlVarPat x] body