diff options
author | Isaac Dupree <id@isaac.cedarswampstudios.org> | 2009-08-23 03:08:03 +0000 |
---|---|---|
committer | Isaac Dupree <id@isaac.cedarswampstudios.org> | 2009-08-23 03:08:03 +0000 |
commit | 70945271f1f4deab363c1acfef3ce51a1b7f617d (patch) | |
tree | 81d518f2ca4dde1a1c72072b8a67290de2952388 /src | |
parent | 9dc98d20af5bbcb8bff7624b3d8c4d840ed6bb4e (diff) |
Move doc parsing/lexing into Haddock for ghc>=6.11
Diffstat (limited to 'src')
-rw-r--r-- | src/Haddock/GhcUtils.hs | 13 | ||||
-rw-r--r-- | src/Haddock/HsDoc.hs | 73 | ||||
-rw-r--r-- | src/Haddock/Interface.hs | 8 | ||||
-rw-r--r-- | src/Haddock/Interface/Create.hs | 140 | ||||
-rw-r--r-- | src/Haddock/Interface/Lex.x | 171 | ||||
-rw-r--r-- | src/Haddock/Interface/LexParseRn.hs | 89 | ||||
-rw-r--r-- | src/Haddock/Interface/Parse.y | 106 | ||||
-rw-r--r-- | src/Haddock/Interface/ParseModuleHeader.hs | 158 | ||||
-rw-r--r-- | src/Haddock/Interface/Rename.hs | 13 | ||||
-rw-r--r-- | src/Haddock/Interface/Rn.hs | 82 | ||||
-rw-r--r-- | src/Haddock/ModuleTree.hs | 4 | ||||
-rw-r--r-- | src/Haddock/Types.hs | 62 | ||||
-rw-r--r-- | src/Main.hs | 8 |
13 files changed, 867 insertions, 60 deletions
diff --git a/src/Haddock/GhcUtils.hs b/src/Haddock/GhcUtils.hs index e0202ed6..62b370bb 100644 --- a/src/Haddock/GhcUtils.hs +++ b/src/Haddock/GhcUtils.hs @@ -23,12 +23,14 @@ import Control.Arrow import Data.Foldable hiding (concatMap) import Data.Traversable -import HsSyn -import SrcLoc import Outputable import Name import Packages import Module +import RdrName (GlobalRdrEnv) +import HscTypes +import LazyUniqFM +import GHC moduleString :: Module -> String @@ -56,6 +58,13 @@ mkModuleNoPackage :: String -> Module mkModuleNoPackage str = mkModule (stringToPackageId "") (mkModuleName str) +lookupLoadedHomeModuleGRE :: GhcMonad m => ModuleName -> m (Maybe GlobalRdrEnv) +lookupLoadedHomeModuleGRE mod_name = withSession $ \hsc_env -> + case lookupUFM (hsc_HPT hsc_env) mod_name of + Just mod_info -> return (mi_globals (hm_iface mod_info)) + _not_a_home_module -> return Nothing + + instance (Outputable a, Outputable b) => Outputable (Map.Map a b) where ppr m = ppr (Map.toList m) diff --git a/src/Haddock/HsDoc.hs b/src/Haddock/HsDoc.hs new file mode 100644 index 00000000..489873a7 --- /dev/null +++ b/src/Haddock/HsDoc.hs @@ -0,0 +1,73 @@ +module Haddock.HsDoc ( + docAppend, + docParagraph + ) where + +#if __GLASGOW_HASKELL__ <= 610 + +import HsDoc -- just re-export + +#else + +import Haddock.Types + +import Data.Char (isSpace) + + +-- used to make parsing easier; we group the list items later +docAppend :: HsDoc id -> HsDoc id -> HsDoc id +docAppend (DocUnorderedList ds1) (DocUnorderedList ds2) + = DocUnorderedList (ds1++ds2) +docAppend (DocUnorderedList ds1) (DocAppend (DocUnorderedList ds2) d) + = DocAppend (DocUnorderedList (ds1++ds2)) d +docAppend (DocOrderedList ds1) (DocOrderedList ds2) + = DocOrderedList (ds1++ds2) +docAppend (DocOrderedList ds1) (DocAppend (DocOrderedList ds2) d) + = DocAppend (DocOrderedList (ds1++ds2)) d +docAppend (DocDefList ds1) (DocDefList ds2) + = DocDefList (ds1++ds2) +docAppend (DocDefList ds1) (DocAppend (DocDefList ds2) d) + = DocAppend (DocDefList (ds1++ds2)) d +docAppend DocEmpty d = d +docAppend d DocEmpty = d +docAppend d1 d2 + = DocAppend d1 d2 + +-- again to make parsing easier - we spot a paragraph whose only item +-- is a DocMonospaced and make it into a DocCodeBlock +docParagraph :: HsDoc id -> HsDoc id +docParagraph (DocMonospaced p) + = DocCodeBlock (docCodeBlock p) +docParagraph (DocAppend (DocString s1) (DocMonospaced p)) + | all isSpace s1 + = DocCodeBlock (docCodeBlock p) +docParagraph (DocAppend (DocString s1) + (DocAppend (DocMonospaced p) (DocString s2))) + | all isSpace s1 && all isSpace s2 + = DocCodeBlock (docCodeBlock p) +docParagraph (DocAppend (DocMonospaced p) (DocString s2)) + | all isSpace s2 + = DocCodeBlock (docCodeBlock p) +docParagraph p + = DocParagraph p + + +-- Drop trailing whitespace from @..@ code blocks. Otherwise this: +-- +-- -- @ +-- -- foo +-- -- @ +-- +-- turns into (DocCodeBlock "\nfoo\n ") which when rendered in HTML +-- gives an extra vertical space after the code block. The single space +-- on the final line seems to trigger the extra vertical space. +-- +docCodeBlock :: HsDoc id -> HsDoc id +docCodeBlock (DocString s) + = DocString (reverse $ dropWhile (`elem` " \t") $ reverse s) +docCodeBlock (DocAppend l r) + = DocAppend l (docCodeBlock r) +docCodeBlock d = d + +#endif + diff --git a/src/Haddock/Interface.hs b/src/Haddock/Interface.hs index 7bcd91d6..7ba2467e 100644 --- a/src/Haddock/Interface.hs +++ b/src/Haddock/Interface.hs @@ -180,8 +180,7 @@ mkGhcModule (mdl, file, checkedMod) dynflags = GhcModule { ghcModule = mdl, ghcFilename = file, ghcMbDocOpts = mbOpts, - ghcHaddockModInfo = info, - ghcMbDoc = mbDoc, + ghcMbDocHdr = mbDocHdr, ghcGroup = group_, ghcMbExports = mbExports, ghcExportedNames = modInfoExports modInfo, @@ -191,7 +190,12 @@ mkGhcModule (mdl, file, checkedMod) dynflags = GhcModule { } where mbOpts = haddockOptions dynflags +#if __GLASGOW_HASKELL__ >= 611 + (group_, _, mbExports, mbDocHdr) = renamed +#else (group_, _, mbExports, mbDoc, info) = renamed + mbDocHdr = (info, mbDoc) +#endif (_, renamed, _, modInfo) = checkedMod diff --git a/src/Haddock/Interface/Create.hs b/src/Haddock/Interface/Create.hs index 62960360..41028100 100644 --- a/src/Haddock/Interface/Create.hs +++ b/src/Haddock/Interface/Create.hs @@ -18,6 +18,7 @@ import Haddock.Options import Haddock.GhcUtils import Haddock.Utils import Haddock.Convert +import Haddock.Interface.LexParseRn import qualified Data.Map as Map import Data.Map (Map) @@ -29,6 +30,7 @@ import Control.Monad import GHC hiding (flags) import Name import Bag +import RdrName (GlobalRdrEnv) -- | Process the data in the GhcModule to produce an interface. @@ -40,24 +42,29 @@ createInterface ghcMod flags modMap instIfaceMap = do let mdl = ghcModule ghcMod + -- The pattern-match should not fail, because createInterface is only + -- done on loaded modules. + Just gre <- liftGhcToErrMsgGhc $ lookupLoadedHomeModuleGRE (moduleName mdl) + opts0 <- liftErrMsg $ mkDocOpts (ghcMbDocOpts ghcMod) flags mdl let opts | Flag_IgnoreAllExports `elem` flags = OptIgnoreExports : opts0 | otherwise = opts0 - let group_ = ghcGroup ghcMod + (info, mbDoc) <- liftErrMsg $ lexParseRnHaddockModHeader + gre (ghcMbDocHdr ghcMod) + decls0 <- liftErrMsg $ declInfos gre (topDecls (ghcGroup ghcMod)) + let decls = filterOutInstances decls0 + declMap = mkDeclMap decls exports = fmap (reverse . map unLoc) (ghcMbExports ghcMod) localNames = ghcDefinedNames ghcMod - decls0 = declInfos . topDecls $ group_ - decls = filterOutInstances decls0 - declMap = mkDeclMap decls ignoreExps = Flag_IgnoreAllExports `elem` flags exportedNames = ghcExportedNames ghcMod instances = ghcInstances ghcMod liftErrMsg $ warnAboutFilteredDecls mdl decls0 - exportItems <- mkExportItems modMap mdl (ghcExportedNames ghcMod) decls declMap + exportItems <- mkExportItems modMap mdl gre (ghcExportedNames ghcMod) decls declMap opts exports ignoreExps instances instIfaceMap let visibleNames = mkVisibleNames exportItems opts @@ -72,8 +79,8 @@ createInterface ghcMod flags modMap instIfaceMap = do return Interface { ifaceMod = mdl, ifaceOrigFilename = ghcFilename ghcMod, - ifaceInfo = ghcHaddockModInfo ghcMod, - ifaceDoc = ghcMbDoc ghcMod, + ifaceInfo = info, + ifaceDoc = mbDoc, ifaceRnDoc = Nothing, ifaceOptions = opts, ifaceLocals = localNames, @@ -139,17 +146,27 @@ mkDeclMap decls = Map.fromList . concat $ , not (isDocD d), not (isInstD d) ] -declInfos :: [(Decl, Maybe Doc)] -> [DeclInfo] -declInfos decls = [ (parent, doc, subordinates d) - | (parent@(L _ d), doc) <- decls] +declInfos :: GlobalRdrEnv -> [(Decl, MaybeDocStrings)] -> ErrMsgM [DeclInfo] +declInfos gre decls = + forM decls $ \(parent@(L _ d), mbDocString) -> do + mbDoc <- lexParseRnHaddockCommentList NormalHaddockComment + gre mbDocString + let subsStringy = subordinates d + subs <- forM subsStringy $ \(subName, mbSubDocString) -> do + mbSubDoc <- lexParseRnHaddockCommentList NormalHaddockComment + gre mbSubDocString + return (subName, mbSubDoc) -subordinates :: HsDecl Name -> [(Name, Maybe Doc)] + return (parent, mbDoc, subs) + + +subordinates :: HsDecl Name -> [(Name, MaybeDocStrings)] subordinates (TyClD d) = classDataSubs d subordinates _ = [] -classDataSubs :: TyClDecl Name -> [(Name, Maybe Doc)] +classDataSubs :: TyClDecl Name -> [(Name, MaybeDocStrings)] classDataSubs decl | isClassDecl decl = classSubs | isDataDecl decl = dataSubs @@ -159,15 +176,16 @@ classDataSubs decl dataSubs = constrs ++ fields where cons = map unL $ tcdCons decl - constrs = [ (unL $ con_name c, fmap unL $ con_doc c) | c <- cons ] - fields = [ (unL n, fmap unL doc) + constrs = [ (unL $ con_name c, maybeToList $ fmap unL $ con_doc c) + | c <- cons ] + fields = [ (unL n, maybeToList $ fmap unL doc) | RecCon flds <- map con_details cons , ConDeclField n _ doc <- flds ] -- All the sub declarations of a class (that we handle), ordered by -- source location, with documentation attached if it exists. -classDecls :: TyClDecl Name -> [(Decl, Maybe Doc)] +classDecls :: TyClDecl Name -> [(Decl, MaybeDocStrings)] classDecls = filterDecls . collectDocs . sortByLoc . declsFromClass @@ -190,7 +208,7 @@ declName _ = error "unexpected argument to declName" -- | The top-level declarations of a module that we care about, -- ordered by source location, with documentation attached if it exists. -topDecls :: HsGroup Name -> [(Decl, Maybe Doc)] +topDecls :: HsGroup Name -> [(Decl, MaybeDocStrings)] topDecls = filterClasses . filterDecls . collectDocs . sortByLoc . declsFromGroup @@ -255,7 +273,7 @@ warnAboutFilteredDecls mdl decls = do -- | Filter out declarations that we don't handle in Haddock -filterDecls :: [(Decl, Maybe Doc)] -> [(Decl, Maybe Doc)] +filterDecls :: [(Decl, doc)] -> [(Decl, doc)] filterDecls decls = filter (isHandled . unL . fst) decls where isHandled (ForD (ForeignImport {})) = True @@ -268,7 +286,7 @@ filterDecls decls = filter (isHandled . unL . fst) decls -- | Go through all class declarations and filter their sub-declarations -filterClasses :: [(Decl, Maybe Doc)] -> [(Decl, Maybe Doc)] +filterClasses :: [(Decl, doc)] -> [(Decl, doc)] filterClasses decls = [ if isClassD d then (L loc (filterClass d), doc) else x | x@(L loc d, doc) <- decls ] where @@ -285,12 +303,25 @@ filterClasses decls = [ if isClassD d then (L loc (filterClass d), doc) else x -- declaration. -------------------------------------------------------------------------------- +type MaybeDocStrings = [HsDocString] +-- avoid [] because we're appending from the left (quadratic), +-- and avoid adding another package dependency for haddock, +-- so use the difference-list pattern +type MaybeDocStringsFast = MaybeDocStrings -> MaybeDocStrings +docStringEmpty :: MaybeDocStringsFast +docStringEmpty = id +docStringSingleton :: HsDocString -> MaybeDocStringsFast +docStringSingleton = (:) +docStringAppend :: MaybeDocStringsFast -> MaybeDocStringsFast -> MaybeDocStringsFast +docStringAppend = (.) +docStringToList :: MaybeDocStringsFast -> MaybeDocStrings +docStringToList = ($ []) -- | Collect the docs and attach them to the right declaration. -collectDocs :: [Decl] -> [(Decl, (Maybe Doc))] -collectDocs = collect Nothing DocEmpty +collectDocs :: [Decl] -> [(Decl, MaybeDocStrings)] +collectDocs = collect Nothing docStringEmpty -collect :: Maybe Decl -> Doc -> [Decl] -> [(Decl, (Maybe Doc))] +collect :: Maybe Decl -> MaybeDocStringsFast -> [Decl] -> [(Decl, MaybeDocStrings)] collect d doc_so_far [] = case d of Nothing -> [] @@ -300,19 +331,24 @@ collect d doc_so_far (e:es) = case e of L _ (DocD (DocCommentNext str)) -> case d of - Nothing -> collect d (docAppend doc_so_far str) es - Just d0 -> finishedDoc d0 doc_so_far (collect Nothing str es) + Nothing -> collect d + (docStringAppend doc_so_far (docStringSingleton str)) + es + Just d0 -> finishedDoc d0 doc_so_far (collect Nothing + (docStringSingleton str) + es) - L _ (DocD (DocCommentPrev str)) -> collect d (docAppend doc_so_far str) es + L _ (DocD (DocCommentPrev str)) -> collect d + (docStringAppend doc_so_far (docStringSingleton str)) + es _ -> case d of Nothing -> collect (Just e) doc_so_far es - Just d0 -> finishedDoc d0 doc_so_far (collect (Just e) DocEmpty es) + Just d0 -> finishedDoc d0 doc_so_far (collect (Just e) docStringEmpty es) -finishedDoc :: Decl -> Doc -> [(Decl, (Maybe Doc))] -> [(Decl, (Maybe Doc))] -finishedDoc d DocEmpty rest = (d, Nothing) : rest -finishedDoc d doc rest | notDocDecl d = (d, Just doc) : rest +finishedDoc :: Decl -> MaybeDocStringsFast -> [(Decl, MaybeDocStrings)] -> [(Decl, MaybeDocStrings)] +finishedDoc d doc rest | notDocDecl d = (d, docStringToList doc) : rest where notDocDecl (L _ (DocD _)) = False notDocDecl _ = True @@ -336,6 +372,7 @@ attachATs exports = mkExportItems :: ModuleMap -> Module -- this module + -> GlobalRdrEnv -> [Name] -- exported names (orig) -> [DeclInfo] -> Map Name DeclInfo -- maps local names to declarations @@ -346,7 +383,7 @@ mkExportItems -> InstIfaceMap -> ErrMsgGhc [ExportItem Name] -mkExportItems modMap this_mod exported_names decls declMap +mkExportItems modMap this_mod gre exported_names decls declMap opts maybe_exps ignore_all_exports _ instIfaceMap | isNothing maybe_exps || ignore_all_exports || OptIgnoreExports `elem` opts = everything_local_exported @@ -357,7 +394,7 @@ mkExportItems modMap this_mod exported_names decls declMap -- instances = [ d | d@(L _ decl, _, _) <- decls, isInstD decl ] everything_local_exported = -- everything exported - return (fullContentsOfThisModule decls) + liftErrMsg $ fullContentsOfThisModule gre decls lookupExport (IEVar x) = declWith x @@ -371,13 +408,22 @@ mkExportItems modMap this_mod exported_names decls declMap lookupExport (IEThingAll t) = declWith t lookupExport (IEThingWith t _) = declWith t lookupExport (IEModuleContents m) = fullContentsOf m - lookupExport (IEGroup lev doc) = return [ ExportGroup lev "" doc ] - lookupExport (IEDoc doc) = return [ ExportDoc doc ] - lookupExport (IEDocNamed str) = do - r <- liftErrMsg $ findNamedDoc str [ unL d | (d,_,_) <- decls ] - case r of - Nothing -> return [] - Just found -> return [ ExportDoc found ] + lookupExport (IEGroup lev docStr) = liftErrMsg $ do + ifDoc (lexParseRnHaddockComment DocSectionComment gre docStr) + (\doc -> return [ ExportGroup lev "" doc ]) + lookupExport (IEDoc docStr) = liftErrMsg $ do + ifDoc (lexParseRnHaddockComment NormalHaddockComment gre docStr) + (\doc -> return [ ExportDoc doc ]) + lookupExport (IEDocNamed str) = liftErrMsg $ do + ifDoc (findNamedDoc str [ unL d | (d,_,_) <- decls ]) + (\docStr -> + ifDoc (lexParseRnHaddockComment NormalHaddockComment gre docStr) + (\doc -> return [ ExportDoc doc ])) + + ifDoc :: (Monad m) => m (Maybe a) -> (a -> m [b]) -> m [b] + ifDoc parse finish = do + mbDoc <- parse + case mbDoc of Nothing -> return []; Just doc -> finish doc declWith :: Name -> ErrMsgGhc [ ExportItem Name ] declWith t = @@ -460,7 +506,7 @@ mkExportItems modMap this_mod exported_names decls declMap isExported n = n `elem` exported_names fullContentsOf modname - | m == this_mod = return (fullContentsOfThisModule decls) + | m == this_mod = liftErrMsg $ fullContentsOfThisModule gre decls | otherwise = case Map.lookup m modMap of Just iface @@ -509,14 +555,16 @@ mkExportItems modMap this_mod exported_names decls declMap -- (For more information, see Trac #69) -fullContentsOfThisModule :: [DeclInfo] -> [ExportItem Name] -fullContentsOfThisModule decls = catMaybes (map mkExportItem decls) +fullContentsOfThisModule :: GlobalRdrEnv -> [DeclInfo] -> ErrMsgM [ExportItem Name] +fullContentsOfThisModule gre decls = liftM catMaybes $ mapM mkExportItem decls where - mkExportItem (L _ (DocD (DocGroup lev doc)), _, _) = Just $ ExportGroup lev "" doc - mkExportItem (L _ (DocD (DocCommentNamed _ doc)), _, _) = Just $ ExportDoc doc - mkExportItem (decl, doc, subs) = Just $ ExportDecl decl doc subs [] - --- mkExportItem _ = Nothing -- TODO: see if this is really needed + mkExportItem (L _ (DocD (DocGroup lev docStr)), _, _) = do + mbDoc <- lexParseRnHaddockComment DocSectionComment gre docStr + return $ fmap (\doc -> ExportGroup lev "" doc) mbDoc + mkExportItem (L _ (DocD (DocCommentNamed _ docStr)), _, _) = do + mbDoc <- lexParseRnHaddockComment NormalHaddockComment gre docStr + return $ fmap ExportDoc mbDoc + mkExportItem (decl, doc, subs) = return $ Just $ ExportDecl decl doc subs [] -- | Sometimes the declaration we want to export is not the "main" declaration: @@ -598,7 +646,7 @@ mkVisibleNames exports opts -- | Find a stand-alone documentation comment by its name -findNamedDoc :: String -> [HsDecl Name] -> ErrMsgM (Maybe Doc) +findNamedDoc :: String -> [HsDecl Name] -> ErrMsgM (Maybe HsDocString) findNamedDoc name decls = search decls where search [] = do diff --git a/src/Haddock/Interface/Lex.x b/src/Haddock/Interface/Lex.x new file mode 100644 index 00000000..5f93084a --- /dev/null +++ b/src/Haddock/Interface/Lex.x @@ -0,0 +1,171 @@ +-- +-- Haddock - A Haskell Documentation Tool +-- +-- (c) Simon Marlow 2002 +-- +-- This file was modified and integrated into GHC by David Waern 2006 +-- + +{ +{-# OPTIONS -Wwarn -w #-} +-- The above warning supression flag is a temporary kludge. +-- While working on this module you are encouraged to remove it and fix +-- any warnings in the module. See +-- http://hackage.haskell.org/trac/ghc/wiki/Commentary/CodingStyle#Warnings +-- for details + +module Haddock.Interface.Lex ( + Token(..), + tokenise + ) where + +import Lexer hiding (Token) +import Parser ( parseIdentifier ) +import StringBuffer +import RdrName +import SrcLoc +import DynFlags + +import Data.Char +import Numeric +import System.IO.Unsafe +} + +$ws = $white # \n +$digit = [0-9] +$hexdigit = [0-9a-fA-F] +$special = [\"\@] +$alphanum = [A-Za-z0-9] +$ident = [$alphanum \'\_\.\!\#\$\%\&\*\+\/\<\=\>\?\@\\\\\^\|\-\~] + +:- + +-- beginning of a paragraph +<0,para> { + $ws* \n ; + $ws* \> { begin birdtrack } + $ws* [\*\-] { token TokBullet `andBegin` string } + $ws* \[ { token TokDefStart `andBegin` def } + $ws* \( $digit+ \) { token TokNumber `andBegin` string } + $ws* { begin string } +} + +-- beginning of a line +<line> { + $ws* \> { begin birdtrack } + $ws* \n { token TokPara `andBegin` para } + -- Here, we really want to be able to say + -- $ws* (\n | <eof>) { token TokPara `andBegin` para} + -- because otherwise a trailing line of whitespace will result in + -- a spurious TokString at the end of a docstring. We don't have <eof>, + -- though (NOW I realise what it was for :-). To get around this, we always + -- append \n to the end of a docstring. + () { begin string } +} + +<birdtrack> .* \n? { strtokenNL TokBirdTrack `andBegin` line } + +<string,def> { + $special { strtoken $ \s -> TokSpecial (head s) } + \<\<.*\>\> { strtoken $ \s -> TokPic (init $ init $ tail $ tail s) } + \<.*\> { strtoken $ \s -> TokURL (init (tail s)) } + \#.*\# { strtoken $ \s -> TokAName (init (tail s)) } + \/ [^\/]* \/ { strtoken $ \s -> TokEmphasis (init (tail s)) } + [\'\`] $ident+ [\'\`] { ident } + \\ . { strtoken (TokString . tail) } + "&#" $digit+ \; { strtoken $ \s -> TokString [chr (read (init (drop 2 s)))] } + "&#" [xX] $hexdigit+ \; { strtoken $ \s -> case readHex (init (drop 3 s)) of [(n,_)] -> TokString [chr n] } + -- allow special characters through if they don't fit one of the previous + -- patterns. + [\/\'\`\<\#\&\\] { strtoken TokString } + [^ $special \/ \< \# \n \'\` \& \\ \]]* \n { strtokenNL TokString `andBegin` line } + [^ $special \/ \< \# \n \'\` \& \\ \]]+ { strtoken TokString } +} + +<def> { + \] { token TokDefEnd `andBegin` string } +} + +-- ']' doesn't have any special meaning outside of the [...] at the beginning +-- of a definition paragraph. +<string> { + \] { strtoken TokString } +} + +{ +data Token + = TokPara + | TokNumber + | TokBullet + | TokDefStart + | TokDefEnd + | TokSpecial Char + | TokIdent [RdrName] + | TokString String + | TokURL String + | TokPic String + | TokEmphasis String + | TokAName String + | TokBirdTrack String +-- deriving Show + +-- ----------------------------------------------------------------------------- +-- Alex support stuff + +type StartCode = Int +type Action = String -> StartCode -> (StartCode -> [Token]) -> [Token] + +type AlexInput = (Char,String) + +alexGetChar (_, []) = Nothing +alexGetChar (_, c:cs) = Just (c, (c,cs)) + +alexInputPrevChar (c,_) = c + +tokenise :: String -> [Token] +tokenise str = let toks = go ('\n', eofHack str) para in {-trace (show toks)-} toks + where go inp@(_,str) sc = + case alexScan inp sc of + AlexEOF -> [] + AlexError _ -> error "lexical error" + AlexSkip inp' _ -> go inp' sc + AlexToken inp' len act -> act (take len str) sc (\sc -> go inp' sc) + +-- NB. we add a final \n to the string, (see comment in the beginning of line +-- production above). +eofHack str = str++"\n" + +andBegin :: Action -> StartCode -> Action +andBegin act new_sc = \str _ cont -> act str new_sc cont + +token :: Token -> Action +token t = \_ sc cont -> t : cont sc + +strtoken, strtokenNL :: (String -> Token) -> Action +strtoken t = \str sc cont -> t str : cont sc +strtokenNL t = \str sc cont -> t (filter (/= '\r') str) : cont sc +-- ^ We only want LF line endings in our internal doc string format, so we +-- filter out all CRs. + +begin :: StartCode -> Action +begin sc = \_ _ cont -> cont sc + +-- ----------------------------------------------------------------------------- +-- Lex a string as a Haskell identifier + +ident :: Action +ident str sc cont = + case strToHsQNames id of + Just names -> TokIdent names : cont sc + Nothing -> TokString str : cont sc + where id = init (tail str) + +strToHsQNames :: String -> Maybe [RdrName] +strToHsQNames str0 = + let buffer = unsafePerformIO (stringToStringBuffer str0) + pstate = mkPState buffer noSrcLoc defaultDynFlags + result = unP parseIdentifier pstate + in case result of + POk _ name -> Just [unLoc name] + _ -> Nothing +} diff --git a/src/Haddock/Interface/LexParseRn.hs b/src/Haddock/Interface/LexParseRn.hs new file mode 100644 index 00000000..04464e77 --- /dev/null +++ b/src/Haddock/Interface/LexParseRn.hs @@ -0,0 +1,89 @@ + +----------------------------------------------------------------------------- +-- | +-- Module : Haddock.Interface.LexParseRn +-- Copyright : (c) Isaac Dupree 2009, +-- License : BSD-like +-- +-- Maintainer : haddock@projects.haskell.org +-- Stability : experimental +-- Portability : portable +----------------------------------------------------------------------------- + +module Haddock.Interface.LexParseRn ( + HaddockCommentType(..), + lexParseRnHaddockComment, + lexParseRnHaddockCommentList, + lexParseRnMbHaddockComment, + lexParseRnHaddockModHeader + ) where + +import Haddock.Types + +import Data.Maybe + +#if __GLASGOW_HASKELL__ >= 611 +import Haddock.Interface.Lex +import Haddock.Interface.Parse +import Haddock.Interface.Rn +import Haddock.Interface.ParseModuleHeader +import Haddock.HsDoc +import FastString +#endif + +import GHC +import RdrName + +data HaddockCommentType = NormalHaddockComment | DocSectionComment + +lexParseRnHaddockCommentList :: HaddockCommentType -> GlobalRdrEnv -> [HsDocString] -> ErrMsgM (Maybe (HsDoc Name)) +lexParseRnHaddockCommentList hty gre docStrs = do + docMbs <- mapM (lexParseRnHaddockComment hty gre) docStrs + let docs = catMaybes docMbs + let doc = foldl docAppend DocEmpty docs + case doc of + DocEmpty -> return Nothing + _ -> return (Just doc) + +lexParseRnHaddockComment :: HaddockCommentType -> + GlobalRdrEnv -> HsDocString -> ErrMsgM (Maybe (HsDoc Name)) +#if __GLASGOW_HASKELL__ >= 611 +lexParseRnHaddockComment hty gre (HsDocString fs) = do + let str = unpackFS fs + let toks = tokenise str + let parse = case hty of + NormalHaddockComment -> parseHaddockParagraphs + DocSectionComment -> parseHaddockString + case parse toks of + Nothing -> do + tell ["doc comment parse failed: "++str] + return Nothing + Just doc -> do + return (Just (rnHsDoc gre doc)) +#else +lexParseRnHaddockComment _ _ doc = return (Just doc) +#endif + +lexParseRnMbHaddockComment :: HaddockCommentType -> GlobalRdrEnv -> Maybe HsDocString -> ErrMsgM (Maybe (HsDoc Name)) +lexParseRnMbHaddockComment _ _ Nothing = return Nothing +lexParseRnMbHaddockComment hty gre (Just d) = lexParseRnHaddockComment hty gre d + +-- yes, you always get a HaddockModInfo though it might be empty +lexParseRnHaddockModHeader :: GlobalRdrEnv -> GhcDocHdr -> ErrMsgM (HaddockModInfo Name, Maybe (HsDoc Name)) +#if __GLASGOW_HASKELL__ >= 611 +lexParseRnHaddockModHeader gre mbStr = do + let failure = (emptyHaddockModInfo, Nothing) + case mbStr of + Nothing -> return failure + Just (L _ (HsDocString fs)) -> do + let str = unpackFS fs + case parseModuleHeader str of + Left mess -> do + tell ["haddock module header parse failed: " ++ mess] + return failure + Right (info, doc) -> + return (rnHaddockModInfo gre info, Just (rnHsDoc gre doc)) +#else +lexParseRnHaddockModHeader _ hdr = return hdr +#endif + diff --git a/src/Haddock/Interface/Parse.y b/src/Haddock/Interface/Parse.y new file mode 100644 index 00000000..f420c8e4 --- /dev/null +++ b/src/Haddock/Interface/Parse.y @@ -0,0 +1,106 @@ +{ +{-# OPTIONS -Wwarn -w #-} +-- The above warning supression flag is a temporary kludge. +-- While working on this module you are encouraged to remove it and fix +-- any warnings in the module. See +-- http://hackage.haskell.org/trac/ghc/wiki/Commentary/CodingStyle#Warnings +-- for details + +module Haddock.Interface.Parse ( + parseHaddockParagraphs, + parseHaddockString +) where + +import Haddock.Interface.Lex +import Haddock.Types (HsDoc(..)) +import Haddock.HsDoc +import HsSyn +import RdrName +} + +%expect 0 + +%tokentype { Token } + +%token '/' { TokSpecial '/' } + '@' { TokSpecial '@' } + '[' { TokDefStart } + ']' { TokDefEnd } + DQUO { TokSpecial '\"' } + URL { TokURL $$ } + PIC { TokPic $$ } + ANAME { TokAName $$ } + '/../' { TokEmphasis $$ } + '-' { TokBullet } + '(n)' { TokNumber } + '>..' { TokBirdTrack $$ } + IDENT { TokIdent $$ } + PARA { TokPara } + STRING { TokString $$ } + +%monad { Maybe } + +%name parseHaddockParagraphs doc +%name parseHaddockString seq + +%% + +doc :: { HsDoc RdrName } + : apara PARA doc { docAppend $1 $3 } + | PARA doc { $2 } + | apara { $1 } + | {- empty -} { DocEmpty } + +apara :: { HsDoc RdrName } + : ulpara { DocUnorderedList [$1] } + | olpara { DocOrderedList [$1] } + | defpara { DocDefList [$1] } + | para { $1 } + +ulpara :: { HsDoc RdrName } + : '-' para { $2 } + +olpara :: { HsDoc RdrName } + : '(n)' para { $2 } + +defpara :: { (HsDoc RdrName, HsDoc RdrName) } + : '[' seq ']' seq { ($2, $4) } + +para :: { HsDoc RdrName } + : seq { docParagraph $1 } + | codepara { DocCodeBlock $1 } + +codepara :: { HsDoc RdrName } + : '>..' codepara { docAppend (DocString $1) $2 } + | '>..' { DocString $1 } + +seq :: { HsDoc RdrName } + : elem seq { docAppend $1 $2 } + | elem { $1 } + +elem :: { HsDoc RdrName } + : elem1 { $1 } + | '@' seq1 '@' { DocMonospaced $2 } + +seq1 :: { HsDoc RdrName } + : PARA seq1 { docAppend (DocString "\n") $2 } + | elem1 seq1 { docAppend $1 $2 } + | elem1 { $1 } + +elem1 :: { HsDoc RdrName } + : STRING { DocString $1 } + | '/../' { DocEmphasis (DocString $1) } + | URL { DocURL $1 } + | PIC { DocPic $1 } + | ANAME { DocAName $1 } + | IDENT { DocIdentifier $1 } + | DQUO strings DQUO { DocModule $2 } + +strings :: { String } + : STRING { $1 } + | STRING strings { $1 ++ $2 } + +{ +happyError :: [Token] -> Maybe a +happyError toks = Nothing +} diff --git a/src/Haddock/Interface/ParseModuleHeader.hs b/src/Haddock/Interface/ParseModuleHeader.hs new file mode 100644 index 00000000..65bb8dd8 --- /dev/null +++ b/src/Haddock/Interface/ParseModuleHeader.hs @@ -0,0 +1,158 @@ + +----------------------------------------------------------------------------- +-- | +-- Module : Haddock.Interface.ParseModuleHeader +-- Copyright : (c) David Waern, Isaac Dupree 2009, +-- License : BSD-like +-- +-- Maintainer : haddock@projects.haskell.org +-- Stability : experimental +-- Portability : portable +----------------------------------------------------------------------------- + +module Haddock.Interface.ParseModuleHeader (parseModuleHeader) where + +import Haddock.Types +import Haddock.Interface.Lex +import Haddock.Interface.Parse + +import RdrName + +import Data.Char + +-- ----------------------------------------------------------------------------- +-- Parsing module headers + +-- NB. The headers must be given in the order Module, Description, +-- Copyright, License, Maintainer, Stability, Portability, except that +-- any or all may be omitted. +parseModuleHeader :: String -> Either String (HaddockModInfo RdrName, HsDoc RdrName) +parseModuleHeader str0 = + let + getKey :: String -> String -> (Maybe String,String) + getKey key str = case parseKey key str of + Nothing -> (Nothing,str) + Just (value,rest) -> (Just value,rest) + + (_moduleOpt,str1) = getKey "Module" str0 + (descriptionOpt,str2) = getKey "Description" str1 + (_copyrightOpt,str3) = getKey "Copyright" str2 + (_licenseOpt,str4) = getKey "License" str3 + (_licenceOpt,str5) = getKey "Licence" str4 + (maintainerOpt,str6) = getKey "Maintainer" str5 + (stabilityOpt,str7) = getKey "Stability" str6 + (portabilityOpt,str8) = getKey "Portability" str7 + + description1 :: Either String (Maybe (HsDoc RdrName)) + description1 = case descriptionOpt of + Nothing -> Right Nothing + Just description -> case parseHaddockString . tokenise $ description of + Nothing -> Left ("Cannot parse Description: " ++ description) + Just doc -> Right (Just doc) + in + case description1 of + Left mess -> Left mess + Right docOpt -> case parseHaddockParagraphs . tokenise $ str8 of + Nothing -> Left "Cannot parse header documentation paragraphs" + Just doc -> Right (HaddockModInfo { + hmi_description = docOpt, + hmi_portability = portabilityOpt, + hmi_stability = stabilityOpt, + hmi_maintainer = maintainerOpt + }, doc) + +-- | This function is how we read keys. +-- +-- all fields in the header are optional and have the form +-- +-- [spaces1][field name][spaces] ":" +-- [text]"\n" ([spaces2][space][text]"\n" | [spaces]"\n")* +-- where each [spaces2] should have [spaces1] as a prefix. +-- +-- Thus for the key "Description", +-- +-- > Description : this is a +-- > rather long +-- > +-- > description +-- > +-- > The module comment starts here +-- +-- the value will be "this is a .. description" and the rest will begin +-- at "The module comment". +parseKey :: String -> String -> Maybe (String,String) +parseKey key toParse0 = + do + let + (spaces0,toParse1) = extractLeadingSpaces toParse0 + + indentation = spaces0 + afterKey0 <- extractPrefix key toParse1 + let + afterKey1 = extractLeadingSpaces afterKey0 + afterColon0 <- case snd afterKey1 of + ':':afterColon -> return afterColon + _ -> Nothing + let + (_,afterColon1) = extractLeadingSpaces afterColon0 + + return (scanKey True indentation afterColon1) + where + scanKey :: Bool -> String -> String -> (String,String) + scanKey _ _ [] = ([],[]) + scanKey isFirst indentation str = + let + (nextLine,rest1) = extractNextLine str + + accept = isFirst || sufficientIndentation || allSpaces + + sufficientIndentation = case extractPrefix indentation nextLine of + Just (c:_) | isSpace c -> True + _ -> False + + allSpaces = case extractLeadingSpaces nextLine of + (_,[]) -> True + _ -> False + in + if accept + then + let + (scanned1,rest2) = scanKey False indentation rest1 + + scanned2 = case scanned1 of + "" -> if allSpaces then "" else nextLine + _ -> nextLine ++ "\n" ++ scanned1 + in + (scanned2,rest2) + else + ([],str) + + extractLeadingSpaces :: String -> (String,String) + extractLeadingSpaces [] = ([],[]) + extractLeadingSpaces (s@(c:cs)) + | isSpace c = + let + (spaces1,cs1) = extractLeadingSpaces cs + in + (c:spaces1,cs1) + | True = ([],s) + + extractNextLine :: String -> (String,String) + extractNextLine [] = ([],[]) + extractNextLine (c:cs) + | c == '\n' = + ([],cs) + | True = + let + (line,rest) = extractNextLine cs + in + (c:line,rest) + + -- comparison is case-insensitive. + extractPrefix :: String -> String -> Maybe String + extractPrefix [] s = Just s + extractPrefix _ [] = Nothing + extractPrefix (c1:cs1) (c2:cs2) + | toUpper c1 == toUpper c2 = extractPrefix cs1 cs2 + | True = Nothing + diff --git a/src/Haddock/Interface/Rename.hs b/src/Haddock/Interface/Rename.hs index 8145d63a..b377b4fb 100644 --- a/src/Haddock/Interface/Rename.hs +++ b/src/Haddock/Interface/Rename.hs @@ -144,6 +144,13 @@ renameExportItems = mapM renameExportItem renameMaybeDoc :: Maybe (HsDoc Name) -> RnM (Maybe (HsDoc DocName)) renameMaybeDoc = mapM renameDoc +#if __GLASGOW_HASKELL__ >= 611 +renameLDocHsSyn :: LHsDocString -> RnM LHsDocString +renameLDocHsSyn = return +#else +renameLDocHsSyn :: LHsDoc Name -> RnM (LHsDoc DocName) +renameLDocHsSyn = renameLDoc +#endif renameLDoc :: LHsDoc Name -> RnM (LHsDoc DocName) renameLDoc = mapM renameDoc @@ -259,7 +266,7 @@ renameType t = case t of HsDocTy ty doc -> do ty' <- renameLType ty - doc' <- renameLDoc doc + doc' <- renameLDocHsSyn doc return (HsDocTy ty' doc') _ -> error "renameType" @@ -363,7 +370,7 @@ renameTyClD d = case d of lcontext' <- renameLContext lcontext details' <- renameDetails details restype' <- renameResType restype - mbldoc' <- mapM renameLDoc mbldoc + mbldoc' <- mapM renameLDocHsSyn mbldoc return (decl { con_name = lname', con_qvars = ltyvars', con_cxt = lcontext' , con_details = details', con_res = restype', con_doc = mbldoc' }) @@ -377,7 +384,7 @@ renameTyClD d = case d of renameField (ConDeclField name t doc) = do name' <- renameL name t' <- renameLType t - doc' <- mapM renameLDoc doc + doc' <- mapM renameLDocHsSyn doc return (ConDeclField name' t' doc') renameResType (ResTyH98) = return ResTyH98 diff --git a/src/Haddock/Interface/Rn.hs b/src/Haddock/Interface/Rn.hs new file mode 100644 index 00000000..c45b5042 --- /dev/null +++ b/src/Haddock/Interface/Rn.hs @@ -0,0 +1,82 @@ + +module Haddock.Interface.Rn ( rnHsDoc, rnHaddockModInfo ) where + +import Haddock.Types + +import RnEnv ( dataTcOccs ) + +import RdrName ( RdrName, gre_name, GlobalRdrEnv, lookupGRE_RdrName ) +import Name ( Name ) +import Outputable ( ppr, defaultUserStyle ) + +rnHaddockModInfo :: GlobalRdrEnv -> HaddockModInfo RdrName -> HaddockModInfo Name +rnHaddockModInfo gre (HaddockModInfo desc port stab maint) = + HaddockModInfo (fmap (rnHsDoc gre) desc) port stab maint + +ids2string :: [RdrName] -> String +ids2string [] = [] +ids2string (x:_) = show $ ppr x defaultUserStyle + +data Id x = Id {unId::x} +instance Monad Id where (Id v)>>=f = f v; return = Id + +rnHsDoc :: GlobalRdrEnv -> HsDoc RdrName -> HsDoc Name +rnHsDoc gre = unId . do_rn + where + do_rn doc_to_rn = case doc_to_rn of + + DocEmpty -> return DocEmpty + + DocAppend a b -> do + a' <- do_rn a + b' <- do_rn b + return (DocAppend a' b') + + DocString str -> return (DocString str) + + DocParagraph doc -> do + doc' <- do_rn doc + return (DocParagraph doc') + + DocIdentifier ids -> do + let choices = concatMap dataTcOccs ids + let gres = concatMap (\rdrName -> + map gre_name (lookupGRE_RdrName rdrName gre)) choices + case gres of + [] -> return (DocString (ids2string ids)) + ids' -> return (DocIdentifier ids') + + DocModule str -> return (DocModule str) + + DocEmphasis doc -> do + doc' <- do_rn doc + return (DocEmphasis doc') + + DocMonospaced doc -> do + doc' <- do_rn doc + return (DocMonospaced doc') + + DocUnorderedList docs -> do + docs' <- mapM do_rn docs + return (DocUnorderedList docs') + + DocOrderedList docs -> do + docs' <- mapM do_rn docs + return (DocOrderedList docs') + + DocDefList list -> do + list' <- mapM (\(a,b) -> do + a' <- do_rn a + b' <- do_rn b + return (a', b')) list + return (DocDefList list') + + DocCodeBlock doc -> do + doc' <- do_rn doc + return (DocCodeBlock doc') + + DocURL str -> return (DocURL str) + + DocPic str -> return (DocPic str) + + DocAName str -> return (DocAName str) diff --git a/src/Haddock/ModuleTree.hs b/src/Haddock/ModuleTree.hs index d76f9883..d6054b66 100644 --- a/src/Haddock/ModuleTree.hs +++ b/src/Haddock/ModuleTree.hs @@ -12,7 +12,9 @@ module Haddock.ModuleTree ( ModuleTree(..), mkModuleTree ) where -import GHC ( HsDoc, Name ) +import Haddock.Types ( HsDoc ) + +import GHC ( Name ) import Module ( Module, moduleNameString, moduleName, modulePackageId ) #if __GLASGOW_HASKELL__ >= 609 import Module (packageIdString) diff --git a/src/Haddock/Types.hs b/src/Haddock/Types.hs index c860976f..494699e5 100644 --- a/src/Haddock/Types.hs +++ b/src/Haddock/Types.hs @@ -15,7 +15,16 @@ -- important types are defined here, like 'Interface' and 'DocName'. ----------------------------------------------------------------------------- -module Haddock.Types where +module Haddock.Types ( + module Haddock.Types +-- avoid duplicate-export warnings, use the conditional to only +-- mention things not defined in this module: +#if __GLASGOW_HASKELL__ >= 611 + , HsDocString, LHsDocString +#else + , HsDoc(..), LHsDoc, HaddockModInfo(..), emptyHaddockModInfo +#endif + ) where import Control.Exception @@ -29,6 +38,10 @@ import Name type Decl = LHsDecl Name type Doc = HsDoc Name +#if __GLASGOW_HASKELL__ <= 610 +type HsDocString = HsDoc Name +type LHsDocString = Located HsDocString +#endif -- | A declaration that may have documentation, including its subordinates, -- which may also have documentation @@ -113,6 +126,11 @@ type InstIfaceMap = Map Module InstalledInterface type DocMap = Map Name (HsDoc DocName) type LinkEnv = Map Name Module +#if __GLASGOW_HASKELL__ >= 611 +type GhcDocHdr = Maybe LHsDocString +#else +type GhcDocHdr = (HaddockModInfo Name, Maybe (HsDoc Name)) +#endif -- | This structure holds the module information we get from GHC's -- type checking phase @@ -120,8 +138,7 @@ data GhcModule = GhcModule { ghcModule :: Module, ghcFilename :: FilePath, ghcMbDocOpts :: Maybe String, - ghcHaddockModInfo :: HaddockModInfo Name, - ghcMbDoc :: Maybe (HsDoc Name), + ghcMbDocHdr :: GhcDocHdr, ghcGroup :: HsGroup Name, ghcMbExports :: Maybe [LIE Name], ghcExportedNames :: [Name], @@ -239,6 +256,28 @@ toInstalledIface interface = InstalledInterface { } +#if __GLASGOW_HASKELL__ >= 611 +data HsDoc id + = DocEmpty + | DocAppend (HsDoc id) (HsDoc id) + | DocString String + | DocParagraph (HsDoc id) + | DocIdentifier [id] + | DocModule String + | DocEmphasis (HsDoc id) + | DocMonospaced (HsDoc id) + | DocUnorderedList [HsDoc id] + | DocOrderedList [HsDoc id] + | DocDefList [(HsDoc id, HsDoc id)] + | DocCodeBlock (HsDoc id) + | DocURL String + | DocPic String + | DocAName String + deriving (Eq, Show) + +type LHsDoc id = Located (HsDoc id) +#endif + data DocMarkup id a = Markup { markupEmpty :: a, markupString :: String -> a, @@ -257,6 +296,23 @@ data DocMarkup id a = Markup { markupPic :: String -> a } +#if __GLASGOW_HASKELL__ >= 611 +data HaddockModInfo name = HaddockModInfo { + hmi_description :: Maybe (HsDoc name), + hmi_portability :: Maybe String, + hmi_stability :: Maybe String, + hmi_maintainer :: Maybe String +} + +emptyHaddockModInfo :: HaddockModInfo a +emptyHaddockModInfo = HaddockModInfo { + hmi_description = Nothing, + hmi_portability = Nothing, + hmi_stability = Nothing, + hmi_maintainer = Nothing +} +#endif + -- A monad which collects error messages, locally defined to avoid a dep on mtl diff --git a/src/Main.hs b/src/Main.hs index 465ebfd8..8d6db877 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -22,6 +22,8 @@ module Main (main) where import Haddock.Backends.Html import Haddock.Backends.Hoogle import Haddock.Interface +import Haddock.Interface.Lex +import Haddock.Interface.Parse import Haddock.Types import Haddock.Version import Haddock.InterfaceFile @@ -469,9 +471,9 @@ getPrologue flags = [] -> return Nothing [filename] -> do str <- readFile filename - case parseHaddockComment str of - Left err -> throwE err - Right doc -> return (Just doc) + case parseHaddockParagraphs (tokenise str) of + Nothing -> throwE "parsing haddock prologue failed" + Just doc -> return (Just doc) _otherwise -> throwE "multiple -p/--prologue options" |