{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Retrie.Replace
( replace
, Replacement(..)
, Change(..)
) where
import Control.Monad.Trans.Class
import Control.Monad.Writer.Strict
import Data.Char (isSpace)
import Data.Generics
import Retrie.ExactPrint
import Retrie.Expr
import Retrie.FreeVars
import Retrie.GHC
import Retrie.Subst
import Retrie.Types
import Retrie.Universe
replace
:: (Data a, MonadIO m) => Context -> a -> TransformT (WriterT Change m) a
replace c =
mkM (replaceImpl @(HsExpr GhcPs) c)
`extM` (replaceImpl @(Stmt GhcPs (LHsExpr GhcPs)) c)
`extM` (replaceImpl @(HsType GhcPs) c)
replaceImpl
:: forall ast m. (Annotate ast, Matchable (Located ast), MonadIO m)
=> Context -> Located ast -> TransformT (WriterT Change m) (Located ast)
replaceImpl c e = do
let
f result@RewriterResult{..} = result
{ rrTransformer =
fmap (fmap (check rrOrigin rrQuantifiers)) <$> rrTransformer
}
check origin quantifiers match
| getLoc e `overlaps` origin = NoMatch
| MatchResult _ Template{..} <- match
, fvs <- freeVars quantifiers (astA tTemplate)
, any (`elemFVs` fvs) (ctxtBinders c) = NoMatch
| otherwise = match
match <- runRewriter f c (ctxtRewriter c) (getUnparened e)
case match of
NoMatch -> return e
MatchResult sub Template{..} -> do
t' <- graftA tTemplate
r <- subst sub c t'
addAllAnnsT e r
res <- (mkM (parenify c) `extM` parenifyT c) r
orig <- printNoLeadingSpaces <$> pruneA e
repl <- printNoLeadingSpaces <$> pruneA res
let replacement = Replacement (getLoc e) orig repl
TransformT $ lift $ tell $ Change [replacement] [tImports]
return res
data Replacement = Replacement
{ replLocation :: SrcSpan
, replOriginal :: String
, replReplacement :: String
}
data Change = NoChange | Change [Replacement] [AnnotatedImports]
instance Semigroup Change where
(<>) = mappend
instance Monoid Change where
mempty = NoChange
mappend NoChange other = other
mappend other NoChange = other
mappend (Change rs1 is1) (Change rs2 is2) =
Change (rs1 <> rs2) (is1 <> is2)
getUnparened :: Data k => k -> k
getUnparened = mkT e `extT` t
where
e :: LHsExpr GhcPs -> LHsExpr GhcPs
#if __GLASGOW_HASKELL__ < 806
e (L _ (HsPar expr)) = expr
#else
e (L _ (HsPar _ expr)) = expr
#endif
e other = other
t :: LHsType GhcPs -> LHsType GhcPs
#if __GLASGOW_HASKELL__ < 806
t (L _ (HsParTy ty)) = ty
#else
t (L _ (HsParTy _ ty)) = ty
#endif
t other = other
printNoLeadingSpaces :: Annotate k => Annotated (Located k) -> String
printNoLeadingSpaces = dropWhile isSpace . printA