packages feed

ghcid-0.1.1: src/GHCi.hs

module GHCi(
    ghci,
    parseShowModules,
    Severity(..), Load(..), isMessage, parseLoad
    ) where

import System.IO
import System.Process
import Control.Concurrent
import Control.Monad
import Data.Char
import Data.List
import Data.Maybe
import System.FilePath
import System.Console.CmdArgs.Verbosity
import Util


---------------------------------------------------------------------
-- IO INTERACTION WITH GHCI

ghci :: String -> IO (String -> IO [String])
ghci cmd = do
    (Just inp, Just out, Just err, pid) <-
        createProcess (shell cmd){std_in=CreatePipe, std_out=CreatePipe, std_err=CreatePipe}
    hSetBuffering out LineBuffering
    hSetBuffering err LineBuffering
    hSetBuffering inp LineBuffering

    lock <- newMVar () -- ensure only one person talks to ghci at a time
    outs <- newMVar [] -- result that is buffering up
    errs <- newMVar []
    flush <- newEmptyMVar -- result is moved to push once we see separate

    let prefix = "#~GHCID-PREFIX~#"
    let separate = "#~GHCID-SEPARATE~#"
    hPutStrLn inp $ ":set prompt " ++ prefix

    forM_ [(out,outs,"GHCOUT"),(err,errs,"GHCERR")] $ \(h,buf,strm) -> forkIO $ forever $ do
        s <- hGetLine h
        whenLoud $ outStrLn $ "%" ++ strm ++ ": " ++ s
        if s == separate then do
            outs <- modifyMVar outs $ \s -> return ([], reverse s)
            errs <- modifyMVar errs $ \s -> return ([], reverse s)
            putMVar flush $ outs ++ errs
        else
            modifyMVar_ buf $ return . (fromMaybe s (stripPrefix prefix s):)

    return $ \s -> withMVar lock $ const $ do
        whenLoud $ outStrLn $ "%GHCINP: " ++ s
        hPutStrLn inp $ s ++ "\nputStrLn \"\\n" ++ separate ++ "\""
        res <- takeMVar flush
        return res


---------------------------------------------------------------------
-- PARSING THE OUTPUT

-- Main             ( Main.hs, interpreted )
-- GHCi             ( GHCi.hs, interpreted )
parseShowModules :: [String] -> [(String, FilePath)]
parseShowModules xs =
    [ (takeWhile (not . isSpace) $ dropWhile isSpace a, takeWhile (/= ',') b)
    | x <- xs, (a,'(':' ':b) <- [break (== '(') x]]

data Severity = Warning | Error deriving (Show,Eq)

data Load
    = Loading {loadModule :: String, loadFile :: FilePath}
    | Message
        {loadSeverity :: Severity
        ,loadFile :: FilePath
        ,loadFilePos :: (Int,Int)
        ,loadMessage :: [String]
        }
      deriving Show

isMessage Message{} = True; isMessage _ = False

-- [1 of 2] Compiling GHCi             ( GHCi.hs, interpreted )
-- GHCi.hs:70:1: Parse error: naked expression at top level
-- GHCi.hs:72:13:
--     No instance for (Num ([String] -> [String]))
--       arising from the literal `1'
--     Possible fix:
--       add an instance declaration for (Num ([String] -> [String]))
--     In the expression: 1
--     In an equation for `parseLoad': parseLoad = 1
-- GHCi.hs:81:1: Warning: Defined but not used: `foo'
parseLoad :: [String] -> [Load]
parseLoad (('[':xs):rest) =
    map (uncurry Loading) (parseShowModules [drop 11 $ dropWhile (/= ']') xs]) ++
    parseLoad rest
parseLoad (x:xs)
    | not $ " " `isPrefixOf` x
    , (file,':':rest) <- break (== ':') x
    , takeExtension file `elem` [".hs",".lhs"]
    , (pos,rest) <- span (\x -> x == ':' || isDigit x) rest
    , [p1,p2] <- map read $ words $ map (\x -> if x == ':' then ' ' else x) pos 
    , (msg,xs) <- span (isPrefixOf " ") xs
    , rest <- dropWhile isSpace rest
    , sev <- if "Warning:" `isPrefixOf` rest then Warning else Error
    = Message sev file (p1,p2) (x:msg) : parseLoad xs
parseLoad (x:xs) = parseLoad xs
parseLoad [] = []