{-# LANGUAGE TupleSections #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} -- | Functions for converting PureScript ASTs into values of the data types -- from Language.PureScript.Docs. module Language.PureScript.Docs.Convert ( convertModule , collectBookmarks ) where import Control.Monad import Control.Category ((>>>)) import Data.Either import Data.Maybe (mapMaybe, isNothing) import Data.List (nub, isPrefixOf, isSuffixOf) import qualified Language.PureScript as P import Language.PureScript.Docs.Types -- | -- Convert a single Module. -- convertModule :: P.Module -> Module convertModule m@(P.Module coms moduleName _ _) = Module (show moduleName) comments (declarations m) where comments = convertComments coms declarations = P.exportedDeclarations >>> mapMaybe (\d -> getDeclarationTitle d >>= convertDeclaration d) >>> augmentDeclarations >>> map addDefaultFixity -- | The data type for an intermediate stage which we go through during -- converting. -- -- In the first pass, we take all top level declarations in the module, and -- collect other information which will later be used to augment the top level -- declarations. These two situation correspond to the Right and Left -- constructors, respectively. -- -- In the second pass, we go over all of the Left values and augment the -- relevant declarations, leaving only the augmented Right values. -- -- Note that in the Left case, we provide a [String] as well as augment -- information. The [String] value should be a list of titles of declarations -- that the augmentation should apply to. For example, for a type instance -- declaration, that would be any types or type classes mentioned in the -- instance. For a fixity declaration, it would be just the relevant operator's -- name. type IntermediateDeclaration = Either ([String], DeclarationAugment) Declaration -- | Some data which will be used to augment a Declaration in the -- output. -- -- The AugmentChild constructor allows us to move all children under their -- respective parents. It is only necessary for type instance declarations, -- since they appear at the top level in the AST, and since they might need to -- appear as children in two places (for example, if a data type defined in a -- module is an instance of a type class also defined in that module). -- -- The AugmentFixity constructor allows us to augment operator definitions -- with their associativity and precedence. data DeclarationAugment = AugmentChild ChildDeclaration | AugmentFixity P.Fixity -- | Augment top-level declarations; the second pass. See the comments under -- the type synonym IntermediateDeclaration for more information. augmentDeclarations :: [IntermediateDeclaration] -> [Declaration] augmentDeclarations (partitionEithers -> (augments, toplevels)) = foldl go toplevels augments where go ds (parentTitles, a) = map (\d -> if declTitle d `elem` parentTitles then augmentWith a d else d) ds augmentWith a d = case a of AugmentChild child -> d { declChildren = declChildren d ++ [child] } AugmentFixity fixity -> d { declFixity = Just fixity } -- | Add the default operator fixity for operators which do not have associated -- fixity declarations. -- -- TODO: This may no longer be necessary after issue 806 is resolved, hopefully -- in 0.8. addDefaultFixity :: Declaration -> Declaration addDefaultFixity decl@Declaration{..} | isOp declTitle && isNothing declFixity = decl { declFixity = Just defaultFixity } | otherwise = decl where isOp :: String -> Bool isOp str = "(" `isPrefixOf` str && ")" `isSuffixOf` str defaultFixity = P.Fixity P.Infixl (-1) getDeclarationTitle :: P.Declaration -> Maybe String getDeclarationTitle (P.TypeDeclaration name _) = Just (show name) getDeclarationTitle (P.ExternDeclaration name _) = Just (show name) getDeclarationTitle (P.DataDeclaration _ name _ _) = Just (show name) getDeclarationTitle (P.ExternDataDeclaration name _) = Just (show name) getDeclarationTitle (P.TypeSynonymDeclaration name _ _) = Just (show name) getDeclarationTitle (P.TypeClassDeclaration name _ _ _) = Just (show name) getDeclarationTitle (P.TypeInstanceDeclaration name _ _ _ _) = Just (show name) getDeclarationTitle (P.FixityDeclaration _ name) = Just ("(" ++ name ++ ")") getDeclarationTitle (P.PositionedDeclaration _ _ d) = getDeclarationTitle d getDeclarationTitle _ = Nothing -- | Create a basic Declaration value. mkDeclaration :: String -> DeclarationInfo -> Declaration mkDeclaration title info = Declaration { declTitle = title , declComments = Nothing , declSourceSpan = Nothing , declChildren = [] , declFixity = Nothing , declInfo = info } basicDeclaration :: String -> DeclarationInfo -> Maybe IntermediateDeclaration basicDeclaration title info = Just $ Right $ mkDeclaration title info convertDeclaration :: P.Declaration -> String -> Maybe IntermediateDeclaration convertDeclaration (P.TypeDeclaration _ ty) title = basicDeclaration title (ValueDeclaration ty) convertDeclaration (P.ExternDeclaration _ ty) title = basicDeclaration title (ValueDeclaration ty) convertDeclaration (P.DataDeclaration dtype _ args ctors) title = Just (Right (mkDeclaration title info) { declChildren = children }) where info = DataDeclaration dtype args children = map convertCtor ctors convertCtor (ctor', tys) = ChildDeclaration (show ctor') Nothing Nothing (ChildDataConstructor tys) convertDeclaration (P.ExternDataDeclaration _ kind') title = basicDeclaration title (ExternDataDeclaration kind') convertDeclaration (P.TypeSynonymDeclaration _ args ty) title = basicDeclaration title (TypeSynonymDeclaration args ty) convertDeclaration (P.TypeClassDeclaration _ args implies ds) title = do Just (Right (mkDeclaration title info) { declChildren = children }) where info = TypeClassDeclaration args implies children = map convertClassMember ds convertClassMember (P.PositionedDeclaration _ _ d) = convertClassMember d convertClassMember (P.TypeDeclaration ident' ty) = ChildDeclaration (show ident') Nothing Nothing (ChildTypeClassMember ty) convertClassMember _ = error "Invalid argument to convertClassMember." convertDeclaration (P.TypeInstanceDeclaration _ constraints className tys _) title = do Just (Left (classNameString : typeNameStrings, AugmentChild childDecl)) where classNameString = unQual className typeNameStrings = nub (concatMap (P.everythingOnTypes (++) extractProperNames) tys) unQual x = let (P.Qualified _ y) = x in show y extractProperNames (P.TypeConstructor n) = [unQual n] extractProperNames (P.SaturatedTypeSynonym n _) = [unQual n] extractProperNames _ = [] childDecl = ChildDeclaration title Nothing Nothing (ChildInstance constraints classApp) classApp = foldl P.TypeApp (P.TypeConstructor className) tys convertDeclaration (P.FixityDeclaration fixity _) title = Just (Left ([title], AugmentFixity fixity)) convertDeclaration (P.PositionedDeclaration srcSpan com d') title = fmap (addComments . addSourceSpan) (convertDeclaration d' title) where addComments (Right d) = Right (d { declComments = convertComments com }) addComments (Left augment) = Left (withAugmentChild (\d -> d { cdeclComments = convertComments com }) augment) addSourceSpan (Right d) = Right (d { declSourceSpan = Just srcSpan }) addSourceSpan (Left augment) = Left (withAugmentChild (\d -> d { cdeclSourceSpan = Just srcSpan }) augment) withAugmentChild f (t, a) = case a of AugmentChild d -> (t, AugmentChild (f d)) _ -> (t, a) convertDeclaration _ _ = Nothing convertComments :: [P.Comment] -> Maybe String convertComments cs = do let raw = concatMap toLines cs guard (all hasPipe raw && not (null raw)) return (go raw) where go = unlines . map stripPipes toLines (P.LineComment s) = [s] toLines (P.BlockComment s) = lines s hasPipe s = case dropWhile (== ' ') s of { ('|':_) -> True; _ -> False } stripPipes = dropPipe . dropWhile (== ' ') dropPipe ('|':' ':s) = s dropPipe ('|':s) = s dropPipe s = s -- | Go through a PureScript module and extract a list of Bookmarks; references -- to data types or values, to be used as a kind of index. These are used for -- generating links in the HTML documentation, for example. collectBookmarks :: InPackage P.Module -> [Bookmark] collectBookmarks (Local m) = map Local (collectBookmarks' m) collectBookmarks (FromDep pkg m) = map (FromDep pkg) (collectBookmarks' m) collectBookmarks' :: P.Module -> [(P.ModuleName, String)] collectBookmarks' m = map (P.getModuleName m, ) (mapMaybe getDeclarationTitle (P.exportedDeclarations m))