Skip to content

Commit d08b2ee

Browse files
committed
docs: Separate conversion and rendering stages
* Modify the data types in Language.PureScript.Docs.Types so that they no longer contain any RenderedCode values, but rather values of the types Language.PureScript.Type or Language.PureScript.Kind, in order to preserve more structure * Rename Language.PureScript.Docs.Render to Language.PureScript.Docs.Convert, and remove all the code related to producing RenderedCode values * Add a new module Language.PureScript.Docs.Render with code rendering functions (extracted from what was previously Language.PureScript.Docs.Render), to be used in markdown/html docs These changes mean that information about the real types and kinds are stored in the JSON-serialized packages that pursuit works with, meaning that pursuit now has access to this information. This enables us to do the transformations necessary for Hoogle input files, and gives us more flexibility with regard to how we work around differences in PureScript's and Haskell's type systems (with respect to, eg, rows). Also, as a bonus, the renderDeclaration function is significantly simpler (resulting from concerns being better separated).
1 parent 4dbfe22 commit d08b2ee

12 files changed

Lines changed: 577 additions & 395 deletions

File tree

psc-publish/Main.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ preparePackage' = do
109109

110110
return D.Package{..}
111111

112-
getModulesAndBookmarks :: PrepareM ([D.Bookmark], [D.RenderedModule])
112+
getModulesAndBookmarks :: PrepareM ([D.Bookmark], [D.Module])
113113
getModulesAndBookmarks = do
114114
(inputFiles, depsFiles) <- liftIO getInputAndDepsFiles
115115
liftIO (D.parseAndDesugar inputFiles depsFiles renderModules)
116116
>>= either (userError . ParseAndDesugarError) return
117117
where
118118
renderModules bookmarks modules =
119-
return (bookmarks, map D.renderModule modules)
119+
return (bookmarks, map D.convertModule modules)
120120

121121
getVersionFromGitTag :: PrepareM (String, Version)
122122
getVersionFromGitTag = do

purescript.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ library
125125
Language.PureScript.Types
126126

127127
Language.PureScript.Docs
128+
Language.PureScript.Docs.Convert
128129
Language.PureScript.Docs.Render
129130
Language.PureScript.Docs.Types
130131
Language.PureScript.Docs.RenderedCode

