1-- Copyright (c) 2019 The DAML Authors. All rights reserved.
2-- SPDX-License-Identifier: Apache-2.0
4module Development.IDE.Core.Preprocessor
8import Development.IDE.GHC.CPP
9import Development.IDE.GHC.Orphans()
10import Development.IDE.GHC.Compat
13import StringBuffer as SB
20import qualified HeaderInfo as Hdr
21import Development.IDE.Types.Diagnostics
22import Development.IDE.GHC.Error
23import SysTools (Option (..), runUnlit)
24import Control.Monad.Trans.Except
25import qualified GHC.LanguageExtensions as LangExt
29-- | Given a file and some contents, apply any necessary preprocessors,
30-- e.g. unlit/cpp. Return the resulting buffer and the DynFlags it implies.
31preprocessor :: GhcMonad m => FilePath -> Maybe StringBuffer -> ExceptT [FileDiagnostic] m (StringBuffer, DynFlags)
32preprocessor filename mbContents = do
34 (isOnDisk, contents) <-
35 if isLiterate filename then do
37 newcontent <- liftIO $ runLhs dflags filename mbContents
38 return (False, newcontent)
40 contents <- liftIO $ maybe (hGetStringBuffer filename) return mbContents
41 let isOnDisk = isNothing mbContents
42 return (isOnDisk, contents)
45 dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents
46 if not $ xopt LangExt.Cpp dflags then
47 return (contents, dflags)
49 contents <- liftIO $ runCpp dflags filename $ if isOnDisk then Nothing else Just contents
50 dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents
51 return (contents, dflags)
54isLiterate :: FilePath -> Bool
55isLiterate x = takeExtension x `elem` [".lhs",".lhs-boot"]
58-- | This reads the pragma information directly from the provided buffer.
59parsePragmasIntoDynFlags
63 -> m (Either [FileDiagnostic] DynFlags)
64parsePragmasIntoDynFlags fp contents = catchSrcErrors "pragmas" $ do
65 dflags0 <- getSessionDynFlags
66 let opts = Hdr.getOptions dflags0 contents fp
67 (dflags, _, _) <- parseDynamicFilePragma dflags0 opts
71-- | Run (unlit) literate haskell preprocessor on a file, or buffer if set
72runLhs :: DynFlags -> FilePath -> Maybe SB.StringBuffer -> IO SB.StringBuffer
73runLhs dflags filename contents = withTempDir $ \dir -> do
74 let fout = dir </> takeFileName filename <.> "unlit"
75 filesrc <- case contents of
76 Nothing -> return filename
78 let fsrc = dir </> takeFileName filename <.> "literate"
79 withBinaryFile fsrc WriteMode $ \h ->
80 hPutStringBuffer h cnts
83 SB.hGetStringBuffer fout
85 unlit filein fileout = SysTools.runUnlit dflags (args filein fileout)
86 args filein fileout = [
88 , SysTools.Option (escape filename) -- name this file
89 , SysTools.FileOption "" filein -- input file
90 , SysTools.FileOption "" fileout ] -- output file
91 -- taken from ghc's DriverPipeline.hs
92 escape ('\\':cs) = '\\':'\\': escape cs
93 escape ('\"':cs) = '\\':'\"': escape cs
94 escape ('\'':cs) = '\\':'\'': escape cs
95 escape (c:cs) = c : escape cs
100runCpp :: DynFlags -> FilePath -> Maybe SB.StringBuffer -> IO SB.StringBuffer
101runCpp dflags filename contents = withTempDir $ \dir -> do
102 let out = dir </> takeFileName filename <.> "out"
105 -- Happy case, file is not modified, so run CPP on it in-place
106 -- which also makes things like relative #include files work
107 -- and means location information is correct
108 doCpp dflags True filename out
109 liftIO $ SB.hGetStringBuffer out
112 -- Sad path, we have to create a version of the path in a temp dir
113 -- __FILE__ macro is wrong, ignoring that for now (likely not a real issue)
115 -- Relative includes aren't going to work, so we fix that by adding to the include path.
116 dflags <- return $ addIncludePathsQuote (takeDirectory filename) dflags
118 -- Location information is wrong, so we fix that by patching it afterwards.
119 let inp = dir </> "___GHCIDE_MAGIC___"
120 withBinaryFile inp WriteMode $ \h ->
121 hPutStringBuffer h contents
122 doCpp dflags True inp out
124 -- Fix up the filename in lines like:
125 -- # 1 "C:/Temp/extra-dir-914611385186/___GHCIDE_MAGIC___"
127 | Just x <- stripPrefix "# " x
128 , "___GHCIDE_MAGIC___" `isInfixOf` x
129 , let num = takeWhile (not . isSpace) x
130 -- important to use /, and never \ for paths, even on Windows, since then C escapes them
131 -- and GHC gets all confused
132 = "# " <> num <> " \"" <> map (\x -> if isPathSeparator x then '/' else x) filename <> "\""
134 stringToStringBuffer . unlines . map tweak . lines <$> readFileUTF8' out
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list