1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
module HaskellCodeExplorer.Preprocessor
( createSourceCodeTransformation
) where
import Control.Applicative ( (<|>) )
import qualified Data.Attoparsec.Text as AT
import Data.Foldable ( foldl' )
import qualified Data.HashMap.Strict as HM
import qualified Data.List as L
import qualified Data.Set as S
import qualified Data.Text as T
import HaskellCodeExplorer.Types ( FileLocation(..)
, HaskellFilePath(..)
, HaskellModulePath(..)
, LinePragma(..)
, SourceCodeTransformation(..)
, haskellPreprocessorExtensions
)
import System.FilePath ( normalise
, takeExtension
, takeFileName
)
-- | Finds locations of line pragmas and creates an index
createSourceCodeTransformation
:: HaskellModulePath -> T.Text -> T.Text -> (SourceCodeTransformation, T.Text)
createSourceCodeTransformation currentModulePath originalSourceCode sourceCodeAfterPreprocessing
= let
sourceCodeLines = T.splitOn "\n" sourceCodeAfterPreprocessing
numberedLines = zip [1 :: Int ..] sourceCodeLines
currentFilePath =
HaskellFilePath . getHaskellModulePath $ currentModulePath
addPragma :: [LinePragma] -> (Int, T.Text) -> [LinePragma]
addPragma acc (lineNumber, line) =
case AT.parseOnly linePragmaParser line of
Right (originalLineNumber, mbFileName) ->
LinePragma
(maybe currentFilePath
(HaskellFilePath . T.pack . normalise . T.unpack)
mbFileName
)
lineNumber
originalLineNumber
: acc
Left _ -> acc
totalLines = length numberedLines
pragmas = L.reverse . L.foldl' addPragma [] $ numberedLines
pragmaPath LinePragma {..} = filePath
currentFileExtension =
takeExtension . T.unpack . getHaskellFilePath $ currentFilePath
standardHeaderFiles =
[ "stdc-predef.h"
, "cabal_macros.h"
, "ghcversion.h"
, "HsVersions.h"
, "ghc_boot_platform.h"
, "ghcautoconf.h"
]
hasIncludedFiles = L.any
( (\path ->
let fileName = takeFileName . T.unpack . getHaskellFilePath $ path
in (path /= currentFilePath)
&& (path /= HaskellFilePath "<built-in>")
&& (path /= HaskellFilePath "<command-line>")
&& not ("ghc_" `L.isPrefixOf` fileName)
&& (fileName `notElem` standardHeaderFiles)
)
. pragmaPath
)
pragmas
in
if hasIncludedFiles
|| currentFileExtension
`elem` haskellPreprocessorExtensions
then
( SourceCodeTransformation
totalLines
currentModulePath
(S.fromList pragmas)
(indexLocations totalLines currentFilePath pragmas)
, sourceCodeAfterPreprocessing
)
else
( SourceCodeTransformation
(length $ T.splitOn "\n" originalSourceCode)
currentModulePath
S.empty
HM.empty
, originalSourceCode
)
-- | Parses line pragma
linePragmaParser :: AT.Parser (Int, Maybe T.Text)
linePragmaParser = pragma1 <|> pragma2
where
pragma1 :: AT.Parser (Int, Maybe T.Text)
pragma1 = parser "#" "line"
pragma2 :: AT.Parser (Int, Maybe T.Text)
pragma2 = parser "{-#" "LINE"
parser :: T.Text -> T.Text -> AT.Parser (Int, Maybe T.Text)
parser start line = do
_ <- AT.string start
_ <- AT.takeWhile (== ' ')
_ <- AT.string line <|> return ""
_ <- AT.takeWhile (== ' ')
num <- AT.decimal
_ <- AT.takeWhile (== ' ')
mbName <- (Just <$> fileName) <|> return Nothing
return (num, mbName)
fileName :: AT.Parser T.Text
fileName = AT.string "\"" *> AT.takeTill (== '\"') <* AT.string "\""
data Line = FirstLine | LastLine Int | Pragma LinePragma deriving (Show,Eq)
-- | Creates a HashMap whose keys are filenames and values are locations in a
-- preprocessed source code
indexLocations
:: Int
-> HaskellFilePath
-> [LinePragma]
-> HM.HashMap HaskellFilePath (S.Set FileLocation)
indexLocations totalLines preprocessedFilePath pragmas =
foldl' add HM.empty
. (zip <*> tail)
$ (FirstLine : map Pragma pragmas)
++ [LastLine totalLines]
where
add
:: HM.HashMap HaskellFilePath (S.Set FileLocation)
-> (Line, Line)
-> HM.HashMap HaskellFilePath (S.Set FileLocation)
-- Interval between the first line and the first pragma
add hMap (FirstLine, Pragma LinePragma {..})
| lineNumberPreprocessed > 1 = HM.insertWith
S.union
preprocessedFilePath
(S.singleton (FileLocation 1 lineNumberPreprocessed 0))
hMap
| otherwise = hMap
-- Interval between two pragmas
add hMap (Pragma (LinePragma fileName lineNumberPreprocessed1 lineNumberOriginal1), Pragma (LinePragma _ lineNumberPreprocessed2 _))
| lineNumberPreprocessed2 - lineNumberPreprocessed1 > 1
= HM.insertWith
S.union
fileName
(S.singleton
(FileLocation
lineNumberOriginal1
( lineNumberOriginal1
+ (lineNumberPreprocessed2 - lineNumberPreprocessed1 - 2)
)
(lineNumberPreprocessed1 - lineNumberOriginal1 + 1)
)
)
hMap
| otherwise
= hMap
-- Interval between the last pragma and the last line
add hMap (Pragma (LinePragma fileName lineNumberPreprocessed lineNumberOriginal), LastLine lastLineNumberPreprocessed)
| lastLineNumberPreprocessed - lineNumberPreprocessed > 1
= HM.insertWith
S.union
fileName
(S.singleton
(FileLocation
lineNumberOriginal
( lineNumberOriginal
+ (lastLineNumberPreprocessed - lineNumberPreprocessed - 2)
)
(lineNumberPreprocessed - lineNumberOriginal + 1)
)
)
hMap
| otherwise
= hMap
add hMap _ = hMap
|