packages feed

ghc-usage-0.1.0.0: GhcUsage.hs

module GhcUsage (frontendPlugin) where

import qualified GHC
import GhcPlugins
import DriverPhases
import GhcMonad
import UniqDFM
import UniqDSet
import Avail
import Outputable

import System.IO
import System.Exit
import Control.Monad
import Data.Maybe
import Data.List
import Data.Map (Map)
import qualified Data.Map as Map

frontendPlugin :: FrontendPlugin
frontendPlugin = defaultFrontendPlugin {
  frontend = usage
  }

usage :: [String] -> [(String, Maybe Phase)] -> Ghc ()
usage _flags args = do
    dflags0 <- getDynFlags
    -- NB: frontend plugin defaults to OneShot
    _ <- GHC.setSessionDynFlags dflags0 { ghcMode = CompManager }
    dflags <- getDynFlags
    let (hs_srcs, non_hs_srcs) = partition isHaskellishTarget args
    targets <- mapM (uncurry GHC.guessTarget) hs_srcs
    GHC.setTargets targets
    ok_flag <- GHC.load GHC.LoadAllTargets
    when (failed ok_flag) (liftIO $ exitWith (ExitFailure 1))
    hsc_env <- GHC.getSession
    let ifaces = map hm_iface
               . eltsHpt
               $ hsc_HPT hsc_env
        usages = Map.unionsWith unionUniqDSets
               . map usageModIface
               $ ifaces
    liftIO . printForUser dflags stdout neverQualify
           . vcat
           . vpunctuate (text "")
           . map (renderHeader usages)
           -- . filter ((==HsigFile) . mi_hsc_src)
           $ ifaces
    return ()

vpunctuate :: SDoc   -- ^ The punctuation
           -> [SDoc] -- ^ The list that will have punctuation added between every adjacent pair of elements
           -> [SDoc] -- ^ Punctuated list
vpunctuate _ []     = []
vpunctuate p (d:ds) = go d ds
                   where
                     go d [] = [d]
                     go d (e:es) = (d $$ p) : go e es

usageModIface :: ModIface -> Map ModuleName (UniqDSet OccName)
usageModIface iface = Map.fromList $
    [ (mod_name, mkUniqDSet (map fst occs_w_fp))
    | UsageHomeModule{ usg_mod_name = mod_name
                     , usg_entities = occs_w_fp } <- mi_usages iface ]

renderName :: Name -> SDoc
renderName n = let occ = occName n
               in parenSymOcc occ (ppr occ)

renderField :: FieldLabel -> SDoc
renderField f = ftext (flLabel f)

renderAvailInfo :: AvailInfo -> SDoc
renderAvailInfo a
    = renderName n <>
        if null ns && null fs
            then empty
            else parens . fsep . punctuate comma
               $ map renderName (sortOn (occNameFS . occName) ns)
              ++ map renderField (sortOn flLabel fs)
  where
    n  = availName a
    ns = filter (n /=) (availNonFldNames a)
    fs = availFlds a

renderHeader :: Map ModuleName (UniqDSet OccName) -> ModIface -> SDoc
renderHeader usages iface
    = header <+> ppr mod_name <+> text "(" $$
      nest 4 pp_exports $$
      text ") where"
  where
    mod_name = moduleName (mi_module iface)
    usage = fromMaybe emptyUniqDSet (Map.lookup mod_name usages)
    header | HsigFile <- mi_hsc_src iface = text "signature"
           | otherwise                    = text "module"
    pp_exports = vcat
               . punctuate comma
               . map renderAvailInfo
               . sortOn availSortKey
               $ exports
    exports = filterAvails ((`elementOfUniqDSet` usage) . occName)
            $ mi_exports iface

availSortKey :: AvailInfo -> String
availSortKey a
    -- Fortuitously, capital letters sort first.
    = occNameString . occName $ availName a