forked from facebookincubator/retrie
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathExactPrint.hs
419 lines (378 loc) · 13.8 KB
/
ExactPrint.hs
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
-- Copyright (c) Facebook, Inc. and its affiliates.
--
-- This source code is licensed under the MIT license found in the
-- LICENSE file in the root directory of this source tree.
--
{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}
-- | Provides consistent interface with ghc-exactprint.
module Retrie.ExactPrint
( -- * Fixity re-association
fix
-- * Parsers
, parseContent
, parseContentNoFixity
, parseDecl
, parseExpr
, parseImports
, parsePattern
, parseStmt
, parseType
-- * Primitive Transformations
, addAllAnnsT
, cloneT
, setEntryDPT
, swapEntryDPT
, transferAnnsT
, transferEntryAnnsT
, transferEntryDPT
-- * Utils
, debugDump
, debugParse
, hasComments
, isComma
-- * Annotated AST
, module Retrie.ExactPrint.Annotated
-- * ghc-exactprint re-exports
, module Language.Haskell.GHC.ExactPrint
, module Language.Haskell.GHC.ExactPrint.Annotate
, module Language.Haskell.GHC.ExactPrint.Types
, module Language.Haskell.GHC.ExactPrint.Utils
) where
import Control.Exception
import Control.Monad.State.Lazy hiding (fix)
import Data.Default as D
import Data.Function (on)
import Data.List (transpose)
import Data.Maybe
import qualified Data.Map as M
import Text.Printf
import Language.Haskell.GHC.ExactPrint hiding
( cloneT
, setEntryDP
, setEntryDPT
, transferEntryDPT
, transferEntryDP
)
import Language.Haskell.GHC.ExactPrint.Annotate (Annotate)
import qualified Language.Haskell.GHC.ExactPrint.Parsers as Parsers
import Language.Haskell.GHC.ExactPrint.Types
( AnnConName(..)
, DeltaPos(..)
, KeywordId(..)
, annGetConstr
, annNone
, emptyAnns
, mkAnnKey
)
import Language.Haskell.GHC.ExactPrint.Utils (annLeadingCommentEntryDelta, showGhc)
import Retrie.ExactPrint.Annotated
import Retrie.Fixity
import Retrie.GHC
import Retrie.SYB
-- Fixity traversal -----------------------------------------------------------
-- | Re-associate AST using given 'FixityEnv'. (The GHC parser has no knowledge
-- of operator fixity, because that requires running the renamer, so it parses
-- all operators as left-associated.)
fix :: (Data ast, Monad m) => FixityEnv -> ast -> TransformT m ast
fix env = fixAssociativity >=> fixEntryDP
where
fixAssociativity = everywhereM (mkM (fixOneExpr env) `extM` fixOnePat env)
fixEntryDP = everywhereM (mkM fixOneEntryExpr `extM` fixOneEntryPat)
-- Should (x op1 y) op2 z be reassociated as x op1 (y op2 z)?
associatesRight :: Fixity -> Fixity -> Bool
associatesRight (Fixity _ p1 a1) (Fixity _ p2 _a2) =
p2 > p1 || p1 == p2 && a1 == InfixR
-- We know GHC produces left-associated chains, so 'z' is never an
-- operator application. We also know that this will be applied bottom-up
-- by 'everywhere', so we can assume the children are already fixed.
fixOneExpr
:: Monad m
=> FixityEnv
-> LHsExpr GhcPs
-> TransformT m (LHsExpr GhcPs)
#if __GLASGOW_HASKELL__ < 806
fixOneExpr env (L l2 (OpApp ap1@(L l1 (OpApp x op1 f1 y)) op2 f2 z))
| associatesRight (lookupOp op1 env) (lookupOp op2 env) = do
let ap2' = L l2 $ OpApp y op2 f2 z
swapEntryDPT ap1 ap2'
transferAnnsT isComma ap2' ap1
rhs <- fixOneExpr env ap2'
return $ L l1 $ OpApp x op1 f1 rhs
#else
fixOneExpr env (L l2 (OpApp x2 ap1@(L l1 (OpApp x1 x op1 y)) op2 z))
| associatesRight (lookupOp op1 env) (lookupOp op2 env) = do
let ap2' = L l2 $ OpApp x2 y op2 z
swapEntryDPT ap1 ap2'
transferAnnsT isComma ap2' ap1
rhs <- fixOneExpr env ap2'
return $ L l1 $ OpApp x1 x op1 rhs
#endif
fixOneExpr _ e = return e
fixOnePat :: Monad m => FixityEnv -> LPat GhcPs -> TransformT m (LPat GhcPs)
#if __GLASGOW_HASKELL__ == 808
fixOnePat env (dL -> L l2 (ConPatIn op2 (InfixCon (dL -> ap1@(L l1 (ConPatIn op1 (InfixCon x y)))) z)))
| associatesRight (lookupOpRdrName op1 env) (lookupOpRdrName op2 env) = do
let ap2' = L l2 (ConPatIn op2 (InfixCon y z))
swapEntryDPT ap1 ap2'
transferAnnsT isComma ap2' ap1
rhs <- fixOnePat env (composeSrcSpan ap2')
return $ cL l1 (ConPatIn op1 (InfixCon x rhs))
#else
fixOnePat env (L l2 (ConPatIn op2 (InfixCon ap1@(L l1 (ConPatIn op1 (InfixCon x y))) z)))
| associatesRight (lookupOpRdrName op1 env) (lookupOpRdrName op2 env) = do
let ap2' = L l2 (ConPatIn op2 (InfixCon y z))
swapEntryDPT ap1 ap2'
transferAnnsT isComma ap2' ap1
rhs <- fixOnePat env ap2'
return $ L l1 (ConPatIn op1 (InfixCon x rhs))
#endif
fixOnePat _ e = return e
-- Move leading whitespace from the left child of an operator application
-- to the application itself. We need this so we have correct offsets when
-- substituting into patterns and don't end up with extra leading spaces.
-- We can assume it is run bottom-up, and that precedence is already fixed.
fixOneEntry
:: (Monad m, Data a)
=> Located a -- ^ Overall application
-> Located a -- ^ Left child
-> TransformT m (Located a)
fixOneEntry e x = do
anns <- getAnnsT
let
zeros = DP (0,0)
(DP (xr,xc), DP (actualRow,_)) =
case M.lookup (mkAnnKey x) anns of
Nothing -> (zeros, zeros)
Just ann -> (annLeadingCommentEntryDelta ann, annEntryDelta ann)
DP (er,ec) =
maybe zeros annLeadingCommentEntryDelta $ M.lookup (mkAnnKey e) anns
when (actualRow == 0) $ do
setEntryDPT e $ DP (er, xc + ec)
setEntryDPT x $ DP (xr, 0)
return e
fixOneEntryExpr :: Monad m => LHsExpr GhcPs -> TransformT m (LHsExpr GhcPs)
#if __GLASGOW_HASKELL__ < 806
fixOneEntryExpr e@(L _ (OpApp x _ _ _)) = fixOneEntry e x
#else
fixOneEntryExpr e@(L _ (OpApp _ x _ _)) = fixOneEntry e x
#endif
fixOneEntryExpr e = return e
fixOneEntryPat :: Monad m => LPat GhcPs -> TransformT m (LPat GhcPs)
#if __GLASGOW_HASKELL__ == 808
fixOneEntryPat p@(ConPatIn _ (InfixCon x _)) =
composeSrcSpan <$> fixOneEntry (dL p) (dL x)
#else
fixOneEntryPat p@(L _ (ConPatIn _ (InfixCon x _))) = fixOneEntry p x
#endif
fixOneEntryPat p = return p
-------------------------------------------------------------------------------
swapEntryDPT
:: (Data a, Data b, Monad m)
=> Located a -> Located b -> TransformT m ()
swapEntryDPT a b = modifyAnnsT $ \ anns ->
let akey = mkAnnKey a
bkey = mkAnnKey b
aann = fromMaybe annNone $ M.lookup akey anns
bann = fromMaybe annNone $ M.lookup bkey anns
in M.insert akey
aann { annEntryDelta = annEntryDelta bann
, annPriorComments = annPriorComments bann } $
M.insert bkey
bann { annEntryDelta = annEntryDelta aann
, annPriorComments = annPriorComments aann } anns
-------------------------------------------------------------------------------
-- Compatibility module with ghc-exactprint
parseContentNoFixity :: FilePath -> String -> IO AnnotatedModule
parseContentNoFixity fp str = do
r <- Parsers.parseModuleFromString fp str
case r of
Left msg -> do
#if __GLASGOW_HASKELL__ < 810
fail $ show msg
#else
join $ Parsers.withDynFlags $ \dflags -> printBagOfErrors dflags msg
fail "parse failed"
#endif
Right (anns, m) -> return $ unsafeMkA m anns 0
parseContent :: FixityEnv -> FilePath -> String -> IO AnnotatedModule
parseContent fixities fp =
parseContentNoFixity fp >=> (`transformA` fix fixities)
-- | Parse import statements. Each string must be a full import statement,
-- including the keyword 'import'. Supports full import syntax.
parseImports :: [String] -> IO AnnotatedImports
parseImports [] = return mempty
parseImports imports = do
-- imports start on second line, so delta offsets are correct
am <- parseContentNoFixity "parseImports" $ "\n" ++ unlines imports
ais <- transformA am $ pure . hsmodImports . unLoc
return $ trimA ais
-- | Parse a top-level 'HsDecl'.
parseDecl :: String -> IO AnnotatedHsDecl
parseDecl = parseHelper "parseDecl" Parsers.parseDecl
-- | Parse a 'HsExpr'.
parseExpr :: String -> IO AnnotatedHsExpr
parseExpr = parseHelper "parseExpr" Parsers.parseExpr
-- | Parse a 'Pat'.
parsePattern :: String -> IO AnnotatedPat
parsePattern = parseHelper "parsePattern" p
where
#if __GLASGOW_HASKELL__ < 808
p = Parsers.parsePattern
#else
p flags fp str = fmap dL <$> Parsers.parsePattern flags fp str
#endif
-- | Parse a 'Stmt'.
parseStmt :: String -> IO AnnotatedStmt
parseStmt = parseHelper "parseStmt" Parsers.parseStmt
-- | Parse a 'HsType'.
parseType :: String -> IO AnnotatedHsType
parseType = parseHelper "parseType" Parsers.parseType
parseHelper :: FilePath -> Parsers.Parser a -> String -> IO (Annotated a)
parseHelper fp parser str = join $ Parsers.withDynFlags $ \dflags ->
case parser dflags fp str of
#if __GLASGOW_HASKELL__ < 810
Left (_, msg) -> throwIO $ ErrorCall msg
#else
Left errBag -> do
printBagOfErrors dflags errBag
throwIO $ ErrorCall "parse failed"
#endif
Right (anns, x) -> return $ unsafeMkA x anns 0
-------------------------------------------------------------------------------
debugDump :: Annotate a => Annotated (Located a) -> IO ()
debugDump ax = do
let
str = printA ax
maxCol = maximum $ map length $ lines str
(tens, ones) =
case transpose [printf "%2d" i | i <- [1 .. maxCol]] of
[ts, os] -> (ts, os)
_ -> ("", "")
putStrLn $ unlines
[ show k ++ "\n " ++ show v | (k,v) <- M.toList (annsA ax) ]
putStrLn tens
putStrLn ones
putStrLn str
cloneT :: (Data a, Typeable a, Monad m) => a -> TransformT m a
cloneT e = getAnnsT >>= flip graftT e
-- The following definitions are all the same as the ones from ghc-exactprint,
-- but the types are liberalized from 'Transform a' to 'TransformT m a'.
transferEntryAnnsT
:: (Data a, Data b, Monad m)
=> (KeywordId -> Bool) -- transfer Anns matching predicate
-> Located a -- from
-> Located b -- to
-> TransformT m ()
transferEntryAnnsT p a b = do
transferEntryDPT a b
transferAnnsT p a b
-- | 'Transform' monad version of 'transferEntryDP'
transferEntryDPT
:: (Data a, Data b, Monad m)
=> Located a -> Located b -> TransformT m ()
transferEntryDPT a b =
modifyAnnsT (transferEntryDP a b)
-- This function fails if b is not in Anns, which seems dumb, since we are inserting it.
transferEntryDP :: (Data a, Data b) => Located a -> Located b -> Anns -> Anns
transferEntryDP a b anns = setEntryDP b dp anns'
where
maybeAnns = do -- Maybe monad
anA <- M.lookup (mkAnnKey a) anns
let anB = M.findWithDefault annNone (mkAnnKey b) anns
anB' = anB { annEntryDelta = DP (0,0) }
return (M.insert (mkAnnKey b) anB' anns, annLeadingCommentEntryDelta anA)
(anns',dp) = fromMaybe
(error $ "transferEntryDP: lookup failed: " ++ show (mkAnnKey a))
maybeAnns
addAllAnnsT
:: (Data a, Data b, Monad m)
=> Located a -> Located b -> TransformT m ()
addAllAnnsT a b =
modifyAnnsT (addAllAnns a b)
addAllAnns :: (Data a, Data b) => Located a -> Located b -> Anns -> Anns
addAllAnns a b anns =
fromMaybe
(error $ "addAllAnns: lookup failed: " ++ show (mkAnnKey a)
++ " or " ++ show (mkAnnKey b))
$ do ann <- M.lookup (mkAnnKey a) anns
case M.lookup (mkAnnKey b) anns of
Just ann' -> return $ M.insert (mkAnnKey b) (ann `annAdd` ann') anns
Nothing -> return $ M.insert (mkAnnKey b) ann anns
where annAdd ann ann' = ann'
{ annEntryDelta = annEntryDelta ann
, annPriorComments = ((++) `on` annPriorComments) ann ann'
, annFollowingComments = ((++) `on` annFollowingComments) ann ann'
, annsDP = ((++) `on` annsDP) ann ann'
}
isComma :: KeywordId -> Bool
isComma (G AnnComma) = True
isComma _ = False
isCommentKeyword :: KeywordId -> Bool
isCommentKeyword (AnnComment _) = True
isCommentKeyword _ = False
isCommentAnnotation :: Annotation -> Bool
isCommentAnnotation Ann{..} =
(not . null $ annPriorComments)
|| (not . null $ annFollowingComments)
|| any (isCommentKeyword . fst) annsDP
hasComments :: (Data a, Monad m) => Located a -> TransformT m Bool
hasComments e = do
anns <- getAnnsT
let b = isCommentAnnotation <$> M.lookup (mkAnnKey e) anns
return $ fromMaybe False b
transferAnnsT
:: (Data a, Data b, Monad m)
=> (KeywordId -> Bool) -- transfer Anns matching predicate
-> Located a -- from
-> Located b -- to
-> TransformT m ()
transferAnnsT p a b = modifyAnnsT f
where
bKey = mkAnnKey b
f anns = fromMaybe anns $ do
anA <- M.lookup (mkAnnKey a) anns
anB <- M.lookup bKey anns
let anB' = anB { annsDP = annsDP anB ++ filter (p . fst) (annsDP anA) }
return $ M.insert bKey anB' anns
-- | 'Transform' monad version of 'getEntryDP'
setEntryDPT
:: (Data a, Monad m)
=> Located a -> DeltaPos -> TransformT m ()
setEntryDPT ast dp = do
modifyAnnsT (setEntryDP ast dp)
-- | The setEntryDP that comes with exactprint does some really confusing
-- entry math around comments that I'm not convinced is either correct or useful.
setEntryDP :: Data a => Located a -> DeltaPos -> Anns -> Anns
setEntryDP x dp anns = M.alter (Just . f . fromMaybe annNone) k anns
where
k = mkAnnKey x
f ann = case annPriorComments ann of
[] -> ann { annEntryDelta = dp }
(c,_):cs -> ann { annPriorComments = (c,dp):cs }
-- Useful for figuring out what annotations should be on something.
debugParse :: String -> IO ()
debugParse s = do
writeFile "debug.txt" s
r <- parseModule "debug.txt"
case r of
Left _ -> putStrLn "parse failed"
Right (anns, modl) -> do
let m = unsafeMkA modl anns 0
putStrLn "parseModule"
debugDump m
void $ transformDebug m
where
transformDebug =
run "fixOneExpr D.def" (fixOneExpr D.def)
>=> run "fixOnePat D.def" (fixOnePat D.def)
>=> run "fixOneEntryExpr" fixOneEntryExpr
>=> run "fixOneEntryPat" fixOneEntryPat
run wat f m = do
putStrLn wat
m' <- transformA m (everywhereM (mkM f))
debugDump m'
return m'