packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/Parse/Builtin.hs

module HsBindgen.Frontend.Pass.Parse.Builtin (
    CursorSource(..)
  , getCursorSource
  , checkIsBuiltin
  ) where

import Foreign.C (CUInt)

import Clang.HighLevel qualified as HighLevel
import Clang.HighLevel.Types
import Clang.LowLevel.Core

import HsBindgen.Frontend.RootHeader qualified as RootHeader
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C

-- | Where a cursor is defined
data CursorSource =
    -- | Built into Clang, such as @__INT_MAX__@
    SourceBuiltin
    -- | A declaration; for a @-D@ option, at its presumed line and column
  | SourceDecl (SingleLoc C.DeclPath)

getCursorSource :: MonadIO m => CXCursor -> m CursorSource
getCursorSource curr = getExpansion curr >>= \case
    ExpansionBuiltin         -> pure SourceBuiltin
    ExpansionCommandLine loc -> pure $ SourceDecl loc
    ExpansionFile loc        -> SourceDecl <$> traverse declPath loc
  where
    declPath :: MonadIO m => CXFile -> m C.DeclPath
    declPath file = do
      name <- SourcePath <$> clang_getFileName file
      if RootHeader.isRootHeaderPath name
        then pure C.InRootHeader
        else C.InHeader <$> HighLevel.clang_getRealPath file

-- | Check for built-in definitions
--
-- Agrees with 'getCursorSource' returning 'SourceBuiltin', and exists only for
-- speed: unlike 'getCursorSource', it does not resolve the location of an
-- ordinary declaration, which the type parser, visiting every type reference,
-- would discard.
checkIsBuiltin :: MonadIO m => CXCursor -> m (Maybe Text)
checkIsBuiltin curr = getExpansion curr >>= \case
    ExpansionBuiltin -> Just <$> clang_getCursorSpelling curr
    _otherwise       -> return Nothing

{-------------------------------------------------------------------------------
  Internal auxiliary
-------------------------------------------------------------------------------}

-- | Presumed file name of a @-D@ option, chosen by Clang
commandLineName :: SourcePath
commandLineName = SourcePath "<command line>"

-- | Expansion location of a cursor
--
--Internal! (May contain a pointer 'CXFile' that is freed when the translation
--unit goes).
data Expansion =
    -- | In Clang's predefines buffer, as a built-in
    ExpansionBuiltin
    -- | In Clang's predefines buffer, from a @-D@ option
  | ExpansionCommandLine (SingleLoc C.DeclPath)
    -- | In a file; careful; contains a pointer ('CXFile') that becomes invalid
    -- when the translation unit goes
  | ExpansionFile (SingleLoc CXFile)

getExpansion :: MonadIO m => CXCursor -> m Expansion
getExpansion curr = do
    start <- clang_getCursorLocation curr
    (file, line, col, offset) <- clang_getExpansionLocation start
    if not (isNullPtr file)
      then return $ ExpansionFile $ mkLoc file line col offset
      else do
        -- Built-ins and @-D@ options both live in the predefines buffer, which
        -- has no file; only the presumed file name tells them apart.
        (path, pLine, pCol) <- clang_getPresumedLocation start
        return $
          if SourcePath path == commandLineName
            then ExpansionCommandLine $ mkLoc C.OnCommandLine pLine pCol offset
            else ExpansionBuiltin
  where
    mkLoc :: path -> CUInt -> CUInt -> CUInt -> SingleLoc path
    mkLoc path line col offset = SingleLoc{
          singleLocPath   = path
        , singleLocLine   = fromIntegral line
        , singleLocColumn = fromIntegral col
        , singleLocOffset = fromIntegral offset
        }