-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathResult.hs
308 lines (257 loc) · 9.62 KB
/
Result.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
{- |
Module : $Header$
Description : Result monad for accumulating Diagnosis messages
Copyright : (c) T. Mossakowski, C. Maeder, Uni Bremen 2002-2008
License : GPLv2 or higher, see LICENSE.txt
Maintainer : Christian.Maeder@dfki.de
Stability : provisional
Portability : portable
'Result' monad for accumulating 'Diagnosis' messages
during analysis phases.
-}
module Common.Result
( DiagKind (..)
, Diagnosis (..)
, mkDiag
, mkNiceDiag
, isErrorDiag
, hasErrors
, addErrorDiag
, checkUniqueness
, Result (..)
, appendDiags
, joinResultWith
, joinResult
, mapR
, fatal_error
, mkError
, debug
, plain_error
, warning
, justWarn
, hint
, justHint
, message
, maybeToResult
, resultToMonad
, resultToMaybe
, adjustPos
, updDiagKind
, propagateErrors
, showErr
, prettyRange
, filterDiags
, showRelDiags
, printDiags
) where
import Common.Doc
import Common.DocUtils
import Common.GlobalAnnotations
import Common.Id
import Common.Lexer
import Control.Monad
import Control.Monad.Identity
import Data.Function
import Data.List
import Text.ParserCombinators.Parsec.Error
import Text.ParserCombinators.Parsec.Char (char)
import Text.ParserCombinators.Parsec (parse)
-- | severness of diagnostic messages
data DiagKind = Error | Warning | Hint | Debug
| MessageW -- ^ used for messages in the web interface
deriving (Eq, Ord, Show)
-- | a diagnostic message with 'Pos'
data Diagnosis = Diag { diagKind :: DiagKind
, diagString :: String
, diagPos :: Range
} deriving Eq
-- | construct a message for a printable item that carries a position
mkDiag :: (GetRange a, Pretty a) => DiagKind -> String -> a -> Diagnosis
mkDiag k s a = let q = text "'" in
Diag k (show $ sep [text s, q <> pretty a <> q]) $ getRangeSpan a
-- | construct a message for a printable item that carries a position
mkNiceDiag :: (GetRange a, Pretty a) => GlobalAnnos
-> DiagKind -> String -> a -> Diagnosis
mkNiceDiag ga k s a = let q = text "'" in
Diag k (renderText ga $ sep [text s, q <> pretty a <> q])
$ getRangeSpan a
-- | check whether a diagnosis is an error
isErrorDiag :: Diagnosis -> Bool
isErrorDiag d = case diagKind d of
Error -> True
_ -> False
-- | Check whether a diagnosis list contains errors
hasErrors :: [Diagnosis] -> Bool
hasErrors = any isErrorDiag
-- | add a further error message to explain a failure
addErrorDiag :: (GetRange a, Pretty a) => String -> a -> Result b -> Result b
addErrorDiag str a r@(Result ds ms) = if hasErrors ds then
Result (mkDiag Error str a : ds) ms else r
-- | add range to a diagnosis
adjustDiagPos :: Range -> Diagnosis -> Diagnosis
adjustDiagPos r d = if isNullRange $ diagPos d then d { diagPos = r } else d
-- | change the diag kind of a diagnosis
updDiagKind :: (DiagKind -> DiagKind) -> Diagnosis -> Diagnosis
updDiagKind f d = d { diagKind = f $ diagKind d }
-- | A uniqueness check yields errors for duplicates in a given list.
checkUniqueness :: (Pretty a, GetRange a, Ord a) => [a] -> [Diagnosis]
checkUniqueness l =
let vd = filter ( not . null . tail) $ group $ sort l
in map (\ vs -> mkDiag Error ("duplicates at '" ++
showSepList (showString " ") shortPosShow
(concatMap getPosList (tail vs)) "'"
++ " for") (head vs)) vd
where shortPosShow :: Pos -> ShowS
shortPosShow p = showParen True
(shows (sourceLine p) .
showString "," .
shows (sourceColumn p))
-- | The result monad. A failing result should include an error message.
data Result a = Result { diags :: [Diagnosis]
, maybeResult :: Maybe a
} deriving Show
instance Functor Result where
fmap f (Result errs m) = Result errs $ fmap f m
instance Monad Result where
return = Result [] . Just
r@(Result e m) >>= f = case m of
Nothing -> Result e Nothing
Just x -> joinResult r $ f x
fail s = fatal_error s nullRange
instance MonadPlus Result where
mzero = Result [] Nothing
r1@(Result _ m) `mplus` r2 = case m of
Nothing -> r2
Just _ -> r1
appendDiags :: [Diagnosis] -> Result ()
appendDiags ds = Result ds (Just ())
-- | join two results with a combining function
joinResultWith :: (a -> b -> c) -> Result a -> Result b -> Result c
joinResultWith f (Result d1 m1) (Result d2 m2) = Result (d1 ++ d2) $
do r1 <- m1
r2 <- m2
return $ f r1 r2
-- | join two results
joinResult :: Result a -> Result b -> Result b
joinResult = joinResultWith (\ _ b -> b)
-- | join a list of results that are independently computed
mapR :: (a -> Result b) -> [a] -> Result [b]
mapR ana = foldr (joinResultWith (:) . ana) $ Result [] $ Just []
-- | a failing result with a proper position
fatal_error :: String -> Range -> Result a
fatal_error s p = Result [Diag Error s p] Nothing
-- | a failing result constructing a message from a type
mkError :: (GetRange a, Pretty a) => String -> a -> Result b
mkError s c = Result [mkDiag Error s c] Nothing
-- | add a debug point
debug :: (GetRange a, Pretty a) => Int -> (String, a) -> Result ()
debug n (s, a) = Result
[mkDiag Debug (unlines [" point " ++ show n, "Variable " ++ s ++ ":"]) a ]
$ Just ()
-- | add an error message but don't fail
plain_error :: a -> String -> Range -> Result a
plain_error x s p = Result [Diag Error s p] $ Just x
-- | add a warning
warning :: a -> String -> Range -> Result a
warning x s p = Result [Diag Warning s p] $ Just x
-- | just add a warning without position information
justWarn :: a -> String -> Result a
justWarn x s = warning x s nullRange
-- | add a hint
hint :: a -> String -> Range -> Result a
hint x s p = Result [Diag Hint s p] $ Just x
-- | just add a hint without position information
justHint :: a -> String -> Result a
justHint x s = hint x s nullRange
-- | add a (web interface) message
message :: a -> String -> Result a
message x m = Result [Diag MessageW m nullRange] $ Just x
-- | add a failure message to 'Nothing'
maybeToResult :: Range -> String -> Maybe a -> Result a
maybeToResult p s m = Result (case m of
Nothing -> [Diag Error s p]
Just _ -> []) m
-- | check whether no errors are present, coerce into 'Maybe'
resultToMaybe :: Result a -> Maybe a
resultToMaybe = resultToMonad ""
-- | adjust positions of diagnoses
adjustPos :: Range -> Result a -> Result a
adjustPos p r =
r {diags = map (adjustDiagPos p) $ diags r}
-- | Propagate errors using the error function
resultToMonad :: Monad m => String -> Result a -> m a
resultToMonad pos r = let ds = diags r in
case (hasErrors ds, maybeResult r) of
(False, Just x) -> return x
_ -> fail $ pos ++ ' ' : showRelDiags 2 ds
-- | Propagate errors using the error function
propagateErrors :: String -> Result a -> a
propagateErrors pos = runIdentity . resultToMonad pos
-- | showing (Parsec) parse errors using our own 'showPos' function
showErr :: ParseError -> String
showErr err = let
(lookAheads, msgs) = partition (\ m -> case m of
Message str -> isPrefixOf lookaheadPosition str
_ -> False) $ errorMessages err
readPos :: String -> Maybe Pos
readPos s = case parse (do
ls <- getNumber
char '.'
cs <- getNumber
return $ newPos "" (value 10 ls) (value 10 cs)) "" s of
Left _ -> Nothing
Right x -> Just x
pos = fromSourcePos (errorPos err)
poss = pos : foldr (\ s l -> case readPos $
drop (length lookaheadPosition)
$ messageString s of
Just p -> p {sourceName = sourceName pos} : l
_ -> l) [] lookAheads
in shows (prettySingleSourceRange poss) ":" ++
showErrorMessages "or" "unknown parse error"
"expecting" "unexpected" "end of input" msgs
prettySingleSourceRange :: [Pos] -> Doc
prettySingleSourceRange sp = let
mi = minimum sp
ma = maximum sp
in case compare mi ma of
EQ -> text (showPos ma "")
_ -> text $ showPos mi "-"
++ showPos ma {sourceName = ""} ""
prettyRange :: [Pos] -> Doc
prettyRange = sepByCommas . map prettySingleSourceRange
. groupBy (on (==) sourceName) . sort
-- Added this instance because prettyRange is not exported
instance Pretty Range where
pretty = prettyRange . rangeToList
relevantDiagKind :: Int -> DiagKind -> Bool
relevantDiagKind v k = case k of
Error -> True
Warning -> v >= 2
Hint -> v >= 4
Debug -> v >= 5
MessageW -> False
filterDiags :: Int -> [Diagnosis] -> [Diagnosis]
filterDiags v = filter $ relevantDiagKind v . diagKind
showRelDiags :: Int -> [Diagnosis] -> String
showRelDiags v = unlines . map show . filterDiags v
printDiags :: Int -> [Diagnosis] -> IO ()
printDiags v = putStr . showRelDiags v
instance Show Diagnosis where
showsPrec _ = shows . pretty
instance Pretty Diagnosis where
pretty (Diag k s (Range sp)) = sep
[ sep [case sp of
[] -> empty
_ -> prettyRange sp <> colon
, case k of
MessageW -> empty
_ -> text (case k of
Error -> "***"
_ -> "###") <+> text (show k) <> colon
]
, text s]
instance GetRange Diagnosis where
getRange = diagPos
instance Pretty a => Pretty (Result a) where
pretty (Result ds m) = vcat $ pretty m : map pretty ds