src/Language/PureScript/Docs.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ module Language.PureScript.Docs (
99
import Language.PureScript.Docs.Types as Docs
1010
import Language.PureScript.Docs.RenderedCode.Types as Docs
1111
import Language.PureScript.Docs.RenderedCode.Render as Docs
12+
import Language.PureScript.Docs.Convert as Docs
1213
import Language.PureScript.Docs.Render as Docs
1314
import Language.PureScript.Docs.ParseAndDesugar as Docs

src/Language/PureScript/Docs/AsMarkdown.hs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import qualified Language.PureScript as P
1212

1313
import Language.PureScript.Docs.Types
1414
import Language.PureScript.Docs.RenderedCode
15+
import qualified Language.PureScript.Docs.Convert as Convert
1516
import qualified Language.PureScript.Docs.Render as Render
1617

1718
-- |
@@ -20,41 +21,41 @@ import qualified Language.PureScript.Docs.Render as Render
2021
--
2122
renderModulesAsMarkdown :: [P.Module] -> String
2223
renderModulesAsMarkdown =
23-
runDocs . modulesAsMarkdown . map Render.renderModule
24+
runDocs . modulesAsMarkdown . map Convert.convertModule
2425

25-
modulesAsMarkdown :: [RenderedModule] -> Docs
26+
modulesAsMarkdown :: [Module] -> Docs
2627
modulesAsMarkdown = mapM_ moduleAsMarkdown
2728

28-
moduleAsMarkdown :: RenderedModule -> Docs
29-
moduleAsMarkdown RenderedModule{..} = do
30-
headerLevel 2 $ "Module " ++ rmName
29+
moduleAsMarkdown :: Module -> Docs
30+
moduleAsMarkdown Module{..} = do
31+
headerLevel 2 $ "Module " ++ modName
3132
spacer
32-
for_ rmComments tell'
33-
mapM_ declAsMarkdown rmDeclarations
33+
for_ modComments tell'
34+
mapM_ declAsMarkdown modDeclarations
3435
spacer
3536

36-
declAsMarkdown :: RenderedDeclaration -> Docs
37-
declAsMarkdown RenderedDeclaration{..} = do
38-
headerLevel 4 (ticks rdTitle)
37+
declAsMarkdown :: Declaration -> Docs
38+
declAsMarkdown decl@Declaration{..} = do
39+
headerLevel 4 (ticks declTitle)
3940
spacer
4041

41-
let (instances, children) = partition (isChildInstance . rcdInfo) rdChildren
42+
let (instances, children) = partition (isChildInstance . cdeclInfo) declChildren
4243
fencedBlock $ do
43-
tell' (codeToString rdCode)
44+
tell' (codeToString $ Render.renderDeclaration decl)
4445
zipWithM_ (\f c -> tell' (childToString f c)) (First : repeat NotFirst) children
4546
spacer
4647

47-
for_ rdFixity (\fixity -> fixityAsMarkdown fixity >> spacer)
48+
for_ declFixity (\fixity -> fixityAsMarkdown fixity >> spacer)
4849

49-
for_ rdComments tell'
50+
for_ declComments tell'
5051

5152
unless (null instances) $ do
5253
headerLevel 5 "Instances"
5354
fencedBlock $ mapM_ (tell' . childToString NotFirst) instances
5455
spacer
5556

5657
where
57-
isChildInstance (ChildInstance _) = True
58+
isChildInstance (ChildInstance _ _) = True
5859
isChildInstance _ = False
5960

6061
codeToString :: RenderedCode -> String
@@ -81,16 +82,18 @@ fixityAsMarkdown (P.Fixity associativity precedence) =
8182
P.Infixr -> "right-associative"
8283
P.Infix -> "non-associative"
8384

84-
childToString :: First -> RenderedChildDeclaration -> String
85-
childToString f RenderedChildDeclaration{..} =
86-
case rcdInfo of
87-
ChildDataConstructor sig _ ->
85+
childToString :: First -> ChildDeclaration -> String
86+
childToString f decl@ChildDeclaration{..} =
87+
case cdeclInfo of
88+
ChildDataConstructor _ ->
8889
let c = if f == First then "=" else "|"
89-
in " " ++ c ++ " " ++ codeToString sig
90-
ChildTypeClassMember ty _ ->
91-
" " ++ codeToString ty
92-
ChildInstance code ->
93-
codeToString code
90+
in " " ++ c ++ " " ++ str
91+
ChildTypeClassMember _ ->
92+
" " ++ str
93+
ChildInstance _ _ ->
94+
str
95+
where
96+
str = codeToString $ Render.renderChildDeclaration decl
9497

9598
data First
9699
= First
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
{-# LANGUAGE TupleSections #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE RecordWildCards #-}
4+
{-# LANGUAGE ViewPatterns #-}
5+
6+
-- | Functions for converting PureScript ASTs into values of the data types
7+
-- from Language.PureScript.Docs.
8+
9+
module Language.PureScript.Docs.Convert
10+
( convertModule
11+
, collectBookmarks
12+
) where
13+
14+
import Control.Monad
15+
import Control.Category ((>>>))
16+
import Data.Either
17+
import Data.Maybe (mapMaybe, isNothing)
18+
import Data.List (nub, isPrefixOf, isSuffixOf)
19+
20+
import qualified Language.PureScript as P
21+
22+
import Language.PureScript.Docs.Types
23+
24+
-- |
25+
-- Convert a single Module.
26+
--
27+
convertModule :: P.Module -> Module
28+
convertModule m@(P.Module coms moduleName _ _) =
29+
Module (show moduleName) comments (declarations m)
30+
where
31+
comments = convertComments coms
32+
declarations =
33+
P.exportedDeclarations
34+
>>> mapMaybe (\d -> getDeclarationTitle d >>= convertDeclaration d)
35+
>>> augmentDeclarations
36+
>>> map addDefaultFixity
37+
38+
-- | The data type for an intermediate stage which we go through during
39+
-- converting.
40+
--
41+
-- In the first pass, we take all top level declarations in the module, and
42+
-- collect other information which will later be used to augment the top level
43+
-- declarations. These two situation correspond to the Right and Left
44+
-- constructors, respectively.
45+
--
46+
-- In the second pass, we go over all of the Left values and augment the
47+
-- relevant declarations, leaving only the augmented Right values.
48+
--
49+
-- Note that in the Left case, we provide a [String] as well as augment
50+
-- information. The [String] value should be a list of titles of declarations
51+
-- that the augmentation should apply to. For example, for a type instance
52+
-- declaration, that would be any types or type classes mentioned in the
53+
-- instance. For a fixity declaration, it would be just the relevant operator's
54+
-- name.
55+
type IntermediateDeclaration
56+
= Either ([String], DeclarationAugment) Declaration
57+
58+
-- | Some data which will be used to augment a Declaration in the
59+
-- output.
60+
--
61+
-- The AugmentChild constructor allows us to move all children under their
62+
-- respective parents. It is only necessary for type instance declarations,
63+
-- since they appear at the top level in the AST, and since they might need to
64+
-- appear as children in two places (for example, if a data type defined in a
65+
-- module is an instance of a type class also defined in that module).
66+
--
67+
-- The AugmentFixity constructor allows us to augment operator definitions
68+
-- with their associativity and precedence.
69+
data DeclarationAugment
70+
= AugmentChild ChildDeclaration
71+
| AugmentFixity P.Fixity
72+
73+
-- | Augment top-level declarations; the second pass. See the comments under
74+
-- the type synonym IntermediateDeclaration for more information.
75+
augmentDeclarations :: [IntermediateDeclaration] -> [Declaration]
76+
augmentDeclarations (partitionEithers -> (augments, toplevels)) =
77+
foldl go toplevels augments
78+
where
79+
go ds (parentTitles, a) =
80+
map (\d ->
81+
if declTitle d `elem` parentTitles
82+
then augmentWith a d
83+
else d) ds
84+
85+
augmentWith a d =
86+
case a of
87+
AugmentChild child ->
88+
d { declChildren = declChildren d ++ [child] }
89+
AugmentFixity fixity ->
90+
d { declFixity = Just fixity }
91+
92+
-- | Add the default operator fixity for operators which do not have associated
93+
-- fixity declarations.
94+
--
95+
-- TODO: This may no longer be necessary after issue 806 is resolved, hopefully
96+
-- in 0.8.
97+
addDefaultFixity :: Declaration -> Declaration
98+
addDefaultFixity decl@Declaration{..}
99+
| isOp declTitle && isNothing declFixity =
100+
decl { declFixity = Just defaultFixity }
101+
| otherwise =
102+
decl
103+
where
104+
isOp :: String -> Bool
105+
isOp str = "(" `isPrefixOf` str && ")" `isSuffixOf` str
106+
defaultFixity = P.Fixity P.Infixl (-1)
107+
108+
getDeclarationTitle :: P.Declaration -> Maybe String
109+
getDeclarationTitle (P.TypeDeclaration name _) = Just (show name)
110+
getDeclarationTitle (P.ExternDeclaration name _) = Just (show name)
111+
getDeclarationTitle (P.DataDeclaration _ name _ _) = Just (show name)
112+
getDeclarationTitle (P.ExternDataDeclaration name _) = Just (show name)
113+
getDeclarationTitle (P.TypeSynonymDeclaration name _ _) = Just (show name)
114+
getDeclarationTitle (P.TypeClassDeclaration name _ _ _) = Just (show name)
115+
getDeclarationTitle (P.TypeInstanceDeclaration name _ _ _ _) = Just (show name)
116+
getDeclarationTitle (P.FixityDeclaration _ name) = Just ("(" ++ name ++ ")")
117+
getDeclarationTitle (P.PositionedDeclaration _ _ d) = getDeclarationTitle d
118+
getDeclarationTitle _ = Nothing
119+
120+
-- | Create a basic Declaration value.
121+
mkDeclaration :: String -> DeclarationInfo -> Declaration
122+
mkDeclaration title info =
123+
Declaration { declTitle = title
124+
, declComments = Nothing
125+
, declSourceSpan = Nothing
126+
, declChildren = []
127+
, declFixity = Nothing
128+
, declInfo = info
129+
}
130+
131+
basicDeclaration :: String -> DeclarationInfo -> Maybe IntermediateDeclaration
132+
basicDeclaration title info = Just $ Right $ mkDeclaration title info
133+
134+
convertDeclaration :: P.Declaration -> String -> Maybe IntermediateDeclaration
135+
convertDeclaration (P.TypeDeclaration _ ty) title =
136+
basicDeclaration title (ValueDeclaration ty)
137+
convertDeclaration (P.ExternDeclaration _ ty) title =
138+
basicDeclaration title (ValueDeclaration ty)
139+
convertDeclaration (P.DataDeclaration dtype _ args ctors) title =
140+
Just (Right (mkDeclaration title info) { declChildren = children })
141+
where
142+
info = DataDeclaration dtype args
143+
children = map convertCtor ctors
144+
convertCtor (ctor', tys) =
145+
ChildDeclaration (show ctor') Nothing Nothing (ChildDataConstructor tys)
146+
convertDeclaration (P.ExternDataDeclaration _ kind') title =
147+
basicDeclaration title (ExternDataDeclaration kind')
148+
convertDeclaration (P.TypeSynonymDeclaration _ args ty) title =
149+
basicDeclaration title (TypeSynonymDeclaration args ty)
150+
convertDeclaration (P.TypeClassDeclaration _ args implies ds) title = do
151+
Just (Right (mkDeclaration title info) { declChildren = children })
152+
where
153+
info = TypeClassDeclaration args implies
154+
children = map convertClassMember ds
155+
convertClassMember (P.PositionedDeclaration _ _ d) =
156+
convertClassMember d
157+
convertClassMember (P.TypeDeclaration ident' ty) =
158+
ChildDeclaration (show ident') Nothing Nothing (ChildTypeClassMember ty)
159+
convertClassMember _ =
160+
error "Invalid argument to convertClassMember."
161+
convertDeclaration (P.TypeInstanceDeclaration _ constraints className tys _) title = do
162+
Just (Left (classNameString : typeNameStrings, AugmentChild childDecl))
163+
where
164+
classNameString = unQual className
165+
typeNameStrings = nub (concatMap (P.everythingOnTypes (++) extractProperNames) tys)
166+
unQual x = let (P.Qualified _ y) = x in show y
167+
168+
extractProperNames (P.TypeConstructor n) = [unQual n]
169+
extractProperNames (P.SaturatedTypeSynonym n _) = [unQual n]
170+
extractProperNames _ = []
171+
172+
childDecl = ChildDeclaration title Nothing Nothing (ChildInstance constraints classApp)
173+
classApp = foldl P.TypeApp (P.TypeConstructor className) tys
174+
convertDeclaration (P.FixityDeclaration fixity _) title =
175+
Just (Left ([title], AugmentFixity fixity))
176+
convertDeclaration (P.PositionedDeclaration srcSpan com d') title =
177+
fmap (addComments . addSourceSpan) (convertDeclaration d' title)
178+
where
179+
addComments (Right d) =
180+
Right (d { declComments = convertComments com })
181+
addComments (Left augment) =
182+
Left (withAugmentChild (\d -> d { cdeclComments = convertComments com })
183+
augment)
184+
185+
addSourceSpan (Right d) =
186+
Right (d { declSourceSpan = Just srcSpan })
187+
addSourceSpan (Left augment) =
188+
Left (withAugmentChild (\d -> d { cdeclSourceSpan = Just srcSpan })
189+
augment)
190+
191+
withAugmentChild f (t, a) =
192+
case a of
193+
AugmentChild d -> (t, AugmentChild (f d))
194+
_ -> (t, a)
195+
convertDeclaration _ _ = Nothing
196+
197+
convertComments :: [P.Comment] -> Maybe String
198+
convertComments cs = do
199+
let raw = concatMap toLines cs
200+
guard (all hasPipe raw && not (null raw))
201+
return (go raw)
202+
where
203+
go = unlines . map stripPipes
204+
205+
toLines (P.LineComment s) = [s]
206+
toLines (P.BlockComment s) = lines s
207+
208+
hasPipe s = case dropWhile (== ' ') s of { ('|':_) -> True; _ -> False }
209+
210+
stripPipes = dropPipe . dropWhile (== ' ')
211+
212+
dropPipe ('|':' ':s) = s
213+
dropPipe ('|':s) = s
214+
dropPipe s = s
215+
216+
-- | Go through a PureScript module and extract a list of Bookmarks; references
217+
-- to data types or values, to be used as a kind of index. These are used for
218+
-- generating links in the HTML documentation, for example.
219+
collectBookmarks :: InPackage P.Module -> [Bookmark]
220+
collectBookmarks (Local m) = map Local (collectBookmarks' m)
221+
collectBookmarks (FromDep pkg m) = map (FromDep pkg) (collectBookmarks' m)
222+
223+
collectBookmarks' :: P.Module -> [(P.ModuleName, String)]
224+
collectBookmarks' m =
225+
map (P.getModuleName m, )
226+
(mapMaybe getDeclarationTitle
227+
(P.exportedDeclarations m))
228+

src/Language/PureScript/Docs/ParseAndDesugar.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import Web.Bower.PackageMeta (PackageName)
1919
import qualified Language.PureScript as P
2020
import qualified Language.PureScript.Constants as C
2121
import Language.PureScript.Docs.Types
22-
import Language.PureScript.Docs.Render
22+
import Language.PureScript.Docs.Convert (collectBookmarks)
2323

2424
data ParseDesugarError
2525
= ParseError P.MultipleErrors

0 commit comments

Comments
 (0)