PageRenderTime 45ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/utils/ghctags/Main.hs

https://github.com/tibbe/ghc
Haskell | 361 lines | 274 code | 49 blank | 38 comment | 14 complexity | 7564607e607f3d9b1d4364644ad602c2 MD5 | raw file
  1. {-# LANGUAGE ScopedTypeVariables #-}
  2. module Main where
  3. import Prelude hiding ( mod, id, mapM )
  4. import GHC
  5. --import Packages
  6. import HscTypes ( isBootSummary )
  7. import Digraph ( flattenSCCs )
  8. import DriverPhases ( isHaskellSrcFilename )
  9. import HscTypes ( msHsFilePath )
  10. import Name ( getOccString )
  11. --import ErrUtils ( printBagOfErrors )
  12. import Panic ( panic )
  13. import DynFlags ( defaultFatalMessager, defaultFlushOut )
  14. import Bag
  15. import Exception
  16. import FastString
  17. import MonadUtils ( liftIO )
  18. import SrcLoc
  19. import Distribution.Simple.GHC ( componentGhcOptions )
  20. import Distribution.Simple.Configure ( getPersistBuildConfig )
  21. import Distribution.Simple.Program.GHC ( renderGhcOptions )
  22. import Distribution.PackageDescription ( library, libBuildInfo )
  23. import Distribution.Simple.LocalBuildInfo
  24. import qualified Distribution.Verbosity as V
  25. import Control.Monad hiding (mapM)
  26. import System.Environment
  27. import System.Console.GetOpt
  28. import System.Exit
  29. import System.IO
  30. import Data.List as List hiding ( group )
  31. import Data.Traversable (mapM)
  32. import Data.Map ( Map )
  33. import qualified Data.Map as M
  34. --import UniqFM
  35. --import Debug.Trace
  36. -- search for definitions of things
  37. -- we do this by parsing the source and grabbing top-level definitions
  38. -- We generate both CTAGS and ETAGS format tags files
  39. -- The former is for use in most sensible editors, while EMACS uses ETAGS
  40. ----------------------------------
  41. ---- CENTRAL DATA TYPES ----------
  42. type FileName = String
  43. type ThingName = String -- name of a defined entity in a Haskell program
  44. -- A definition we have found (we know its containing module, name, and location)
  45. data FoundThing = FoundThing ModuleName ThingName RealSrcLoc
  46. -- Data we have obtained from a file (list of things we found)
  47. data FileData = FileData FileName [FoundThing] (Map Int String)
  48. --- invariant (not checked): every found thing has a source location in that file?
  49. ------------------------------
  50. -------- MAIN PROGRAM --------
  51. main :: IO ()
  52. main = do
  53. progName <- getProgName
  54. let usageString =
  55. "Usage: " ++ progName ++ " [OPTION...] [-- GHC OPTION... --] [files...]"
  56. args <- getArgs
  57. let (ghcArgs', ourArgs, unbalanced) = splitArgs args
  58. let (flags, filenames, errs) = getOpt Permute options ourArgs
  59. let (hsfiles, otherfiles) = List.partition isHaskellSrcFilename filenames
  60. let ghc_topdir = case [ d | FlagTopDir d <- flags ] of
  61. [] -> ""
  62. (x:_) -> x
  63. mapM_ (\n -> putStr $ "Warning: ignoring non-Haskellish file " ++ n ++ "\n")
  64. otherfiles
  65. if unbalanced || errs /= [] || elem FlagHelp flags || hsfiles == []
  66. then do
  67. putStr $ unlines errs
  68. putStr $ usageInfo usageString options
  69. exitWith (ExitFailure 1)
  70. else return ()
  71. ghcArgs <- case [ d | FlagUseCabalConfig d <- flags ] of
  72. [distPref] -> do
  73. cabalOpts <- flagsFromCabal distPref
  74. return (cabalOpts ++ ghcArgs')
  75. [] ->
  76. return ghcArgs'
  77. _ -> error "Too many --use-cabal-config flags"
  78. print ghcArgs
  79. let modes = getMode flags
  80. let openFileMode = if elem FlagAppend flags
  81. then AppendMode
  82. else WriteMode
  83. ctags_hdl <- if CTags `elem` modes
  84. then Just `liftM` openFile "tags" openFileMode
  85. else return Nothing
  86. etags_hdl <- if ETags `elem` modes
  87. then Just `liftM` openFile "TAGS" openFileMode
  88. else return Nothing
  89. GHC.defaultErrorHandler defaultFatalMessager defaultFlushOut $
  90. runGhc (Just ghc_topdir) $ do
  91. --liftIO $ print "starting up session"
  92. dflags <- getSessionDynFlags
  93. (pflags, unrec, warns) <- parseDynamicFlags dflags{ verbosity=1 }
  94. (map noLoc ghcArgs)
  95. unless (null unrec) $
  96. liftIO $ putStrLn $ "Unrecognised options:\n" ++ show (map unLoc unrec)
  97. liftIO $ mapM_ putStrLn (map unLoc warns)
  98. let dflags2 = pflags { hscTarget = HscNothing } -- don't generate anything
  99. -- liftIO $ print ("pkgDB", case (pkgDatabase dflags2) of Nothing -> 0
  100. -- Just m -> sizeUFM m)
  101. _ <- setSessionDynFlags dflags2
  102. --liftIO $ print (length pkgs)
  103. GHC.defaultCleanupHandler dflags2 $ do
  104. targetsAtOneGo hsfiles (ctags_hdl,etags_hdl)
  105. mapM_ (mapM (liftIO . hClose)) [ctags_hdl, etags_hdl]
  106. ----------------------------------------------
  107. ---------- ARGUMENT PROCESSING --------------
  108. data Flag
  109. = FlagETags
  110. | FlagCTags
  111. | FlagBoth
  112. | FlagAppend
  113. | FlagHelp
  114. | FlagTopDir FilePath
  115. | FlagUseCabalConfig FilePath
  116. | FlagFilesFromCabal
  117. deriving (Ord, Eq, Show)
  118. -- ^Represents options passed to the program
  119. data Mode = ETags | CTags deriving Eq
  120. getMode :: [Flag] -> [Mode]
  121. getMode fs = go (concatMap modeLike fs)
  122. where go [] = [ETags,CTags]
  123. go [x] = [x]
  124. go more = nub more
  125. modeLike FlagETags = [ETags]
  126. modeLike FlagCTags = [CTags]
  127. modeLike FlagBoth = [ETags,CTags]
  128. modeLike _ = []
  129. splitArgs :: [String] -> ([String], [String], Bool)
  130. -- ^Pull out arguments between -- for GHC
  131. splitArgs args0 = split [] [] False args0
  132. where split ghc' tags' unbal ("--" : args) = split tags' ghc' (not unbal) args
  133. split ghc' tags' unbal (arg : args) = split ghc' (arg:tags') unbal args
  134. split ghc' tags' unbal [] = (reverse ghc', reverse tags', unbal)
  135. options :: [OptDescr Flag]
  136. -- supports getopt
  137. options = [ Option "" ["topdir"]
  138. (ReqArg FlagTopDir "DIR") "root of GHC installation (optional)"
  139. , Option "c" ["ctags"]
  140. (NoArg FlagCTags) "generate CTAGS file (ctags)"
  141. , Option "e" ["etags"]
  142. (NoArg FlagETags) "generate ETAGS file (etags)"
  143. , Option "b" ["both"]
  144. (NoArg FlagBoth) ("generate both CTAGS and ETAGS")
  145. , Option "a" ["append"]
  146. (NoArg FlagAppend) ("append to existing CTAGS and/or ETAGS file(s)")
  147. , Option "" ["use-cabal-config"]
  148. (ReqArg FlagUseCabalConfig "DIR") "use local cabal configuration from dist dir"
  149. , Option "" ["files-from-cabal"]
  150. (NoArg FlagFilesFromCabal) "use files from cabal"
  151. , Option "h" ["help"] (NoArg FlagHelp) "This help"
  152. ]
  153. flagsFromCabal :: FilePath -> IO [String]
  154. flagsFromCabal distPref = do
  155. lbi <- getPersistBuildConfig distPref
  156. let pd = localPkgDescr lbi
  157. findLibraryConfig [] = Nothing
  158. findLibraryConfig ((CLibName, clbi, _) : _) = Just clbi
  159. findLibraryConfig (_ : xs) = findLibraryConfig xs
  160. mLibraryConfig = findLibraryConfig (componentsConfigs lbi)
  161. case (library pd, mLibraryConfig) of
  162. (Just lib, Just clbi) ->
  163. let bi = libBuildInfo lib
  164. odir = buildDir lbi
  165. opts = componentGhcOptions V.normal lbi bi clbi odir
  166. in return $ renderGhcOptions (compiler lbi) opts
  167. _ -> error "no library"
  168. ----------------------------------------------------------------
  169. --- LOADING HASKELL SOURCE
  170. --- (these bits actually run the compiler and produce abstract syntax)
  171. safeLoad :: LoadHowMuch -> Ghc SuccessFlag
  172. -- like GHC.load, but does not stop process on exception
  173. safeLoad mode = do
  174. _dflags <- getSessionDynFlags
  175. ghandle (\(e :: SomeException) -> liftIO (print e) >> return Failed ) $
  176. handleSourceError (\e -> printException e >> return Failed) $
  177. load mode
  178. targetsAtOneGo :: [FileName] -> (Maybe Handle, Maybe Handle) -> Ghc ()
  179. -- load a list of targets
  180. targetsAtOneGo hsfiles handles = do
  181. targets <- mapM (\f -> guessTarget f Nothing) hsfiles
  182. setTargets targets
  183. modgraph <- depanal [] False
  184. let mods = flattenSCCs $ topSortModuleGraph False modgraph Nothing
  185. graphData mods handles
  186. fileTarget :: FileName -> Target
  187. fileTarget filename = Target (TargetFile filename Nothing) True Nothing
  188. ---------------------------------------------------------------
  189. ----- CRAWLING ABSTRACT SYNTAX TO SNAFFLE THE DEFINITIONS -----
  190. graphData :: ModuleGraph -> (Maybe Handle, Maybe Handle) -> Ghc ()
  191. graphData graph handles = do
  192. mapM_ foundthings graph
  193. where foundthings ms =
  194. let filename = msHsFilePath ms
  195. modname = moduleName $ ms_mod ms
  196. in handleSourceError (\e -> do
  197. printException e
  198. liftIO $ exitWith (ExitFailure 1)) $
  199. do liftIO $ putStrLn ("loading " ++ filename)
  200. mod <- loadModule =<< typecheckModule =<< parseModule ms
  201. case mod of
  202. _ | isBootSummary ms -> return ()
  203. _ | Just s <- renamedSource mod ->
  204. liftIO (writeTagsData handles =<< fileData filename modname s)
  205. _otherwise ->
  206. liftIO $ exitWith (ExitFailure 1)
  207. fileData :: FileName -> ModuleName -> RenamedSource -> IO FileData
  208. fileData filename modname (group, _imports, _lie, _doc) = do
  209. -- lie is related to type checking and so is irrelevant
  210. -- imports contains import declarations and no definitions
  211. -- doc and haddock seem haddock-related; let's hope to ignore them
  212. ls <- lines `fmap` readFile filename
  213. let line_map = M.fromAscList $ zip [1..] ls
  214. line_map' <- evaluate line_map
  215. return $ FileData filename (boundValues modname group) line_map'
  216. boundValues :: ModuleName -> HsGroup Name -> [FoundThing]
  217. -- ^Finds all the top-level definitions in a module
  218. boundValues mod group =
  219. let vals = case hs_valds group of
  220. ValBindsOut nest _sigs ->
  221. [ x | (_rec, binds) <- nest
  222. , bind <- bagToList binds
  223. , x <- boundThings mod bind ]
  224. _other -> error "boundValues"
  225. tys = [ n | ns <- map hsLTyClDeclBinders (tyClGroupConcat (hs_tyclds group))
  226. , n <- map found ns ]
  227. fors = concat $ map forBound (hs_fords group)
  228. where forBound lford = case unLoc lford of
  229. ForeignImport n _ _ _ -> [found n]
  230. ForeignExport { } -> []
  231. in vals ++ tys ++ fors
  232. where found = foundOfLName mod
  233. startOfLocated :: Located a -> RealSrcLoc
  234. startOfLocated lHs = case getLoc lHs of
  235. RealSrcSpan l -> realSrcSpanStart l
  236. UnhelpfulSpan _ -> panic "startOfLocated UnhelpfulSpan"
  237. foundOfLName :: ModuleName -> Located Name -> FoundThing
  238. foundOfLName mod id = FoundThing mod (getOccString $ unLoc id) (startOfLocated id)
  239. boundThings :: ModuleName -> LHsBind Name -> [FoundThing]
  240. boundThings modname lbinding =
  241. case unLoc lbinding of
  242. FunBind { fun_id = id } -> [thing id]
  243. PatBind { pat_lhs = lhs } -> patThings lhs []
  244. VarBind { var_id = id } -> [FoundThing modname (getOccString id) (startOfLocated lbinding)]
  245. AbsBinds { } -> [] -- nothing interesting in a type abstraction
  246. PatSynBind { patsyn_id = id } -> [thing id]
  247. where thing = foundOfLName modname
  248. patThings lpat tl =
  249. let loc = startOfLocated lpat
  250. lid id = FoundThing modname (getOccString id) loc
  251. in case unLoc lpat of
  252. WildPat _ -> tl
  253. VarPat name -> lid name : tl
  254. LazyPat p -> patThings p tl
  255. AsPat id p -> patThings p (thing id : tl)
  256. ParPat p -> patThings p tl
  257. BangPat p -> patThings p tl
  258. ListPat ps _ _ -> foldr patThings tl ps
  259. TuplePat ps _ _ -> foldr patThings tl ps
  260. PArrPat ps _ -> foldr patThings tl ps
  261. ConPatIn _ conargs -> conArgs conargs tl
  262. ConPatOut{ pat_args = conargs } -> conArgs conargs tl
  263. LitPat _ -> tl
  264. NPat _ _ _ -> tl -- form of literal pattern?
  265. NPlusKPat id _ _ _ -> thing id : tl
  266. SigPatIn p _ -> patThings p tl
  267. SigPatOut p _ -> patThings p tl
  268. _ -> error "boundThings"
  269. conArgs (PrefixCon ps) tl = foldr patThings tl ps
  270. conArgs (RecCon (HsRecFields { rec_flds = flds })) tl
  271. = foldr (\f tl' -> patThings (hsRecFieldArg f) tl') tl flds
  272. conArgs (InfixCon p1 p2) tl = patThings p1 $ patThings p2 tl
  273. -- stuff for dealing with ctags output format
  274. writeTagsData :: (Maybe Handle, Maybe Handle) -> FileData -> IO ()
  275. writeTagsData (mb_ctags_hdl, mb_etags_hdl) fd = do
  276. maybe (return ()) (\hdl -> writectagsfile hdl fd) mb_ctags_hdl
  277. maybe (return ()) (\hdl -> writeetagsfile hdl fd) mb_etags_hdl
  278. writectagsfile :: Handle -> FileData -> IO ()
  279. writectagsfile ctagsfile filedata = do
  280. let things = getfoundthings filedata
  281. mapM_ (\x -> hPutStrLn ctagsfile $ dumpthing False x) things
  282. mapM_ (\x -> hPutStrLn ctagsfile $ dumpthing True x) things
  283. getfoundthings :: FileData -> [FoundThing]
  284. getfoundthings (FileData _filename things _src_lines) = things
  285. dumpthing :: Bool -> FoundThing -> String
  286. dumpthing showmod (FoundThing modname name loc) =
  287. fullname ++ "\t" ++ filename ++ "\t" ++ (show line)
  288. where line = srcLocLine loc
  289. filename = unpackFS $ srcLocFile loc
  290. fullname = if showmod then moduleNameString modname ++ "." ++ name
  291. else name
  292. -- stuff for dealing with etags output format
  293. writeetagsfile :: Handle -> FileData -> IO ()
  294. writeetagsfile etagsfile = hPutStr etagsfile . e_dumpfiledata
  295. e_dumpfiledata :: FileData -> String
  296. e_dumpfiledata (FileData filename things line_map) =
  297. "\x0c\n" ++ filename ++ "," ++ (show thingslength) ++ "\n" ++ thingsdump
  298. where
  299. thingsdump = concat $ map (e_dumpthing line_map) things
  300. thingslength = length thingsdump
  301. e_dumpthing :: Map Int String -> FoundThing -> String
  302. e_dumpthing src_lines (FoundThing modname name loc) =
  303. tagline name ++ tagline (moduleNameString modname ++ "." ++ name)
  304. where tagline n = src_code ++ "\x7f"
  305. ++ n ++ "\x01"
  306. ++ (show line) ++ "," ++ (show $ column) ++ "\n"
  307. line = srcLocLine loc
  308. column = srcLocCol loc
  309. src_code = case M.lookup line src_lines of
  310. Just l -> take (column + length name) l
  311. Nothing -> --trace (show ("not found: ", moduleNameString modname, name, line, column))
  312. name