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
|
2014-04-24 02:26:30 +00:00
|
|
|
-- browse [<package>:]<module>
|
2014-04-28 00:29:24 +00:00
|
|
|
-- quit
|
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 GHC as G
|
2014-03-19 01:23:47 +00:00
|
|
|
import Language.Haskell.GhcMod
|
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-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
|
2014-07-11 01:10:37 +00:00
|
|
|
-- c = cradle0 { cradleCurrentDir = rootdir } TODO: ?????
|
2014-04-07 02:06:17 +00:00
|
|
|
setCurrentDirectory rootdir
|
|
|
|
mvar <- liftIO newEmptyMVar
|
2014-07-17 03:25:10 +00:00
|
|
|
void $ forkIO $ setupDB mvar
|
2014-07-22 17:45:48 +00:00
|
|
|
(res, _) <- runGhcModT opt $ loop S.empty mvar
|
|
|
|
|
|
|
|
case res of
|
|
|
|
Right () -> return ()
|
|
|
|
Left e -> error $ show e
|
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
|
|
|
----------------------------------------------------------------
|
|
|
|
|
2014-07-17 03:25:10 +00:00
|
|
|
setupDB :: MVar SymbolDb -> IO ()
|
2014-07-17 05:04:28 +00:00
|
|
|
setupDB mvar = loadSymbolDb >>= putMVar mvar
|
2014-03-25 02:14:25 +00:00
|
|
|
|
|
|
|
----------------------------------------------------------------
|
|
|
|
|
2014-07-16 09:14:12 +00:00
|
|
|
loop :: IOish m => Set FilePath -> MVar SymbolDb -> GhcModT m ()
|
2014-07-11 01:10:37 +00:00
|
|
|
loop 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
|
2014-07-11 01:10:37 +00:00
|
|
|
"check" -> checkStx set arg
|
2014-05-14 16:05:40 +00:00
|
|
|
"find" -> findSym set arg mvar
|
2014-07-11 01:10:37 +00:00
|
|
|
"lint" -> lintStx set arg
|
2014-06-28 19:43:51 +00:00
|
|
|
"info" -> showInfo set arg
|
|
|
|
"type" -> showType set arg
|
|
|
|
"split" -> doSplit set arg
|
|
|
|
"sig" -> doSig set arg
|
2014-07-17 04:59:10 +00:00
|
|
|
"refine" -> doRefine set arg
|
2014-08-01 15:08:23 +00:00
|
|
|
"auto" -> doAuto set arg
|
2014-05-10 11:51:35 +00:00
|
|
|
"boot" -> bootIt set
|
2014-05-10 13:10:34 +00:00
|
|
|
"browse" -> browseIt 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
|
2014-07-11 01:10:37 +00:00
|
|
|
when ok $ loop set' mvar
|
2014-03-24 08:32:06 +00:00
|
|
|
|
2014-03-25 02:14:25 +00:00
|
|
|
----------------------------------------------------------------
|
|
|
|
|
2014-07-12 09:16:16 +00:00
|
|
|
checkStx :: IOish m
|
|
|
|
=> Set FilePath
|
2014-03-24 08:32:06 +00:00
|
|
|
-> FilePath
|
2014-07-12 09:16:16 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-07-11 01:10:37 +00:00
|
|
|
checkStx set file = do
|
2014-07-18 06:31:42 +00:00
|
|
|
set' <- newFileSet set file
|
2014-04-28 07:31:28 +00:00
|
|
|
let files = S.toList set'
|
2014-05-10 13:10:34 +00:00
|
|
|
eret <- check files
|
2014-04-28 04:52:28 +00:00
|
|
|
case eret of
|
|
|
|
Right ret -> return (ret, True, set')
|
|
|
|
Left ret -> return (ret, True, set) -- fxime: set
|
2014-04-26 12:46:11 +00:00
|
|
|
|
2014-07-18 06:31:42 +00:00
|
|
|
newFileSet :: IOish m => Set FilePath -> FilePath -> GhcModT m (Set FilePath)
|
2014-04-28 07:31:28 +00:00
|
|
|
newFileSet set file = do
|
|
|
|
let set1
|
|
|
|
| S.member file set = set
|
|
|
|
| otherwise = S.insert file set
|
|
|
|
mx <- isSameMainFile file <$> getModSummaryForMain
|
|
|
|
return $ case mx of
|
|
|
|
Nothing -> set1
|
|
|
|
Just mainfile -> S.delete mainfile set1
|
|
|
|
|
2014-07-18 06:31:42 +00:00
|
|
|
getModSummaryForMain :: IOish m => GhcModT m (Maybe G.ModSummary)
|
2014-04-28 07:31:28 +00:00
|
|
|
getModSummaryForMain = find isMain <$> G.getModuleGraph
|
2014-04-26 12:46:11 +00:00
|
|
|
where
|
2014-03-27 05:55:24 +00:00
|
|
|
isMain m = G.moduleNameString (G.moduleName (G.ms_mod m)) == "Main"
|
2014-04-28 07:31:28 +00:00
|
|
|
|
|
|
|
isSameMainFile :: FilePath -> (Maybe G.ModSummary) -> Maybe FilePath
|
|
|
|
isSameMainFile _ Nothing = Nothing
|
|
|
|
isSameMainFile file (Just x)
|
|
|
|
| mainfile == file = Nothing
|
|
|
|
| otherwise = Just mainfile
|
|
|
|
where
|
|
|
|
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 02:14:25 +00:00
|
|
|
|
2014-07-16 09:14:12 +00:00
|
|
|
findSym :: IOish m => Set FilePath -> String -> MVar SymbolDb
|
2014-07-12 09:16:16 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-05-14 16:05:40 +00:00
|
|
|
findSym set sym mvar = do
|
2014-03-25 02:14:25 +00:00
|
|
|
db <- liftIO $ readMVar mvar
|
2014-07-18 06:13:30 +00:00
|
|
|
ret <- lookupSymbol sym db
|
2014-03-25 02:14:25 +00:00
|
|
|
return (ret, True, set)
|
2014-03-27 01:34:43 +00:00
|
|
|
|
2014-07-12 09:16:16 +00:00
|
|
|
lintStx :: IOish m => Set FilePath
|
2014-07-11 01:10:37 +00:00
|
|
|
-> FilePath
|
2014-07-12 09:16:16 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-07-11 01:10:37 +00:00
|
|
|
lintStx set optFile = do
|
2014-07-18 06:31:42 +00:00
|
|
|
ret <- withOptions changeOpt $ lint 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-07-18 06:31:42 +00:00
|
|
|
changeOpt o = o { 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-07-12 09:16:16 +00:00
|
|
|
showInfo :: IOish m
|
|
|
|
=> Set FilePath
|
2014-04-11 07:07:36 +00:00
|
|
|
-> FilePath
|
2014-07-12 09:16:16 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-06-28 19:43:51 +00:00
|
|
|
showInfo set fileArg = do
|
2014-04-11 07:07:36 +00:00
|
|
|
let [file, expr] = words fileArg
|
2014-04-28 07:31:28 +00:00
|
|
|
set' <- newFileSet set file
|
2014-06-28 19:43:51 +00:00
|
|
|
ret <- info file expr
|
2014-04-21 00:45:41 +00:00
|
|
|
return (ret, True, set')
|
2014-04-11 07:07:36 +00:00
|
|
|
|
2014-07-12 09:16:16 +00:00
|
|
|
showType :: IOish m
|
|
|
|
=> Set FilePath
|
2014-04-11 07:07:36 +00:00
|
|
|
-> FilePath
|
2014-07-12 09:16:16 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-06-28 19:43:51 +00:00
|
|
|
showType set fileArg = do
|
2014-04-11 07:07:36 +00:00
|
|
|
let [file, line, column] = words fileArg
|
2014-04-28 07:31:28 +00:00
|
|
|
set' <- newFileSet set file
|
2014-06-28 19:43:51 +00:00
|
|
|
ret <- types 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
|
|
|
|
2014-07-12 09:16:16 +00:00
|
|
|
doSplit :: IOish m
|
|
|
|
=> Set FilePath
|
2014-06-08 10:33:13 +00:00
|
|
|
-> FilePath
|
2014-07-12 09:16:16 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-06-28 19:43:51 +00:00
|
|
|
doSplit set fileArg = do
|
2014-06-08 10:33:13 +00:00
|
|
|
let [file, line, column] = words fileArg
|
|
|
|
set' <- newFileSet set file
|
2014-06-28 19:43:51 +00:00
|
|
|
ret <- splits file (read line) (read column)
|
2014-06-08 10:33:13 +00:00
|
|
|
return (ret, True, set')
|
|
|
|
|
2014-07-12 09:16:16 +00:00
|
|
|
doSig :: IOish m
|
|
|
|
=> Set FilePath
|
2014-06-10 19:34:05 +00:00
|
|
|
-> FilePath
|
2014-07-12 09:16:16 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-06-28 19:43:51 +00:00
|
|
|
doSig set fileArg = do
|
2014-06-10 19:34:05 +00:00
|
|
|
let [file, line, column] = words fileArg
|
|
|
|
set' <- newFileSet set file
|
2014-06-28 19:43:51 +00:00
|
|
|
ret <- sig file (read line) (read column)
|
2014-06-10 19:34:05 +00:00
|
|
|
return (ret, True, set')
|
|
|
|
|
2014-07-17 04:59:10 +00:00
|
|
|
doRefine :: IOish m
|
|
|
|
=> Set FilePath
|
2014-07-16 16:20:52 +00:00
|
|
|
-> FilePath
|
2014-07-17 04:59:10 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-07-16 16:20:52 +00:00
|
|
|
doRefine set fileArg = do
|
|
|
|
let [file, line, column, expr] = words fileArg
|
|
|
|
set' <- newFileSet set file
|
2014-07-17 04:59:10 +00:00
|
|
|
ret <- refine file (read line) (read column) expr
|
2014-07-16 16:20:52 +00:00
|
|
|
return (ret, True, set')
|
|
|
|
|
2014-08-01 15:08:23 +00:00
|
|
|
doAuto :: IOish m
|
|
|
|
=> Set FilePath
|
|
|
|
-> FilePath
|
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
|
|
|
doAuto set fileArg = do
|
|
|
|
let [file, line, column] = words fileArg
|
|
|
|
set' <- newFileSet set file
|
|
|
|
ret <- auto file (read line) (read column)
|
|
|
|
return (ret, True, set')
|
|
|
|
|
2014-04-21 07:30:31 +00:00
|
|
|
----------------------------------------------------------------
|
|
|
|
|
2014-07-12 09:16:16 +00:00
|
|
|
bootIt :: IOish m
|
|
|
|
=> Set FilePath
|
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-05-10 11:51:35 +00:00
|
|
|
bootIt set = do
|
|
|
|
ret <- boot
|
2014-04-21 07:30:31 +00:00
|
|
|
return (ret, True, set)
|
2014-04-21 08:33:53 +00:00
|
|
|
|
2014-07-12 09:16:16 +00:00
|
|
|
browseIt :: IOish m
|
|
|
|
=> Set FilePath
|
2014-04-21 08:33:53 +00:00
|
|
|
-> ModuleString
|
2014-07-12 09:16:16 +00:00
|
|
|
-> GhcModT m (String, Bool, Set FilePath)
|
2014-05-10 13:10:34 +00:00
|
|
|
browseIt set mdl = do
|
|
|
|
ret <- browse mdl
|
2014-04-21 08:33:53 +00:00
|
|
|
return (ret, True, set)
|