module Language.Haskell.Refact.Utils.GhcBugWorkArounds
(
getRichTokenStreamWA
) where
import qualified Bag as GHC
import qualified DynFlags as GHC
import qualified ErrUtils as GHC
import qualified FastString as GHC
import qualified GHC as GHC
import qualified HscTypes as GHC
import qualified Lexer as GHC
import qualified MonadUtils as GHC
import qualified Outputable as GHC
import qualified SrcLoc as GHC
import qualified StringBuffer as GHC
import Control.Exception
import Data.IORef
import System.Directory
import System.FilePath
import qualified Data.Map as Map
import qualified Data.Set as Set
getRichTokenStreamWA :: GHC.GhcMonad m => GHC.Module -> m [(GHC.Located GHC.Token, String)]
getRichTokenStreamWA modu = do
(sourceFile, source, flags) <- getModuleSourceAndFlags modu
let startLoc = GHC.mkRealSrcLoc (GHC.mkFastString sourceFile) 1 1
case GHC.lexTokenStream source startLoc flags of
GHC.POk _ ts -> return $ GHC.addSourceToTokens startLoc source ts
GHC.PFailed _span _err ->
do
strSrcBuf <- getPreprocessedSrc sourceFile
case GHC.lexTokenStream strSrcBuf startLoc flags of
GHC.POk _ ts ->
do directiveToks <- GHC.liftIO $ getPreprocessorAsComments sourceFile
nonDirectiveToks <- tokeniseOriginalSrc startLoc flags source
let toks = GHC.addSourceToTokens startLoc source ts
return $ combineTokens directiveToks nonDirectiveToks toks
GHC.PFailed sspan err -> parseError flags sspan err
combineTokens ::
[(GHC.Located GHC.Token, String)]
-> [(GHC.Located GHC.Token, String)]
-> [(GHC.Located GHC.Token, String)]
-> [(GHC.Located GHC.Token, String)]
combineTokens directiveToks origSrcToks postCppToks = toks
where
locFn (GHC.L l1 _,_) (GHC.L l2 _,_) = compare l1 l2
m1Toks = mergeBy locFn postCppToks directiveToks
origSpans = map (\(GHC.L l _,_) -> l) origSrcToks
m1Spans = map (\(GHC.L l _,_) -> l) m1Toks
missingSpans = (Set.fromList origSpans) Set.\\ (Set.fromList m1Spans)
missingToks = filter (\(GHC.L l _,_) -> Set.member l missingSpans) origSrcToks
missingAsComments = map mkCommentTok missingToks
where
mkCommentTok :: (GHC.Located GHC.Token,String) -> (GHC.Located GHC.Token,String)
mkCommentTok (GHC.L l _,s) = (GHC.L l (GHC.ITlineComment s),s)
toks = mergeBy locFn m1Toks missingAsComments
tokeniseOriginalSrc ::
GHC.GhcMonad m
=> GHC.RealSrcLoc -> GHC.DynFlags -> GHC.StringBuffer
-> m [(GHC.Located GHC.Token, String)]
tokeniseOriginalSrc startLoc flags buf = do
let src = stripPreprocessorDirectives buf
case GHC.lexTokenStream src startLoc flags of
GHC.POk _ ts -> return $ GHC.addSourceToTokens startLoc src ts
GHC.PFailed sspan err -> parseError flags sspan err
stripPreprocessorDirectives :: GHC.StringBuffer -> GHC.StringBuffer
stripPreprocessorDirectives buf = buf'
where
srcByLine = lines $ sbufToString buf
noDirectivesLines = map (\line -> if line /= [] && head line == '#' then "" else line) srcByLine
buf' = GHC.stringToStringBuffer $ unlines noDirectivesLines
sbufToString :: GHC.StringBuffer -> String
sbufToString sb@(GHC.StringBuffer _buf len _cur) = GHC.lexemeToString sb len
getPreprocessedSrc ::
GHC.GhcMonad m => FilePath -> m GHC.StringBuffer
getPreprocessedSrc srcFile = do
df <- GHC.getSessionDynFlags
d <- GHC.liftIO $ getTempDir df
fileList <- GHC.liftIO $ getDirectoryContents d
let suffix = "hscpp"
let cppFiles = filter (\f -> getSuffix f == suffix) fileList
origNames <- GHC.liftIO $ mapM getOriginalFile $ map (\f -> d </> f) cppFiles
let tmpFile = head $ filter (\(o,_) -> o == srcFile) origNames
buf <- GHC.liftIO $ GHC.hGetStringBuffer $ snd tmpFile
return buf
getSuffix :: FilePath -> String
getSuffix fname = reverse $ fst $ break (== '.') $ reverse fname
getOriginalFile :: FilePath -> IO (FilePath,FilePath)
getOriginalFile fname = do
fcontents <- readFile fname
let firstLine = head $ lines fcontents
let (_,originalFname) = break (== '"') firstLine
return $ (tail $ init $ originalFname,fname)
getPreprocessorAsComments :: FilePath -> IO [(GHC.Located GHC.Token, String)]
getPreprocessorAsComments srcFile = do
fcontents <- readFile srcFile
let directives = filter (\(_lineNum,line) -> line /= [] && head line == '#') $ zip [1..] $ lines fcontents
let mkTok (lineNum,line) = (GHC.L l (GHC.ITlineComment line),line)
where
start = GHC.mkSrcLoc (GHC.mkFastString srcFile) lineNum 1
end = GHC.mkSrcLoc (GHC.mkFastString srcFile) lineNum (length line)
l = GHC.mkSrcSpan start end
let toks = map mkTok directives
return toks
#if __GLASGOW_HASKELL__ > 704
parseError :: GHC.DynFlags -> GHC.SrcSpan -> GHC.MsgDoc -> m b
parseError dflags sspan err = do
throw $ GHC.mkSrcErr (GHC.unitBag $ GHC.mkPlainErrMsg dflags sspan err)
#else
parseError :: GHC.GhcMonad m => GHC.DynFlags -> GHC.SrcSpan -> GHC.Message -> m b
parseError _dflags sspan err = do
throw $ GHC.mkSrcErr (GHC.unitBag $ GHC.mkPlainErrMsg sspan err)
#endif
getModuleSourceAndFlags :: GHC.GhcMonad m => GHC.Module -> m (String, GHC.StringBuffer, GHC.DynFlags)
getModuleSourceAndFlags modu = do
m <- GHC.getModSummary (GHC.moduleName modu)
case GHC.ml_hs_file $ GHC.ms_location m of
Nothing ->
#if __GLASGOW_HASKELL__ > 704
do dflags <- GHC.getDynFlags
GHC.liftIO $ throwIO $ GHC.mkApiErr dflags (GHC.text "No source available for module " GHC.<+> GHC.ppr modu)
#else
GHC.liftIO $ throwIO $ GHC.mkApiErr (GHC.text "No source available for module " GHC.<+> GHC.ppr modu)
#endif
Just sourceFile -> do
source <- GHC.liftIO $ GHC.hGetStringBuffer sourceFile
return (sourceFile, source, GHC.ms_hspp_opts m)
getTempDir :: GHC.DynFlags -> IO FilePath
getTempDir dflags
= do let ref = GHC.dirsToClean dflags
tmp_dir = GHC.tmpDir dflags
mapping <- readIORef ref
case Map.lookup tmp_dir mapping of
Nothing -> error "should already be a tmpDir"
Just d -> return d
mergeBy :: (a -> a -> Ordering) -> [a] -> [a] -> [a]
mergeBy _cmp [] ys = ys
mergeBy _cmp xs [] = xs
mergeBy cmp (allx@(x:xs)) (ally@(y:ys))
| (x `cmp` y) <= EQ = x : mergeBy cmp xs ally
| otherwise = y : mergeBy cmp allx ys