@@ -1,270 +1,487 @@
1module Reporting.Render.Type
1module Reporting.Render.Type
2 ( Localizer
2 ( Localizer
4 , aliasDecl
4 , aliasDecl
5 , annotation
5 , annotation
12+import Control.Arrow ((***), first)
10import qualified Data.Map as Map
13import qualified Data.Map as Map
14+import qualified Data.Set as Set
11import Text.PrettyPrint.ANSI.Leijen
15import Text.PrettyPrint.ANSI.Leijen
16+ ( Doc, (<+>), cat, colon, comma, dullyellow, equals, hang, hsep, indent
17+ , lbrace, lparen, rbrace, rparen, sep, text, vcat
13−import qualified AST.Helpers as Help
20+import qualified AST.Type as Type
21+import qualified AST.Variable as Var
22+import Reporting.Error.Helpers as Help
17− Map.Map String String
26+-- PUBLIC API FOR CREATING DOCS
21− error "TODO"
29+decl :: Localizer -> String -> [String] -> [(String, [Type.Canonical])] -> Doc
30+decl localizer name vars tags =
33+ hang 2 (sep (text tag : map (docType localizer) args))
36+ (hsep (map text ("type" : name : vars)))
39+ (equals : repeat (text "|"))
24−aliasDecl =
43+aliasDecl :: Localizer -> String -> [String] -> Type.Canonical -> Doc
25− error "TODO"
44+aliasDecl localizer name vars tipe =
46+ [ hsep (map text ("type" : name : vars) ++ [equals])
47+ , docType localizer tipe
28annotation =
51annotation =
29 error "TODO"
52 error "TODO"
32−tipe =
55+toDoc :: Localizer -> Type.Canonical -> Doc
33− error "TODO"
56+toDoc localizer tipe =
57+ docType localizer tipe
60+diffToDocs :: Localizer -> Style -> Type.Canonical -> Type.Canonical -> (Doc,Doc)
61+diffToDocs localizer style leftType rightType =
62+ case diff localizer style leftType rightType of
66+ Diff leftDoc rightDoc ->
37−{-- HELPERS
71+-- LOCALIZE TYPES
39−commaCat :: [Doc] -> Doc
41− cat (punctuate comma docs)
75+ Map.Map String String
78+varToDoc :: Localizer -> Var.Canonical -> Doc
79+varToDoc localizer var =
81+ name = Var.toString var
83+ if name == "_Tuple0" then
44−commaSep :: [Doc] -> Doc
86+ else
45−commaSep docs =
87+ text (maybe name id (Map.lookup name localizer))
46− sep (punctuate comma docs)
49−parensIf :: Bool -> Doc -> Doc
51− if bool then parens doc else doc
54−variable :: String -> Doc
56− if Help.isOp x then parens (text x) else text x
60−-- PRETTY PRINTING
99+instance Functor Diff where
103+ Diff (func left) (func right)
62−instance (P.Pretty t) => P.Pretty (Port t) where
105+ Same both ->
63− pretty dealiaser needsParens portType =
106+ Same (func both)
64− P.pretty dealiaser needsParens (getPortType portType)
67−instance P.Pretty Raw' where
109+instance Applicative Diff where
68− pretty dealiaser needsParens tipe =
110+ pure a =
69− case tipe of
111+ Same a
71− P.parensIf needsParens (prettyLambda dealiaser getRawLambda arg body)
73− RVar x ->
113+ (<*>) func arg =
74− P.text x
114+ case (func, arg) of
115+ (Diff leftFunc rightFunc, Diff leftArg rightArg) ->
116+ Diff (leftFunc leftArg) (rightFunc rightArg)
76− RType var ->
118+ (Diff leftFunc rightFunc, Same arg) ->
77− prettyType dealiaser var
119+ Diff (leftFunc arg) (rightFunc arg)
79− RApp func args ->
121+ (Same func, Diff leftArg rightArg) ->
80− let
122+ Diff (func leftArg) (func rightArg)
81− isTuple (A.A _ (RType name)) = Help.isTuple (Var.toString name)
84− prettyApp dealiaser needsParens isTuple func args
86− RRecord fields ext ->
124+ (Same func, Same arg) ->
87− prettyRecord dealiaser (flattenRawRecord fields ext)
125+ Same (func arg)
90−instance P.Pretty Canonical where
128+partitionDiffs :: Map.Map k (Diff a) -> ( [(k, (a,a))], [(k, a)] )
91− pretty dealiaser needsParens tipe =
129+partitionDiffs dict =
131+ collect key value (diffKeys, sameKeys) =
92− case tipe of
132+ case value of
93− Lambda arg body ->
133+ Diff left right ->
94− P.parensIf needsParens (prettyLambda dealiaser getCanLambda arg body)
134+ ( (key, (left, right)) : diffKeys
96− Var x ->
138+ Same both ->
97− P.text x
139+ ( diffKeys
140+ , (key, both) : sameKeys
143+ Map.foldrWithKey collect ([],[]) dict
100− prettyType dealiaser var
104− isTuple (Type name) = Help.isTuple (Var.toString name)
107− prettyApp dealiaser needsParens isTuple func args
109− Record fields ext ->
147+-- DOC DIFF
110− prettyRecord dealiaser (flattenCanRecord fields ext)
112− Aliased name args _ ->
113− P.parensIf (needsParens && not (null args)) $
115− (P.pretty dealiaser False name)
117− (P.sep (map (P.pretty dealiaser True . snd) args))
122−prettyType :: (Var.ToString var) => P.Dealiaser -> var -> P.Doc
155+diff :: Localizer -> Style -> Type.Canonical -> Type.Canonical -> Diff Doc
123−prettyType dealiaser var =
156+diff localizer style leftType rightType =
125− v = Var.toString var
158+ go = diff localizer style
127− P.text $
160+ case (leftType, rightType) of
128− if v == "_Tuple0" then
161+ (Type.Lambda _ _, Type.Lambda _ _) ->
130− else
163+ leftParts = Type.collectLambdas leftType
131− maybe v id (Map.lookup v dealiaser)
164+ rightParts = Type.collectLambdas rightType
166+ if length leftParts /= length rightParts then
168+ (docLambda (map (docType localizer) leftParts))
169+ (docLambda (map (docType localizer) rightParts))
172+ docLambda <$> sequenceA (zipWith go leftParts rightParts)
134−-- PRETTY LAMBDAS
174+ (Type.Var x, Type.Var y) | x == y ->
136−prettyLambda :: (P.Pretty t) => P.Dealiaser -> (t -> Maybe (t,t)) -> t -> t -> P.Doc
177+ (Type.Type leftName, Type.Type rightName) | leftName == rightName ->
137−prettyLambda dealiaser getLambda arg body =
178+ pure (varToDoc localizer leftName)
140− gatherLambda getLambda body
142− prettyArg t =
180+ (Type.App (Type.Type leftName) leftArgs, Type.App (Type.Type rightName) rightArgs) ->
143− P.pretty dealiaser (Maybe.isJust (getLambda t)) t
181+ if leftName /= rightName || length leftArgs /= length rightArgs then
145− P.sep
183+ (docApp localizer leftName (map (docType localizer) leftArgs))
146− [ prettyArg arg
184+ (docApp localizer rightName (map (docType localizer) rightArgs))
147− , P.sep (map (\t -> P.text "->" <+> prettyArg t) rest)
187+ docApp localizer leftName
188+ <$> sequenceA (zipWith go leftArgs rightArgs)
151−getRawLambda :: Raw -> Maybe (Raw, Raw)
190+ (Type.App _ _, Type.App _ _) ->
152−getRawLambda (A.A _ tipe) =
191+ error "Type applications without concrete names should not get here."
154− RLambda arg body -> Just (arg, body)
193+ (Type.Record outerLeftFields outerLeftExt, Type.Record outerRightFields outerRightExt) ->
195+ (leftFields, leftExt) =
196+ flattenRecord outerLeftFields outerLeftExt
158−getCanLambda :: Canonical -> Maybe (Canonical, Canonical)
198+ (rightFields, rightExt) =
159−getCanLambda tipe =
199+ flattenRecord outerRightFields outerRightExt
160− case tipe of
201+ case style of
161− Lambda arg body -> Just (arg, body)
202+ Elide ->
162− _ -> Nothing
203+ diffRecordElide localizer leftFields leftExt rightFields rightExt
165−gatherLambda :: (t -> Maybe (t,t)) -> t -> [t]
208+ (Type.Aliased leftName leftArgs _, Type.Aliased rightName rightArgs _) | leftName == rightName ->
166−gatherLambda get tipe =
209+ docApp localizer leftName
167− case get tipe of
210+ <$> sequenceA (zipWith go (map snd leftArgs) (map snd rightArgs))
168− Just (arg, body) ->
211+
169− arg : gatherLambda get body
212+ (Type.Aliased _ args real, _) ->
213+ go (Type.dealias args real) rightType
215+ (_, Type.Aliased _ args real) ->
216+ go leftType (Type.dealias args real)
220+ (docType localizer leftType)
221+ (docType localizer rightType)
224+difference :: Doc -> Doc -> Diff Doc
225+difference leftDoc rightDoc =
226+ Diff (dullyellow leftDoc) (dullyellow rightDoc)
175−collectLambdas :: Canonical -> [Canonical]
176−collectLambdas tipe =
177− gatherLambda getCanLambda tipe
180−-- PRETTY APP
230+-- RECORD DIFFS
182−prettyApp :: (P.Pretty t) => P.Dealiaser -> Bool -> (t -> Bool) -> t -> [t] -> P.Doc
183−prettyApp dealiaser needsParens isTuple func args
186− P.punctuate P.comma (map (P.pretty dealiaser False) args)
188− | null args =
233+diffRecordElide :: Localizer -> Fields -> Maybe String -> Fields -> Maybe String -> Diff Doc
189− P.pretty dealiaser needsParens func
234+diffRecordElide localizer leftFields leftExt rightFields rightExt =
236+ (leftOnly, both, rightOnly) =
237+ vennDiagram leftFields rightFields
239+ if Map.null leftOnly && Map.null rightOnly then
242+ Map.map (uncurry (diff localizer Elide)) both
244+ case partitionDiffs fieldDiffs of
248+ docRecord Full (map (first text) sames)
250+ if leftExt == rightExt then
251+ Same (mkRecord (fmap text leftExt))
255+ (mkRecord (fmap (dullyellow . text) leftExt))
256+ (mkRecord (fmap (dullyellow . text) rightExt))
260+ (lefts, rights) = unzipDiffs diffs
261+ style = if null sames then Full else Elide
264+ (docRecord style lefts (fmap text leftExt))
265+ (docRecord style rights (fmap text rightExt))
270+ analyzeFields (Map.keys leftOnly) (Map.keys rightOnly)
273+ if Map.null both then Full else Elide
276+ (docRecord style lefts (fmap text leftExt))
277+ (docRecord style rights (fmap text rightExt))
280+unzipDiffs :: [(String, (Doc, Doc))] -> ( [(Doc, Doc)], [(Doc, Doc)] )
281+unzipDiffs diffPairs =
283+ unzipHelp (name, (left, right)) =
284+ (,) (text name, left) (text name, right)
286+ unzip (map unzipHelp diffPairs)
192− P.parensIf needsParens $
194− (P.pretty dealiaser True func)
196− (P.sep (map (P.pretty dealiaser True) args))
290+-- ANALYZE FIELD NAMES FOR SIMILAR NAMES
201−prettyRecord :: (P.Pretty t) => P.Dealiaser -> ([(String, t)], Maybe String) -> P.Doc
293+analyzeFields :: [String] -> [String] -> ( [(Doc, Doc)], [(Doc, Doc)] )
202−prettyRecord dealiaser recordInfo =
294+analyzeFields leftOnly rightOnly =
204− prettyField (field, tipe) =
296+ potentialTypos =
205− P.hang
297+ findPotentialTypos leftOnly rightOnly
206− (P.text field <+> P.text ":")
298+
207− 4
299+ mkField func name =
208− (P.pretty dealiaser False tipe)
300+ ( func (text name), text "…" )
302+ mkFieldWith counts name =
303+ mkField (if Set.member name counts then dullyellow else id) name
210− case recordInfo of
305+ case vetTypos potentialTypos of
211− ([], Nothing) ->
306+ Just (leftTypos, rightTypos) ->
212− P.text "{}"
307+ ( map (mkFieldWith leftTypos) leftOnly
308+ , map (mkFieldWith rightTypos) rightOnly
214− (fields, Nothing) ->
311+ Nothing ->
215− P.sep
312+ ( map (mkField dullyellow) leftOnly
216− [ P.cat (zipWith (<+>) (P.lbrace : repeat P.comma) (map prettyField fields))
313+ , map (mkField dullyellow) rightOnly
222− (P.lbrace <+> P.text x <+> P.text "|")
225− [ P.sep (P.punctuate P.comma (map prettyField fields))
317+findPotentialTypos :: [String] -> [String] -> [(String, String)]
318+findPotentialTypos leftOnly rightOnly =
321+ map ((,) leftName) (filter ((==1) . Help.distance leftName) rightOnly)
323+ concatMap veryNear leftOnly
234− -> ( [(String, Raw)], Maybe String )
235−flattenRawRecord fields ext =
240− Just (A.A _ (RVar x)) ->
326+vetTypos :: [(String, String)] -> Maybe (Set.Set String, Set.Set String)
241− (fields, Just x)
327+vetTypos potentialTypos =
329+ tallyNames (ln, rn) (lc, rc) =
330+ ( Map.insertWith (+) ln 1 lc
331+ , Map.insertWith (+) rn 1 rc
243− Just (A.A _ (RRecord fields' ext')) ->
334+ (leftCounts, rightCounts) =
244− flattenRawRecord (fields' ++ fields) ext'
335+ foldr tallyNames (Map.empty, Map.empty) potentialTypos
246− _ ->
337+ allUnique counts =
247− error "Trying to flatten ill-formed record."
338+ Map.foldr (\n unique -> n < 2 && unique) True counts
340+ if allUnique leftCounts && allUnique rightCounts then
341+ Just (Map.keysSet leftCounts, Map.keysSet rightCounts)
347+-- RECORD DIFFS HELPERS
351+ Map.Map String Type.Canonical
355+ Map.Map String (Type.Canonical,Type.Canonical)
358+vennDiagram :: Fields -> Fields -> (Fields, DoubleFields, Fields)
359+vennDiagram leftFields rightFields =
360+ ( Map.difference leftFields rightFields
361+ , Map.intersectionWith (,) leftFields rightFields
362+ , Map.difference rightFields leftFields
250−flattenCanRecord
366+flattenRecord
251− :: [(String, Canonical)]
367+ :: [(String, Type.Canonical)]
252− -> Maybe Canonical
368+ -> Maybe Type.Canonical
369+ -> (Fields, Maybe String)
370+flattenRecord fields ext =
371+ first Map.fromList (flattenRecordHelp fields ext)
375+ :: [(String, Type.Canonical)]
376+ -> Maybe Type.Canonical
253− -> ( [(String, Canonical)], Maybe String )
377+ -> ([(String, Type.Canonical)], Maybe String)
254−flattenCanRecord fields ext =
378+flattenRecordHelp fields ext =
255 case ext of
379 case ext of
256 Nothing ->
380 Nothing ->
257 (fields, Nothing)
381 (fields, Nothing)
259− Just (Var x) ->
383+ Just (Type.Var x) ->
260 (fields, Just x)
384 (fields, Just x)
262− Just (Record fields' ext') ->
386+ Just (Type.Record subFields subExt) ->
263− flattenCanRecord (fields' ++ fields) ext'
387+ flattenRecordHelp (fields ++ subFields) subExt
265− Just (Aliased _ args tipe) ->
389+ Just (Type.Aliased _ args tipe) ->
266− flattenCanRecord fields (Just (dealias args tipe))
390+ flattenRecordHelp fields (Just (Type.dealias args tipe))
269 error "Trying to flatten ill-formed record."
393 error "Trying to flatten ill-formed record."
400+docType :: Localizer -> Type.Canonical -> Doc
401+docType localizer tipe =
404+ docLambda (map (docType localizer) (Type.collectLambdas tipe))
410+ varToDoc localizer name
412+ Type.App (Type.Type name) args ->
413+ docApp localizer name (map (docType localizer) args)
416+ error "type applications should start with a type atom"
418+ Type.Record outerFields outerExt ->
421+ flattenRecordHelp outerFields outerExt
424+ (map (text *** docType localizer) fields)
427+ Type.Aliased name args _ ->
428+ docApp localizer name (map (docType localizer . snd) args)
432+docLambda :: [Doc] -> Doc
433+docLambda (arg:rest) =
434+ sep (arg : map (text "->" <+>) rest)
438+docApp :: Localizer -> Var.Canonical -> [Doc] -> Doc
439+docApp localizer name args =
440+ if Var.isTuple name then
442+ [ cat (zipWith (<+>) (lparen : repeat comma) args)
446+ else if null args then
447+ varToDoc localizer name
450+ hang 4 (sep (varToDoc localizer name : args))
454+docRecord :: Style -> [(Doc,Doc)] -> Maybe Doc -> Doc
455+docRecord style fields maybeExt =
457+ docField (name, tipe) =
458+ hang 4 (sep [ name <+> colon, tipe ])
469+ elision ++ map docField fields
471+ case (fieldDocs, maybeExt) of
477+ [ cat (zipWith (<+>) (lbrace : repeat comma) fieldDocs)
484+ , indent 4 (cat (zipWith (<+>) (text "|" : repeat comma) fieldDocs))