catch up with changes to checkModule
[ghc.git] / utils / ghctags / GhcTags.hs
1 module Main where
2
3 import GHC
4 import DriverPhases ( isHaskellSrcFilename )
5 import HscTypes (msHsFilePath)
6 import Name
7 import Outputable
8 import ErrUtils ( printBagOfErrors )
9 import DynFlags(GhcMode, defaultDynFlags)
10 import SrcLoc
11 import Bag
12 import Util ( handle, handleDyn )
13 import FastString
14
15 import System.Environment
16 import System.Console.GetOpt
17 import System.Exit
18 import Data.Char
19 import System.IO
20 import Data.List as List
21 import Data.Maybe
22
23 -- search for definitions of things
24 -- we do this by parsing the source and grabbing top-level definitions
25
26 -- We generate both CTAGS and ETAGS format tags files
27 -- The former is for use in most sensible editors, while EMACS uses ETAGS
28
29 ---------------------------------
30 --------- CONFIGURATION ---------
31
32 ghcRootDir = "/usr/local/lib/ghc-6.5" --- root for -package ghc? (passed to GHC.init)
33
34
35 ----------------------------------
36 ---- CENTRAL DATA TYPES ----------
37
38 type FileName = String
39 type ThingName = String -- name of a defined entity in a Haskell program
40
41 -- A definition we have found (we know its containing module, name, and location)
42 data FoundThing = FoundThing ModuleName ThingName SrcLoc
43
44 -- Data we have obtained from a file (list of things we found)
45 data FileData = FileData FileName [FoundThing]
46 --- invariant (not checked): every found thing has a source location in that file?
47
48
49 ------------------------------
50 -------- MAIN PROGRAM --------
51
52 main :: IO ()
53 main = do
54 progName <- getProgName
55 let usageString =
56 "Usage: " ++ progName ++ " [OPTION...] [-- GHC OPTION... --] [files...]"
57 args <- getArgs
58 let (ghcArgs, ourArgs, unbalanced) = splitArgs args
59 let (modes, filenames, errs) = getOpt Permute options ourArgs
60 let (hsfiles, otherfiles) = List.partition isHaskellSrcFilename filenames
61 mapM_ (\n -> putStr $ "Warning: ignoring non-Haskellish file " ++ n ++ "\n")
62 otherfiles
63 if unbalanced || errs /= [] || elem Help modes || hsfiles == []
64 then do
65 putStr $ unlines errs
66 putStr $ usageInfo usageString options
67 exitWith (ExitFailure 1)
68 else return ()
69 GHC.defaultErrorHandler defaultDynFlags $ do
70 session <- newSession (Just ghcRootDir)
71 flags <- getSessionDynFlags session
72 (pflags, _) <- parseDynamicFlags flags ghcArgs
73 let flags = pflags { hscTarget = HscNothing } -- don't generate anything
74 GHC.defaultCleanupHandler flags $ do
75 setSessionDynFlags session flags
76 -- targets <- mapM (\s -> guessTarget s Nothing) hsfiles
77 -- guessTarget would be more compatible with ghc -M
78 filedata <- targetsAtOneGo session hsfiles
79 filedata <- case filedata of
80 Just fd -> return fd
81 Nothing -> targetsOneAtATime session hsfiles
82 emitTagsData modes filedata
83
84
85 ----------------------------------------------
86 ---------- ARGUMENT PROCESSING --------------
87
88 data Mode = ETags | CTags | BothTags | Append | Help deriving (Ord, Eq, Show)
89 -- ^Represents options passed to the program
90
91 -- | getMode takes a list of modes and extract the mode with the
92 -- highest precedence. These are as follows: Both, CTags, ETags
93 -- The default case is Both.
94 getMode :: [Mode] -> Mode
95 getMode [] = BothTags
96 getMode [x] = x
97 getMode (x:xs) = max x (getMode xs)
98
99
100 splitArgs :: [String] -> ([String], [String], Bool)
101 -- ^Pull out arguments between -- for GHC
102 splitArgs args = split [] [] False args
103 where split ghc' tags' unbal ("--" : args) = split tags' ghc' (not unbal) args
104 split ghc' tags' unbal (arg : args) = split ghc' (arg:tags') unbal args
105 split ghc' tags' unbal [] = (reverse ghc', reverse tags', unbal)
106
107 options :: [OptDescr Mode]
108 -- supports getopt
109 options = [ Option "c" ["ctags"]
110 (NoArg CTags) "generate CTAGS file (ctags)"
111 , Option "e" ["etags"]
112 (NoArg ETags) "generate ETAGS file (etags)"
113 , Option "b" ["both"]
114 (NoArg BothTags) ("generate both CTAGS and ETAGS")
115 , Option "a" ["append"]
116 (NoArg Append) ("append to existing CTAGS and/or ETAGS file(s)")
117 , Option "h" ["help"] (NoArg Help) "This help"
118 ]
119
120
121 ----------------------------------------------------------------
122 --- LOADING HASKELL SOURCE
123 --- (these bits actually run the compiler and produce abstract syntax)
124
125 safeLoad :: Session -> LoadHowMuch -> IO SuccessFlag
126 -- like GHC.load, but does not stop process on exception
127 safeLoad session mode = do
128 dflags <- getSessionDynFlags session
129 handle (\exception -> return Failed ) $
130 handleDyn (\dyn -> do printBagOfErrors dflags (unitBag dyn)
131 return Failed) $ load session mode
132
133
134 targetsAtOneGo :: Session -> [FileName] -> IO (Maybe [FileData])
135 -- load a list of targets
136 targetsAtOneGo session hsfiles = do
137 let targets = map fileTarget hsfiles
138 setTargets session targets
139 print $ "trying " ++ targetInfo hsfiles
140 success <- safeLoad session LoadAllTargets --- bring module graph up to date
141 case success of
142 Failed -> return Nothing
143 Succeeded -> do
144 print $ "loaded " ++ targetInfo hsfiles
145 graph <- getModuleGraph session
146 print "got modules graph"
147 fd <- graphData session graph
148 return $ Just fd
149
150 where targetInfo [hs] = "target " ++ hs
151 targetInfo hss = show (length hss) ++ " targets at one go"
152
153 targetsOneAtATime :: Session -> [FileName] -> IO ([FileData])
154 -- load a list of targets, one at a time (more resilient to errors)
155 targetsOneAtATime session hsfiles = do
156 print "trying targets one by one"
157 results <- mapM (targetsAtOneGo session) [[f] | f <- hsfiles]
158 return $ List.concat $ catMaybes results
159
160 fileTarget :: FileName -> Target
161 fileTarget filename = Target (TargetFile filename Nothing) Nothing
162
163 ---------------------------------------------------------------
164 ----- CRAWLING ABSTRACT SYNTAX TO SNAFFLE THE DEFINITIONS -----
165
166 graphData :: Session -> ModuleGraph -> IO [FileData]
167 graphData session graph =
168 mapM foundthings graph
169 where foundthings ms =
170 let filename = msHsFilePath ms
171 modname = moduleName $ ms_mod ms
172 in do mod <- checkModule session modname False
173 return $ maybe (FileData filename []) id $ do
174 m <- mod
175 s <- renamedSource m
176 return $ fileData filename modname s
177
178 fileData :: FileName -> ModuleName -> RenamedSource -> FileData
179 fileData filename modname (group, _imports, _lie, _doc, _haddock) =
180 -- lie is related to type checking and so is irrelevant
181 -- imports contains import declarations and no definitions
182 -- doc and haddock seem haddock-related; let's hope to ignore them
183 FileData filename (boundValues modname group)
184
185 boundValues :: ModuleName -> HsGroup Name -> [FoundThing]
186 -- ^Finds all the top-level definitions in a module
187 boundValues mod group =
188 let vals = case hs_valds group of
189 ValBindsOut nest sigs ->
190 [ x | (_rec, binds) <- nest, bind <- bagToList binds,
191 x <- boundThings mod bind ]
192 tys = concat $ map tyBound (hs_tyclds group)
193 where tyBound ltcd = case unLoc ltcd of
194 ForeignType { tcdLName = n } -> [found n]
195 TyData { tcdLName = tycon, tcdCons = cons } ->
196 dataNames tycon cons
197 TySynonym { tcdLName = n } -> [found n]
198 ClassDecl { tcdLName = n } -> [found n]
199 fors = concat $ map forBound (hs_fords group)
200 where forBound lford = case unLoc lford of
201 ForeignImport n _ _ -> [found n]
202 ForeignExport { } -> []
203 in vals ++ tys ++ fors
204 where dataNames tycon cons = found tycon : map conName cons
205 conName td = found $ con_name $ unLoc td
206 found = foundOfLName mod
207
208 startOfLocated :: Located a -> SrcLoc
209 startOfLocated lHs = srcSpanStart $ getLoc lHs
210
211 foundOfLName :: ModuleName -> Located Name -> FoundThing
212 foundOfLName mod id = FoundThing mod (getOccString $ unLoc id) (startOfLocated id)
213
214 boundThings :: ModuleName -> LHsBind Name -> [FoundThing]
215 boundThings modname lbinding =
216 case unLoc lbinding of
217 FunBind { fun_id = id } -> [thing id]
218 PatBind { pat_lhs = lhs } -> patThings lhs []
219 VarBind { var_id = id } -> [FoundThing modname (getOccString id) (startOfLocated lbinding)]
220 AbsBinds { } -> [] -- nothing interesting in a type abstraction
221 where thing = foundOfLName modname
222 patThings lpat tl =
223 let loc = startOfLocated lpat
224 lid id = FoundThing modname (getOccString id) loc
225 in case unLoc lpat of
226 WildPat _ -> tl
227 VarPat name -> lid name : tl
228 VarPatOut name _ -> lid name : tl -- XXX need help here
229 LazyPat p -> patThings p tl
230 AsPat id p -> patThings p (thing id : tl)
231 ParPat p -> patThings p tl
232 BangPat p -> patThings p tl
233 ListPat ps _ -> foldr patThings tl ps
234 TuplePat ps _ _ -> foldr patThings tl ps
235 PArrPat ps _ -> foldr patThings tl ps
236 ConPatIn _ conargs -> conArgs conargs tl
237 ConPatOut _ _ _ _ conargs _ -> conArgs conargs tl
238 LitPat _ -> tl
239 NPat _ _ _ _ -> tl -- form of literal pattern?
240 NPlusKPat id _ _ _ -> thing id : tl
241 TypePat _ -> tl -- XXX need help here
242 SigPatIn p _ -> patThings p tl
243 SigPatOut p _ -> patThings p tl
244 conArgs (PrefixCon ps) tl = foldr patThings tl ps
245 conArgs (RecCon (HsRecFields { rec_flds = flds })) tl
246 = foldr (\f tl -> patThings (hsRecFieldArg f) tl) tl flds
247 conArgs (InfixCon p1 p2) tl = patThings p1 $ patThings p2 tl
248
249
250 -----------------------------------------------
251 ------- WRITING THE DATA TO TAGS FILES --------
252
253 emitTagsData :: [Mode] -> [FileData] -> IO ()
254 emitTagsData modes filedata = do
255 let mode = getMode (Append `delete` modes)
256 let openFileMode = if elem Append modes
257 then AppendMode
258 else WriteMode
259 if mode == BothTags || mode == CTags
260 then do
261 ctagsfile <- openFile "tags" openFileMode
262 writectagsfile ctagsfile filedata
263 hClose ctagsfile
264 else return ()
265 if mode == BothTags || mode == ETags
266 then do
267 etagsfile <- openFile "TAGS" openFileMode
268 writeetagsfile etagsfile filedata
269 hClose etagsfile
270 else return ()
271
272
273 -- stuff for dealing with ctags output format
274
275 writectagsfile :: Handle -> [FileData] -> IO ()
276 writectagsfile ctagsfile filedata = do
277 let things = concat $ map getfoundthings filedata
278 mapM_ (\x -> hPutStrLn ctagsfile $ dumpthing False x) things
279 mapM_ (\x -> hPutStrLn ctagsfile $ dumpthing True x) things
280
281 getfoundthings :: FileData -> [FoundThing]
282 getfoundthings (FileData filename things) = things
283
284 dumpthing :: Bool -> FoundThing -> String
285 dumpthing showmod (FoundThing modname name loc) =
286 fullname ++ "\t" ++ filename ++ "\t" ++ (show $ line + 1)
287 where line = srcLocLine loc
288 filename = unpackFS $ srcLocFile loc
289 fullname = if showmod then moduleNameString modname ++ "." ++ name
290 else name
291
292 -- stuff for dealing with etags output format
293
294 writeetagsfile :: Handle -> [FileData] -> IO ()
295 writeetagsfile etagsfile filedata = do
296 mapM_ (\x -> hPutStr etagsfile $ e_dumpfiledata x) filedata
297
298 e_dumpfiledata :: FileData -> String
299 e_dumpfiledata (FileData filename things) =
300 "\x0c\n" ++ filename ++ "," ++ (show thingslength) ++ "\n" ++ thingsdump
301 where
302 thingsdump = concat $ map e_dumpthing things
303 thingslength = length thingsdump
304
305 e_dumpthing :: FoundThing -> String
306 e_dumpthing (FoundThing modname name loc) =
307 tagline name ++ tagline (moduleNameString modname ++ "." ++ name)
308 where tagline n = n ++ "\x7f" ++ (show line) ++ "," ++ (show $ line+1) ++ "\n"
309 line = srcLocLine loc