libclang-bindings-0.1.0.0: src/Clang/HighLevel/Documentation.hs
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use panicIO" #-}
module Clang.HighLevel.Documentation (
-- * Definition
Comment(..)
, CommentBlockContent(..)
, CommentInlineContent(..)
, CXCommentInlineCommandRenderKind(..)
, CXCommentParamPassDirection(..)
-- * Top-Level
, clang_getComment
) where
import Control.Monad
import Control.Monad.IO.Class
import Data.Char (isPunctuation)
import Data.Either
import Data.Text (Text)
import Data.Text qualified as Text
import GHC.Generics (Generic)
import Clang.Enum.Simple
import Clang.LowLevel.Core
import Clang.LowLevel.Doxygen
{-------------------------------------------------------------------------------
Definition
-------------------------------------------------------------------------------}
-- | Reified Clang comment
--
-- This type corresponds to a @CXComment_FullComment@ comment.
--
-- The @CXComment_Null@ kind is not represented by this type.
-- @'Maybe' 'Comment'@ is used instead, where a @CXComment_Null@ comment is
-- represented by 'Nothing'.
--
-- The 'ref' type parameter is to be filled by the user. This data type will
-- allow one to cross reference C identifiers when translating from Doxygen to
-- Haddocks.
--
newtype Comment ref = Comment {
-- | Children of a the comment
commentChildren :: [CommentBlockContent ref]
}
deriving stock (Functor, Foldable, Traversable, Show, Eq, Ord, Generic)
-- | Reified Clang comment block content
data CommentBlockContent ref =
Paragraph {
paragraphContent :: [CommentInlineContent ref]
}
| BlockCommand {
blockCommandName :: Text
, blockCommandArgs :: [Text]
, blockCommandParagraph :: [CommentInlineContent ref]
}
| ParamCommand {
paramCommandName :: Text
, paramCommandIndex :: Maybe Int
, paramCommandDirection :: Maybe CXCommentParamPassDirection
, paramCommandIsDirectionExplicit :: Bool
, paramCommandContent :: [CommentBlockContent ref]
}
| TParamCommand {
tParamCommandName :: Text
, tParamCommandPosition :: Maybe [(Int, Int)]
, tParamCommandContent :: [CommentBlockContent ref]
}
| VerbatimBlockCommand {
verbatimBlockLines :: [Text]
}
| VerbatimLine {
verbatimLine :: Text
}
deriving stock (Functor, Foldable, Traversable, Show, Eq, Ord, Generic)
-- | Reified Clang comment inline content
data CommentInlineContent ref =
TextContent {
textContent :: Text
}
| InlineCommand {
inlineCommandName :: Text
, inlineCommandRenderKind :: CXCommentInlineCommandRenderKind
, inlineCommandArgs :: [Text]
}
| InlineRefCommand {
inlineCommandArg :: ref
}
| HtmlStartTag {
htmlStartTagName :: Text
, htmlStartTagIsSelfClosing :: Bool
, htmlStartTagAttributes :: [(Text, Text)]
}
| HtmlEndTag {
htmlEndTagName :: Text
}
deriving stock (Functor, Foldable, Traversable, Show, Eq, Ord, Generic)
{-------------------------------------------------------------------------------
Top-level
-------------------------------------------------------------------------------}
-- | Reify the Clang comment for a cursor to the Haskell type
--
-- An error is thrown when an unexpected comment kind is encountered, such as
-- block content within inline content.
clang_getComment :: MonadIO m => CXCursor -> m (Maybe (Comment Text))
clang_getComment cursor = do
comment <- clang_Cursor_getParsedComment cursor
eCommentKind <- fromSimpleEnum <$> clang_Comment_getKind comment
case eCommentKind of
Right CXComment_Null -> pure Nothing
Right CXComment_FullComment -> do
commentChildren <- getChildren (getBlockContent cursor) comment
pure $ Just Comment{..}
Right commentKind ->
errorWithContext cursor $ "root comment of kind " ++ show commentKind
Left n ->
errorWithContext cursor $ "root comment with invalid kind " ++ show n
-- | Reify block content
--
-- An error is thrown when an unexpected comment kind is encountered.
getBlockContent ::
MonadIO m
=> CXCursor -- ^ cursor to provide context in error messages
-> CXComment
-> m (CommentBlockContent Text)
getBlockContent cursor comment = do
eCommentKind <- fromSimpleEnum <$> clang_Comment_getKind comment
case eCommentKind of
Right CXComment_Paragraph -> do
paragraphContent <- concat <$> getChildren (getInlineContent cursor) comment
pure Paragraph{..}
Right CXComment_BlockCommand -> do
blockCommandName <- Text.strip <$> clang_BlockCommandComment_getCommandName comment
idxs <- getIdxs <$> clang_BlockCommandComment_getNumArgs comment
blockCommandArgs <-
fmap Text.strip
<$> mapM (clang_BlockCommandComment_getArgText comment) idxs
blockCommandParagraph <- fmap concat $ getChildren (getInlineContent cursor)
=<< clang_BlockCommandComment_getParagraph comment
pure BlockCommand{..}
Right CXComment_ParamCommand -> do
paramCommandName <- Text.strip <$> clang_ParamCommandComment_getParamName comment
paramCommandIndex <- do
isValid <- clang_ParamCommandComment_isParamIndexValid comment
if isValid
then
Just . fromIntegral
<$> clang_ParamCommandComment_getParamIndex comment
else pure Nothing
paramCommandDirection <-
either (const Nothing) Just . fromSimpleEnum
<$> clang_ParamCommandComment_getDirection comment
paramCommandIsDirectionExplicit <-
clang_ParamCommandComment_isDirectionExplicit comment
paramCommandContent <- getChildren (getBlockContent cursor) comment
pure ParamCommand{..}
Right CXComment_TParamCommand -> do
tParamCommandName <- Text.strip <$> clang_TParamCommandComment_getParamName comment
tParamCommandPosition <- do
isValid <- clang_TParamCommandComment_isParamPositionValid comment
if isValid
then fmap Just $ do
depth <- clang_TParamCommandComment_getDepth comment
forM [0 .. depth] $ \d ->
(fromIntegral d,) . fromIntegral
<$> clang_TParamCommandComment_getIndex comment d
else pure Nothing
tParamCommandContent <- getChildren (getBlockContent cursor) comment
pure TParamCommand{..}
Right CXComment_VerbatimBlockCommand -> do
verbatimBlockLines <- fmap Text.strip
<$> getChildren (getVerbatimBlockLine cursor) comment
pure VerbatimBlockCommand{..}
Right CXComment_VerbatimLine -> do
-- rest of line after misused command becomes a verbatim line
verbatimLine <- Text.strip
<$> clang_VerbatimLineComment_getText comment
pure VerbatimLine{..}
Right commentKind -> errorWithContext cursor $
"child comment of non-block kind " ++ show commentKind
Left n ->
errorWithContext cursor $ "child comment with invalid kind " ++ show n
-- | Reify inline content
--
-- An error is thrown when an unexpected comment kind is encountered.
getInlineContent ::
MonadIO m
=> CXCursor -- ^ cursor to provide context in error messages
-> CXComment
-> m [CommentInlineContent Text]
getInlineContent cursor comment = do
eCommentKind <- fromSimpleEnum <$> clang_Comment_getKind comment
case eCommentKind of
Right CXComment_Text -> do
textContent <- Text.strip <$> clang_TextComment_getText comment
pure [TextContent{..}]
Right CXComment_InlineCommand -> do
inlineCommandName <- Text.strip <$> clang_InlineCommandComment_getCommandName comment
inlineCommandRenderKind <-
fromRight CXCommentInlineCommandRenderKind_Normal . fromSimpleEnum
<$> clang_InlineCommandComment_getRenderKind comment
idxs <- getIdxs <$> clang_InlineCommandComment_getNumArgs comment
inlineCommandArgs <-
fmap (Text.strip)
<$> mapM (clang_InlineCommandComment_getArgText comment) idxs
case Text.unpack inlineCommandName of
"ref" -> pure $ sanitize inlineCommandArgs
_ -> pure [InlineCommand{..}]
Right CXComment_HTMLStartTag -> do
htmlStartTagName <- Text.strip <$> clang_HTMLTagComment_getTagName comment
htmlStartTagIsSelfClosing <-
clang_HTMLStartTagComment_isSelfClosing comment
idxs <- getIdxs <$> clang_HTMLStartTag_getNumAttrs comment
htmlStartTagAttributes <- forM idxs $ \idx -> do
attrName <- Text.strip <$> clang_HTMLStartTag_getAttrName comment idx
attrValue <- Text.strip <$> clang_HTMLStartTag_getAttrValue comment idx
pure (attrName, attrValue)
pure [HtmlStartTag{..}]
Right CXComment_HTMLEndTag -> do
htmlEndTagName <- Text.strip <$> clang_HTMLTagComment_getTagName comment
pure [HtmlEndTag{..}]
Right commentKind -> errorWithContext cursor $
"child comment of non-inline kind " ++ show commentKind
Left n ->
errorWithContext cursor $ "child comment with invalid kind " ++ show n
where
-- | Splits strings to separate punctuation from alphanumeric text.
-- for every word, create an InlineRefCommand and for every punctuation
-- mark create a TextContent.
--
-- Example: ["foo,", "bar!"] becomes
-- [ InlineRefCommand "foo"
-- , TextContent ","
-- , InlineRefCommand "bar"
-- , TextContent "!"
-- ]
-- ["test_function,"] becomes
-- [InlineRefCommand "test_function", TextContent ","]
sanitize :: [Text] -> [CommentInlineContent Text]
sanitize = concatMap (splitPunctuation . Text.unpack)
where
splitPunctuation [] = []
splitPunctuation (c:cs)
| isPunctuation c
, c /= '_' = TextContent (Text.pack [c]) : splitPunctuation cs
| otherwise =
case break (\x -> isPunctuation x && x /= '_') (c:cs) of
(word, rest) -> InlineRefCommand (Text.pack word) : splitPunctuation rest
-- | Get a verbatim block line as 'Text'
--
-- An error is thrown when an unexpected comment kind is encountered.
getVerbatimBlockLine ::
MonadIO m
=> CXCursor -- ^ cursor to provide context in error messages
-> CXComment
-> m Text
getVerbatimBlockLine cursor comment = do
eCommentKind <- fromSimpleEnum <$> clang_Comment_getKind comment
case eCommentKind of
Right CXComment_VerbatimBlockLine ->
Text.strip
<$> clang_VerbatimBlockLineComment_getText comment
Right commentKind -> errorWithContext cursor $
"child comment of non-verbatim-block-line kind " ++ show commentKind
Left n ->
errorWithContext cursor $ "child comment with invalid kind " ++ show n
-- | Reify children
getChildren :: MonadIO m => (CXComment -> m a) -> CXComment -> m [a]
getChildren f comment = do
idxs <- getIdxs <$> clang_Comment_getNumChildren comment
mapM (f <=< clang_Comment_getChild comment) idxs
-- | Get indexes (zero-based)
getIdxs :: (Enum a, Eq a, Num a)
=> a -- ^ number of items
-> [a]
getIdxs 0 = []
getIdxs n = [0 .. n - 1]
{-------------------------------------------------------------------------------
Translation
-------------------------------------------------------------------------------}
-- See "HsBindgen.Backend.Artefact.HsModule.Render"
{-------------------------------------------------------------------------------
Auxiliary Functions
-------------------------------------------------------------------------------}
-- | Throw an error with context information
errorWithContext ::
MonadIO m
=> CXCursor -- ^ cursor to provide context in error messages
-> String -- ^ error message
-> m a
errorWithContext cursor msg = liftIO $ do
displayName <- clang_getCursorDisplayName cursor
extent <- clang_getCursorExtent cursor
(file, startLine, startCol) <-
clang_getPresumedLocation =<< clang_getRangeStart extent
(_, endLine, endCol) <-
clang_getPresumedLocation =<< clang_getRangeEnd extent
fail $ concat
[ msg, ": cursor ", show displayName, " in ", show file, " ("
, show startLine, ":", show startCol, "-", show endLine, ":"
, show endCol, ")"
]