ghc-mod/src/GHCModi.hs

273 lines
8.9 KiB
Haskell
Raw Normal View History

2014-04-07 02:06:17 +00:00
{-# LANGUAGE DeriveDataTypeable #-}
2014-03-25 02:14:25 +00:00
2014-03-27 01:34:43 +00:00
-- Commands:
-- check <file>
-- find <symbol>
2014-04-19 11:48:44 +00:00
-- info <file> <expr>
-- type <file> <line> <column>
2014-03-28 04:44:44 +00:00
-- lint [hlint options] <file>
-- the format of hlint options is [String] because they may contain
2014-04-21 08:33:53 +00:00
-- spaces and also <file> may contain spaces.
2014-04-21 07:30:31 +00:00
-- boot
-- browse [<package>:]<module>
2014-03-27 01:34:43 +00:00
--
-- Session separators:
-- OK -- success
-- NG -- failure
2014-03-19 01:23:47 +00:00
module Main where
2014-04-25 13:03:09 +00:00
import Config (cProjectVersion)
2014-03-25 03:28:39 +00:00
import Control.Applicative ((<$>))
2014-03-27 05:55:24 +00:00
import Control.Concurrent (forkIO, MVar, newEmptyMVar, putMVar, readMVar)
2014-04-23 05:44:05 +00:00
import Control.Exception (SomeException(..), Exception)
2014-03-27 05:55:24 +00:00
import qualified Control.Exception as E
2014-03-24 08:32:06 +00:00
import Control.Monad (when, void)
2014-03-27 06:08:07 +00:00
import CoreMonad (liftIO)
2014-04-24 08:02:50 +00:00
import Data.List (find)
2014-03-28 04:53:58 +00:00
import Data.Maybe (fromMaybe)
2014-03-25 02:14:25 +00:00
import Data.Set (Set)
import qualified Data.Set as S
2014-04-11 07:07:36 +00:00
import Data.Typeable (Typeable)
2014-04-07 02:06:17 +00:00
import Data.Version (showVersion)
2014-03-27 05:55:24 +00:00
import qualified Exception as GE
import GHC (Ghc, LoadHowMuch(LoadAllTargets), TargetId(TargetFile))
import qualified GHC as G
2014-03-25 02:34:58 +00:00
import HscTypes (SourceError)
2014-03-19 01:23:47 +00:00
import Language.Haskell.GhcMod
import Language.Haskell.GhcMod.Ghc
2014-03-19 01:23:47 +00:00
import Language.Haskell.GhcMod.Internal
2014-04-07 02:06:17 +00:00
import Paths_ghc_mod
import System.Console.GetOpt
2014-04-03 07:18:35 +00:00
import System.Directory (setCurrentDirectory)
2014-04-07 02:06:17 +00:00
import System.Environment (getArgs)
2014-03-24 08:32:06 +00:00
import System.IO (hFlush,stdout)
2014-03-19 01:23:47 +00:00
2014-04-21 07:30:31 +00:00
import Boot
2014-03-25 02:14:25 +00:00
----------------------------------------------------------------
2014-04-21 05:04:58 +00:00
type Logger = IO String
2014-03-25 02:14:25 +00:00
----------------------------------------------------------------
2014-04-25 13:03:09 +00:00
progVersion :: String
progVersion = "ghc-modi version " ++ showVersion version ++ " compiled by GHC " ++ cProjectVersion ++ "\n"
2014-04-07 02:06:17 +00:00
argspec :: [OptDescr (Options -> Options)]
argspec = [ Option "b" ["boundary"]
(ReqArg (\s opts -> opts { lineSeparator = LineSeparator s }) "sep")
"specify line separator (default is Nul string)"
2014-04-18 08:28:12 +00:00
, Option "l" ["tolisp"]
(NoArg (\opts -> opts { outputStyle = LispStyle }))
"print as a list of Lisp"
2014-04-10 13:21:30 +00:00
, Option "g" []
(ReqArg (\s opts -> opts { ghcOpts = s : ghcOpts opts }) "flag") "specify a ghc flag"
2014-04-07 02:06:17 +00:00
]
usage :: String
2014-04-25 13:03:09 +00:00
usage = progVersion
2014-04-07 02:06:17 +00:00
++ "Usage:\n"
2014-04-18 08:28:12 +00:00
++ "\t ghc-modi [-l] [-b sep] [-g flag]\n"
2014-04-25 05:09:32 +00:00
++ "\t ghc-modi version\n"
2014-04-07 02:06:17 +00:00
++ "\t ghc-modi help\n"
parseArgs :: [OptDescr (Options -> Options)] -> [String] -> (Options, [String])
parseArgs spec argv
= case getOpt Permute spec argv of
(o,n,[] ) -> (foldr id defaultOptions o, n)
2014-04-23 05:44:05 +00:00
(_,_,errs) -> E.throw (CmdArg errs)
2014-04-07 02:06:17 +00:00
----------------------------------------------------------------
data GHCModiError = CmdArg [String]
deriving (Show, Typeable)
2014-04-23 05:44:05 +00:00
instance Exception GHCModiError
2014-04-07 02:06:17 +00:00
----------------------------------------------------------------
2014-03-25 02:34:58 +00:00
-- Running two GHC monad threads disables the handling of
-- C-c since installSignalHandlers is called twice, sigh.
2014-03-19 01:23:47 +00:00
main :: IO ()
2014-04-23 05:44:05 +00:00
main = E.handle cmdHandler $
2014-04-07 02:06:17 +00:00
go =<< parseArgs argspec <$> getArgs
2014-03-19 01:23:47 +00:00
where
2014-04-23 05:44:05 +00:00
cmdHandler (CmdArg _) = putStr $ usageInfo usage argspec
go (_,"help":_) = putStr $ usageInfo usage argspec
2014-04-25 13:03:09 +00:00
go (_,"version":_) = putStr progVersion
2014-04-23 05:44:05 +00:00
go (opt,_) = E.handle someHandler $ do
2014-04-07 02:06:17 +00:00
cradle0 <- findCradle
let rootdir = cradleRootDir cradle0
cradle = cradle0 { cradleCurrentDir = rootdir }
setCurrentDirectory rootdir
mvar <- liftIO newEmptyMVar
mlibdir <- getSystemLibDir
void $ forkIO $ setupDB cradle mlibdir opt mvar
2014-04-21 05:04:58 +00:00
run cradle mlibdir opt $ loop opt S.empty mvar
2014-04-23 05:44:05 +00:00
where
2014-04-24 03:53:14 +00:00
-- this is just in case.
-- If an error is caught here, it is a bug of GhcMod library.
2014-04-25 02:08:29 +00:00
someHandler (SomeException e) = do
putStrLn $ "NG " ++ replace (show e)
replace :: String -> String
replace [] = []
replace ('\n':xs) = ';' : replace xs
replace (x:xs) = x : replace xs
2014-03-20 08:40:06 +00:00
2014-03-25 02:14:25 +00:00
----------------------------------------------------------------
run :: Cradle -> Maybe FilePath -> Options -> Ghc a -> IO a
2014-03-27 05:55:24 +00:00
run cradle mlibdir opt body = G.runGhc mlibdir $ do
initializeFlagsWithCradle opt cradle ["-Wall"]
2014-03-27 05:55:24 +00:00
dflags <- G.getSessionDynFlags
G.defaultCleanupHandler dflags body
2014-03-25 02:14:25 +00:00
----------------------------------------------------------------
2014-04-24 08:02:50 +00:00
setupDB :: Cradle -> Maybe FilePath -> Options -> MVar SymMdlDb -> IO ()
setupDB cradle mlibdir opt mvar = E.handle handler $ do
db <- run cradle mlibdir opt getSymMdlDb
2014-04-24 08:02:50 +00:00
putMVar mvar db
2014-03-25 02:14:25 +00:00
where
2014-04-24 08:02:50 +00:00
handler (SomeException _) = return () -- fixme: put emptyDb?
2014-03-25 02:14:25 +00:00
----------------------------------------------------------------
loop :: Options -> Set FilePath -> MVar SymMdlDb -> Ghc ()
loop opt set mvar = do
2014-03-28 04:53:58 +00:00
cmdArg <- liftIO getLine
2014-03-24 08:32:06 +00:00
let (cmd,arg') = break (== ' ') cmdArg
arg = dropWhile (== ' ') arg'
2014-04-19 12:23:01 +00:00
(ret,ok,set') <- case cmd of
"check" -> checkStx opt set arg
2014-04-21 08:33:53 +00:00
"find" -> findSym opt set arg mvar
"lint" -> lintStx opt set arg
"info" -> showInfo opt set arg
"type" -> showType opt set arg
2014-04-21 08:33:53 +00:00
"boot" -> bootIt opt set
"browse" -> browseIt opt set arg
2014-04-25 02:08:29 +00:00
"quit" -> return ("quit", False, set)
"" -> return ("quit", False, set)
_ -> return ([], True, set)
if ok then do
liftIO $ putStr ret
liftIO $ putStrLn "OK"
else do
liftIO $ putStrLn $ "NG " ++ replace ret
2014-03-24 08:32:06 +00:00
liftIO $ hFlush stdout
when ok $ loop opt set' mvar
2014-03-24 08:32:06 +00:00
2014-03-25 02:14:25 +00:00
----------------------------------------------------------------
2014-04-19 12:23:01 +00:00
checkStx :: Options
-> Set FilePath
2014-03-24 08:32:06 +00:00
-> FilePath
2014-04-19 12:23:01 +00:00
-> Ghc (String, Bool, Set FilePath)
checkStx opt set file = do
2014-03-19 01:23:47 +00:00
let add = not $ S.member file set
2014-03-27 05:55:24 +00:00
GE.ghandle handler $ do
2014-03-25 03:28:39 +00:00
mdel <- removeMainTarget
ret <- withLogger opt $ do
when add $ addTargetFiles [file]
void $ G.load LoadAllTargets
2014-03-25 03:28:39 +00:00
let set1 = if add then S.insert file set else set
set2 = case mdel of
Nothing -> set1
Just delfl -> S.delete delfl set1
2014-04-19 12:23:01 +00:00
return (ret, True, set2)
2014-03-19 01:23:47 +00:00
where
2014-04-19 12:23:01 +00:00
handler :: SourceError -> Ghc (String, Bool, Set FilePath)
2014-03-19 01:23:47 +00:00
handler err = do
2014-04-21 05:04:58 +00:00
ret <- handleErrMsg opt err
2014-04-25 02:08:29 +00:00
return (ret, True, set)
2014-03-25 03:28:39 +00:00
removeMainTarget = do
2014-03-27 05:55:24 +00:00
mx <- find isMain <$> G.getModuleGraph
2014-03-25 03:28:39 +00:00
case mx of
Nothing -> return Nothing
Just x -> do
let mmainfile = G.ml_hs_file (G.ms_location x)
-- G.ms_hspp_file x is a temporary file with CPP.
-- this is a just fake.
mainfile = fromMaybe (G.ms_hspp_file x) mmainfile
2014-03-25 03:28:39 +00:00
if mainfile == file then
return Nothing
else do
let target = TargetFile mainfile Nothing
2014-03-27 05:55:24 +00:00
G.removeTarget target
2014-03-25 03:28:39 +00:00
return $ Just mainfile
2014-03-27 05:55:24 +00:00
isMain m = G.moduleNameString (G.moduleName (G.ms_mod m)) == "Main"
2014-03-25 02:14:25 +00:00
2014-04-24 08:02:50 +00:00
findSym :: Options -> Set FilePath -> String -> MVar SymMdlDb
2014-04-19 12:23:01 +00:00
-> Ghc (String, Bool, Set FilePath)
2014-04-21 05:04:58 +00:00
findSym opt set sym mvar = do
2014-03-25 02:14:25 +00:00
db <- liftIO $ readMVar mvar
2014-04-24 08:02:50 +00:00
let ret = lookupSym opt sym db
2014-03-25 02:14:25 +00:00
return (ret, True, set)
2014-03-27 01:34:43 +00:00
2014-04-21 05:04:58 +00:00
lintStx :: Options -> Set FilePath -> FilePath
2014-04-19 12:23:01 +00:00
-> Ghc (String, Bool, Set FilePath)
2014-04-25 02:08:29 +00:00
lintStx opt set optFile = liftIO $ do
2014-04-21 05:04:58 +00:00
ret <-lintSyntax opt' file
2014-04-19 12:23:01 +00:00
return (ret, True, set)
2014-03-27 01:34:43 +00:00
where
2014-04-19 12:23:01 +00:00
(opts,file) = parseLintOptions optFile
hopts = if opts == "" then [] else read opts
2014-04-21 05:04:58 +00:00
opt' = opt { hlintOpts = hopts }
2014-03-28 05:41:01 +00:00
-- |
-- >>> parseLintOptions "[\"--ignore=Use camelCase\", \"--ignore=Eta reduce\"] file name"
-- (["--ignore=Use camelCase", "--ignore=Eta reduce"], "file name")
-- >>> parseLintOptions "file name"
-- ([], "file name")
parseLintOptions :: String -> (String, String)
parseLintOptions optFile = case brk (== ']') (dropWhile (/= '[') optFile) of
("","") -> ([], optFile)
2014-03-28 06:03:41 +00:00
(opt',file') -> (opt', dropWhile (== ' ') file')
2014-03-28 05:41:01 +00:00
where
brk _ [] = ([],[])
brk p (x:xs')
| p x = ([x],xs')
| otherwise = let (ys,zs) = brk p xs' in (x:ys,zs)
2014-04-11 07:07:36 +00:00
2014-04-21 07:30:31 +00:00
----------------------------------------------------------------
2014-04-19 12:23:01 +00:00
showInfo :: Options
-> Set FilePath
2014-04-11 07:07:36 +00:00
-> FilePath
2014-04-19 12:23:01 +00:00
-> Ghc (String, Bool, Set FilePath)
showInfo opt set fileArg = do
2014-04-11 07:07:36 +00:00
let [file, expr] = words fileArg
(_, _, set') <- checkStx opt set file
2014-04-21 00:45:41 +00:00
ret <- info opt file expr
return (ret, True, set')
2014-04-11 07:07:36 +00:00
showType :: Options
-> Set FilePath
-> FilePath
2014-04-19 12:23:01 +00:00
-> Ghc (String, Bool, Set FilePath)
showType opt set fileArg = do
2014-04-11 07:07:36 +00:00
let [file, line, column] = words fileArg
(_, _, set') <- checkStx opt set file
2014-04-21 05:04:58 +00:00
ret <- types opt file (read line) (read column)
2014-04-19 12:23:01 +00:00
return (ret, True, set')
2014-04-21 07:30:31 +00:00
----------------------------------------------------------------
bootIt :: Options
-> Set FilePath
-> Ghc (String, Bool, Set FilePath)
bootIt opt set = do
ret <- boot' opt
return (ret, True, set)
2014-04-21 08:33:53 +00:00
browseIt :: Options
-> Set FilePath
-> ModuleString
-> Ghc (String, Bool, Set FilePath)
browseIt opt set mdl = do
ret <- browse opt mdl
return (ret, True, set)