trace-embrace-1.2.0: src/Debug/TraceEmbrace/FileIndex.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# OPTIONS_GHC -Wno-orphans #-}
-- | Index functions and methods of a Haskell module by line number
module Debug.TraceEmbrace.FileIndex where
import Data.IntMap.Strict qualified as IM
import Data.String ( IsString )
import GHC.Data.FastString ( mkFastString )
import GHC.Data.StringBuffer ( stringToStringBuffer )
import GHC.Driver.Config.Parser ( initParserOpts )
import GHC.Driver.DynFlags ( HasDynFlags(..) )
import GHC.Exts (IsList (toList))
import GHC.Hs.Extension (GhcPs)
import GHC.Parser ( parseModule )
import GHC.Parser.Lexer
( ParserOpts,
P(unP),
ParseResult(..),
getPsErrorMessages,
initParserState )
import GHC.Tc.Types ( TcM )
import GHC.Types.Name ( occNameString )
import GHC.Types.Name.Reader ( RdrName(Unqual) )
import GHC.Types.SrcLoc
( GenLocated(L), mkRealSrcLoc, realSrcSpanStart, srcLocLine )
import GHC.Utils.Outputable
( Outputable(ppr), defaultSDocContext, renderWithContext )
import Language.Haskell.Syntax
import Language.Haskell.TH.Syntax (Q (..), runIO, getQ, putQ, Loc (..), Lift, reportWarning)
import Language.Preprocessor.Cpphs (runCpphs, defaultCpphsOptions)
import Unsafe.Coerce ( unsafeCoerce )
#if MIN_VERSION_base(4,21,0)
import GHC.Parser.Annotation (epaLocationRealSrcSpan, SrcSpanAnnA, EpAnn(entry, EpAnn) )
#else
import GHC.Parser.Annotation (anchor, SrcSpanAnnA, EpAnn(entry, EpAnn))
import GHC.Types.SrcLoc (EpaLocation', RealSrcSpan)
epaLocationRealSrcSpan :: EpaLocation' a -> RealSrcSpan
epaLocationRealSrcSpan = anchor
#endif
newtype FunName = FunName { unFunName :: String } deriving (Show, Eq, Ord, IsString, Lift)
type LineFileIndex = IM.IntMap FunName
unsafeRunTcM :: TcM a -> Q a
unsafeRunTcM m = Q (unsafeCoerce m)
instance HasDynFlags Q where
getDynFlags = unsafeRunTcM getDynFlags
calret :: Monad m => (a -> m ()) -> a -> m a
calret f a = f a >> pure a
getLineFileIndex' :: FilePath -> Q LineFileIndex
getLineFileIndex' fp = getQ >>= maybe (calret putQ =<< mkLineFunIndex fp) pure
getLineFileIndex :: Loc -> Q LineFileIndex
getLineFileIndex = getLineFileIndex' . loc_filename
indexEntry :: EpAnn ann -> GenLocated l RdrName -> [(Int, FunName)]
indexEntry EpAnn {entry} = \case
L _ fi ->
case fi of
Unqual s ->
[ ( srcLocLine (realSrcSpanStart (epaLocationRealSrcSpan entry))
, FunName $ occNameString s
)
]
_ -> []
mkLineFunIndex :: FilePath -> Q LineFileIndex
mkLineFunIndex fp = do
fileContent <- runIO (runCpphs defaultCpphsOptions fp =<< readFile fp)
ops <- initParserOpts <$> getDynFlags
case runParser fp ops fileContent parseModule of
POk _ (L _ r) ->
pure $ IM.fromList (concatMap extract (hsmodDecls r))
PFailed ps -> do
reportWarning $
"Failed to parse [" <> fp <> "] for line function index due: " <>
renderWithContext defaultSDocContext (ppr $ getPsErrorMessages ps) <>
"\n--------------------------------------------------------------------"
pure mempty
where
methodExtract (L l (FunBind {fun_id})) = indexEntry l fun_id
methodExtract _ = []
extract :: GenLocated SrcSpanAnnA (HsDecl GhcPs) -> [(IM.Key, FunName)]
extract (L l (ValD _ (FunBind {fun_id}))) = indexEntry l fun_id
extract (L _ (InstD _ (ClsInstD {cid_inst}))) =
case cid_inst of
ClsInstDecl {cid_binds} -> concatMap methodExtract (toList cid_binds)
extract (L _ (TyClD _ (ClassDecl {tcdMeths}))) =
concatMap methodExtract (toList tcdMeths)
extract _ = []
runParser :: FilePath -> ParserOpts -> String -> P a -> ParseResult a
runParser filename opts str parser = unP parser parseState
where
location' = mkRealSrcLoc (mkFastString filename) 1 1
buffer = stringToStringBuffer str
parseState = initParserState opts buffer location'