-- | An exactprintable structure for docstrings {-# LANGUAGE DeriveDataTypeable #-} module GHC.Hs.DocString ( LHsDocString , HsDocString(..) , HsDocStringDecorator(..) , HsDocStringChunk(..) , LHsDocStringChunk , isEmptyDocString , unpackHDSC , mkHsDocStringChunk , mkHsDocStringChunkUtf8ByteString , pprHsDocString , pprHsDocStrings , mkGeneratedHsDocString , docStringChunks , renderHsDocString , renderHsDocStrings , exactPrintHsDocString , pprWithDocString ) where import GHC.Prelude import GHC.Utils.Binary import GHC.Utils.Encoding import GHC.Utils.Outputable as Outputable hiding ((<>)) import GHC.Types.SrcLoc import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.Data import Data.List.NonEmpty (NonEmpty(..)) import Data.List (intercalate) type LHsDocString = Located HsDocString -- | Haskell Documentation String -- -- Rich structure to support exact printing -- The location around each chunk doesn't include the decorators data HsDocString = MultiLineDocString !HsDocStringDecorator !(NonEmpty LHsDocStringChunk) -- ^ The first chunk is preceded by "-- " and each following chunk is preceded by "--" -- Example: -- | This is a docstring for 'foo'. It is the line with the decorator '|' and is always included -- -- This continues that docstring and is the second element in the NonEmpty list -- foo :: a -> a | NestedDocString !HsDocStringDecorator LHsDocStringChunk -- ^ The docstring is preceded by "{-" and followed by "-}" -- The chunk contains balanced pairs of '{-' and '-}' | GeneratedDocString HsDocStringChunk -- ^ A docstring generated either internally or via TH -- Pretty printed with the '-- |' decorator -- This is because it may contain unbalanced pairs of '{-' and '-}' and -- not form a valid 'NestedDocString' deriving (Eq, Data, Show) instance Outputable HsDocString where ppr = text . renderHsDocString -- | Annotate a pretty printed thing with its doc -- The docstring comes after if is 'HsDocStringPrevious' -- Otherwise it comes before. -- Note - we convert MultiLineDocString HsDocStringPrevious to HsDocStringNext -- because we can't control if something else will be pretty printed on the same line pprWithDocString :: HsDocString -> SDoc -> SDoc pprWithDocString (MultiLineDocString HsDocStringPrevious ds) sd = pprWithDocString (MultiLineDocString HsDocStringNext ds) sd pprWithDocString doc@(NestedDocString HsDocStringPrevious _) sd = sd <+> pprHsDocString doc pprWithDocString doc sd = pprHsDocString doc $+$ sd instance Binary HsDocString where put_ bh x = case x of MultiLineDocString dec xs -> do putByte bh 0 put_ bh dec put_ bh xs NestedDocString dec x -> do putByte bh 1 put_ bh dec put_ bh x GeneratedDocString x -> do putByte bh 2 put_ bh x get bh = do tag <- getByte bh case tag of 0 -> MultiLineDocString <$> get bh <*> get bh 1 -> NestedDocString <$> get bh <*> get bh 2 -> GeneratedDocString <$> get bh t -> fail $ "HsDocString: invalid tag " ++ show t data HsDocStringDecorator = HsDocStringNext -- ^ '|' is the decorator | HsDocStringPrevious -- ^ '^' is the decorator | HsDocStringNamed !String -- ^ '$' is the decorator | HsDocStringGroup !Int -- ^ The decorator is the given number of '*'s deriving (Eq, Ord, Show, Data) instance Outputable HsDocStringDecorator where ppr = text . printDecorator printDecorator :: HsDocStringDecorator -> String printDecorator HsDocStringNext = "|" printDecorator HsDocStringPrevious = "^" printDecorator (HsDocStringNamed n) = '$':n printDecorator (HsDocStringGroup n) = replicate n '*' instance Binary HsDocStringDecorator where put_ bh x = case x of HsDocStringNext -> putByte bh 0 HsDocStringPrevious -> putByte bh 1 HsDocStringNamed n -> putByte bh 2 >> put_ bh n HsDocStringGroup n -> putByte bh 3 >> put_ bh n get bh = do tag <- getByte bh case tag of 0 -> pure HsDocStringNext 1 -> pure HsDocStringPrevious 2 -> HsDocStringNamed <$> get bh 3 -> HsDocStringGroup <$> get bh t -> fail $ "HsDocStringDecorator: invalid tag " ++ show t type LHsDocStringChunk = Located HsDocStringChunk -- | A continguous chunk of documentation newtype HsDocStringChunk = HsDocStringChunk ByteString deriving (Eq,Ord,Data, Show) instance Binary HsDocStringChunk where put_ bh (HsDocStringChunk bs) = put_ bh bs get bh = HsDocStringChunk <$> get bh instance Outputable HsDocStringChunk where ppr = text . unpackHDSC mkHsDocStringChunk :: String -> HsDocStringChunk mkHsDocStringChunk s = HsDocStringChunk (utf8EncodeString s) -- | Create a 'HsDocString' from a UTF8-encoded 'ByteString'. mkHsDocStringChunkUtf8ByteString :: ByteString -> HsDocStringChunk mkHsDocStringChunkUtf8ByteString = HsDocStringChunk unpackHDSC :: HsDocStringChunk -> String unpackHDSC (HsDocStringChunk bs) = utf8DecodeByteString bs nullHDSC :: HsDocStringChunk -> Bool nullHDSC (HsDocStringChunk bs) = BS.null bs mkGeneratedHsDocString :: String -> HsDocString mkGeneratedHsDocString = GeneratedDocString . mkHsDocStringChunk isEmptyDocString :: HsDocString -> Bool isEmptyDocString (MultiLineDocString _ xs) = all (nullHDSC . unLoc) xs isEmptyDocString (NestedDocString _ s) = nullHDSC $ unLoc s isEmptyDocString (GeneratedDocString x) = nullHDSC x docStringChunks :: HsDocString -> [LHsDocStringChunk] docStringChunks (MultiLineDocString _ (x:|xs)) = x:xs docStringChunks (NestedDocString _ x) = [x] docStringChunks (GeneratedDocString x) = [L (UnhelpfulSpan UnhelpfulGenerated) x] -- | Pretty print with decorators, exactly as the user wrote it pprHsDocString :: HsDocString -> SDoc pprHsDocString = text . exactPrintHsDocString pprHsDocStrings :: [HsDocString] -> SDoc pprHsDocStrings = text . intercalate "\n\n" . map exactPrintHsDocString -- | Pretty print with decorators, exactly as the user wrote it exactPrintHsDocString :: HsDocString -> String exactPrintHsDocString (MultiLineDocString dec (x :| xs)) = unlines' $ ("-- " ++ printDecorator dec ++ unpackHDSC (unLoc x)) : map (\x -> "--" ++ unpackHDSC (unLoc x)) xs exactPrintHsDocString (NestedDocString dec (L _ s)) = "{-" ++ printDecorator dec ++ unpackHDSC s ++ "-}" exactPrintHsDocString (GeneratedDocString x) = case lines (unpackHDSC x) of [] -> "" (x:xs) -> unlines' $ ( "-- |" ++ x) : map (\y -> "--"++y) xs -- | Just get the docstring, without any decorators renderHsDocString :: HsDocString -> String renderHsDocString (MultiLineDocString _ (x :| xs)) = unlines' $ map (unpackHDSC . unLoc) (x:xs) renderHsDocString (NestedDocString _ ds) = unpackHDSC $ unLoc ds renderHsDocString (GeneratedDocString x) = unpackHDSC x -- | Don't add a newline to a single string unlines' :: [String] -> String unlines' = intercalate "\n" -- | Just get the docstring, without any decorators -- Seperates docstrings using "\n\n", which is how haddock likes to render them renderHsDocStrings :: [HsDocString] -> String renderHsDocStrings = intercalate "\n\n" . map renderHsDocString