hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/Parse/Decl/Macro.hs
-- | Parse functions related to reparsing
module HsBindgen.Frontend.Pass.Parse.Decl.Macro (
getReparseInfo
) where
import Clang.HighLevel qualified as HighLevel
import Clang.HighLevel.Types (MultiLoc (..), Range (..), RealPath,
SingleLoc (..))
import Clang.LowLevel.Core (CXCursor, CXFile, CXSourceRange, CXTranslationUnit,
clang_getCursorExtent, clang_getExpansionLocation,
clang_getLocation, clang_getRange,
clang_getRangeStart)
import HsBindgen.Frontend.Pass.Parse.IsPass (ReparseInfo (..), Tokens)
import HsBindgen.Frontend.Pass.Parse.Monad.Decl (ParseDecl, getMacroExpansions,
getMacroExpansionsAt,
getTranslationUnit)
import HsBindgen.Macro.Syntax (MacroInvocation (..))
-- | Do we need to reparse a declaration because it contains macros that were
-- expanded? If so, what are the original tokens of the declaration before
-- macro expansion?
--
-- See "Reparsing: the source range of a declaration" in @dev/macros.md@.
getReparseInfo :: CXCursor -> ParseDecl (ReparseInfo Tokens)
getReparseInfo = \curr -> do
range <- getFileRange =<< HighLevel.clang_getCursorExtent curr
macroExpansionsMay <- getMacroExpansions range
case macroExpansionsMay of
Nothing -> pure ReparseNotNeeded
Just macroExpansions -> do
unit <- getTranslationUnit
-- An expansion location always lies in a file: the bodies of @-D@
-- macros in the predefines buffer cannot contain declarations.
(file, _, _, _) <- clang_getExpansionLocation
=<< clang_getRangeStart
=<< clang_getCursorExtent curr
tokens <- HighLevel.clang_tokenize unit =<< toSourceRange unit file range
pure $ ReparseNeeded tokens macroExpansions
-- | The range a declaration occupies in its file
--
-- A declaration starting with a macro, such as the field @T x;@ after
-- @#define T int@, starts in the macro definition; the expansion location is
-- the invocation @T@. The same holds for an end in a macro body.
--
-- A declaration ending in a macro argument, such as @T f PARAMS((T a))@ after
-- @#define PARAMS(args) args@, ends in the file, inside the invocation, and the
-- expansion location is the /start/ of the invocation; we extend the range to
-- its end. Only then does the range contain the invocations nested in the
-- argument.
getFileRange :: Range (MultiLoc RealPath) -> ParseDecl (Range (SingleLoc RealPath))
getFileRange extent = case extent.rangeEnd.multiLocFile of
-- 'Nothing' if the file location equals the expansion location, which holds
-- outside macro arguments, or if it has no real path
Nothing -> pure expansion
Just _ -> do
invocationsMay <- getMacroExpansionsAt expansion.rangeEnd
pure $ case invocationsMay of
Nothing -> expansion
Just invocations -> expansion{
rangeEnd = maximum $ invocationEnd <$> invocations
}
where
expansion :: Range (SingleLoc RealPath)
expansion = multiLocExpansion <$> extent
invocationEnd :: MacroInvocation -> SingleLoc RealPath
invocationEnd invocation = invocation.locRange.rangeEnd.multiLocExpansion
-- | Convert a range in the given file to a 'CXSourceRange'
toSourceRange ::
CXTranslationUnit
-> CXFile
-> Range (SingleLoc RealPath)
-> ParseDecl CXSourceRange
toSourceRange unit file range = do
let toLocation loc = clang_getLocation unit file
(fromIntegral loc.singleLocLine)
(fromIntegral loc.singleLocColumn)
start <- toLocation range.rangeStart
end <- toLocation range.rangeEnd
clang_getRange start end