hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/Parse/Monad/Decl.hs
-- | Monad for parsing declarations
--
-- Intended for unqualified import (unless context is unambiguous).
--
-- > import HsBindgen.Frontend.Pass.Parse.Monad.Decl (ParseDecl)
-- > import HsBindgen.Frontend.Pass.Parse.Monad.Decl qualified as ParseDecl
module HsBindgen.Frontend.Pass.Parse.Monad.Decl (
-- * Definition
ParseDecl
, Env (..)
, run
-- * Functionality
-- ** "Reader"
, getTranslationUnit
, getEmptyMacros
, evalGetMainHeadersAndInclude
-- ** "State"
, recordMacroDefinition
, getMacroDefinitions
, recordMacroExpansionAt
, getMacroExpansions
, getMacroExpansionsAt
-- ** Logging
, traceImmediate
, traceImmediateGlobal
-- ** Errors
, parseFail
, parseFailNoInfo
) where
import Data.IORef
import Clang.HighLevel.Types
import Clang.LowLevel.Core
import HsBindgen.Runtime.Macro qualified as Runtime.Macro
import HsBindgen.Eff
import HsBindgen.Errors
import HsBindgen.Frontend.Analysis.IncludeGraph qualified as IncludeGraph
import HsBindgen.Frontend.Pass.Parse.Builtin
import HsBindgen.Frontend.Pass.Parse.Context
import HsBindgen.Frontend.Pass.Parse.IsPass
import HsBindgen.Frontend.Pass.Parse.Monad.SourceRangeMap (LookupResult (..),
SourceRangeMap,
initSourceRangeMap,
lookupAt,
lookupRange,
recordAt)
import HsBindgen.Frontend.Pass.Parse.Msg
import HsBindgen.Frontend.Pass.Parse.Result
import HsBindgen.Frontend.ProcessIncludes
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass
import HsBindgen.Macro.Error (MacroParseError)
import HsBindgen.Macro.Syntax (MacroDefinition (..), MacroInvocation (..))
import HsBindgen.Util.Tracer
{-------------------------------------------------------------------------------
Definition
We are careful to distinguish between the state that the computation can
depend on ('MacroExpansions') and the additional output that generate during
parsing but that cannot otherwise affect the computation ('IncludeGraph').
-------------------------------------------------------------------------------}
-- | Monad used during folding
type ParseDecl = Eff ParseDeclMonad
data ParseDeclMonad a
-- | Support for 'ParseDecl' (internal type, not exported)
data ParseSupport = ParseSupport {
env :: Env -- ^ Reader
, state :: IORef ParseState -- ^ State
}
type instance Support ParseDeclMonad = ParseSupport
run :: Env -> ParseDecl a -> IO a
run env f = do
support <- ParseSupport env <$> newIORef initParseState
unwrapEff f support
{-------------------------------------------------------------------------------
"Reader"
-------------------------------------------------------------------------------}
data Env = Env {
unit :: CXTranslationUnit
, getMainHeadersAndInclude :: GetMainHeadersAndInclude
, emptyMacros :: EmptyMacros
, tracer :: Tracer (Msg Parse)
}
getTranslationUnit :: ParseDecl CXTranslationUnit
getTranslationUnit = wrapEff $ \support -> return support.env.unit
getEmptyMacros :: ParseDecl EmptyMacros
getEmptyMacros = wrapEff $ \support -> return support.env.emptyMacros
evalGetMainHeadersAndInclude ::
RealPath
-> ParseDecl
(Either DelayedParseMsg
(NonEmpty C.HashIncludeArg, IncludeGraph.Include))
evalGetMainHeadersAndInclude realPath = wrapEff $ \support ->
pure $
first (\err -> ParseNoMainHeadersException err realPath) $
support.env.getMainHeadersAndInclude realPath
{-------------------------------------------------------------------------------
"State"
-------------------------------------------------------------------------------}
data ParseState = ParseState {
-- | Macro definitions
--
-- Once parsing is done, macro definitions are analysed for ambiguity; see
-- "HsBindgen.Macro.UniqueExpansion".
macroDefinitions :: [MacroDefinition]
-- | Where did Clang expand macros, and what are their names?
--
-- Declarations with expanded macros need to be reparsed.
, macroExpansions :: SourceRangeMap MacroInvocation
}
deriving (Generic)
initParseState :: ParseState
initParseState = ParseState{
macroDefinitions = []
, macroExpansions = initSourceRangeMap
}
getParseState :: ParseDecl ParseState
getParseState = wrapEff $ \support -> readIORef support.state
modifyParseState :: (ParseState -> ParseState) -> ParseDecl ()
modifyParseState f = wrapEff $ \support -> modifyIORef support.state f
recordMacroDefinition ::
Text
-> Either MacroParseError (Runtime.Macro.Raw (Token SourcePath TokenSpelling))
-> ParseDecl ()
recordMacroDefinition macroName macro =
modifyParseState $ #macroDefinitions %~ (macroDefinition:)
where
macroDefinition :: MacroDefinition
macroDefinition = MacroDefinition {
name = macroName
, macro = macro
}
getMacroDefinitions :: ParseDecl [MacroDefinition]
getMacroDefinitions = reverse . (.macroDefinitions) <$> getParseState
recordMacroExpansionAt ::
Text
-> Range (MultiLoc RealPath)
-> [Token SourcePath TokenSpelling]
-> ParseDecl ()
recordMacroExpansionAt macroName locRange tokens =
modifyParseState $ #macroExpansions %~ recordAt loc macroInvocation
where
macroInvocation :: MacroInvocation
macroInvocation = MacroInvocation {
name = macroName
, locRange = locRange
, tokens = tokens
}
loc :: SingleLoc RealPath
loc = locRange.rangeStart.multiLocExpansion
-- | The macro invocations starting in the given half-open range
getMacroExpansions :: Range (SingleLoc RealPath) -> ParseDecl (Maybe (NonEmpty MacroInvocation))
getMacroExpansions range = do
macroExpansions <- (.macroExpansions) <$> getParseState
case lookupRange range macroExpansions of
-- We do not support getting macro expansions for declarations spanning
-- multiple files.
LookupErrorMultipleFiles -> do
traceImmediateGlobal (ParseGetMacroExpansionsMultipleFiles range)
pure Nothing
LookupNotFound ->
pure Nothing
LookupFound macroInvocations ->
pure $ Just macroInvocations
-- | The macro invocations starting at the given location
getMacroExpansionsAt :: SingleLoc RealPath -> ParseDecl (Maybe (NonEmpty MacroInvocation))
getMacroExpansionsAt loc = lookupAt loc . (.macroExpansions) <$> getParseState
{-------------------------------------------------------------------------------
Logging
-------------------------------------------------------------------------------}
-- | Immediately emit a parse trace message with location information
traceImmediate ::
HasCallStack
=> C.PrelimDeclId
-> SingleLoc C.DeclPath
-> ImmediateParseMsg
-> ParseDecl ()
traceImmediate declId declLoc msg = wrapEff $ \support ->
traceWith support.env.tracer $
withCallStack C.WithLocationInfo{
loc = C.prelimDeclIdLocationInfo declId [declLoc]
, msg = msg
}
-- | Immediately emit a global parse trace message without location information
traceImmediateGlobal :: HasCallStack => ImmediateParseMsg -> ParseDecl ()
traceImmediateGlobal msg = wrapEff $ \support ->
traceWith support.env.tracer $
withCallStack C.WithLocationInfo{
loc = C.LocationUnavailable
, msg = msg
}
{-------------------------------------------------------------------------------
Errors
-------------------------------------------------------------------------------}
-- | Record a parse failure
--
-- In contrast to 'parseSucceed' and 'parseDoNotAttempt', this is a monadic
-- action: It checks for immediate parse messages that should be
-- emitted directly.
parseFail ::
ParseCtx
-> C.PrelimDeclId
-> SingleLoc C.DeclPath
-> DelayedParseMsg
-> ParseDecl [ParseResult l Parse]
parseFail ctx declId declLoc msg = do
maybeEmitScopingMsg ctx.outer.scoping declId declLoc
pure $ (:[]) $
ParseResult{
id = declId
, loc = declLoc
, classification = ParseResultFailure msg
}
-- | Record a parse failure without having the declaration information readily
-- available
--
-- Retrieve the information using libclang and the provided cursor.
parseFailNoInfo ::
ParseCtx
-> DelayedParseMsg
-> CXCursor
-> ParseDecl [ParseResult l Parse]
parseFailNoInfo ctx msg curr = do
(declId, declLoc) <- getDeclInfoForTrace
parseFail ctx declId declLoc msg
where
-- The declaration ID and the location are not always available while
-- parsing, and so are not part of the declaration context. We have to
-- obtain them again here.
getDeclInfoForTrace :: ParseDecl (C.PrelimDeclId, SingleLoc C.DeclPath)
getDeclInfoForTrace = do
declId <- C.prelimDeclIdAtCursor curr ctx.outer.kind
source <- getCursorSource curr
case source of
SourceDecl declLoc -> pure (declId, declLoc)
-- We skip built-ins before parsing them
SourceBuiltin -> panicIO "parse failure in a built-in"
-- TODO <https://github.com/well-typed/hs-bindgen/issues/1820>
-- TODO <https://github.com/well-typed/hs-bindgen/issues/1249>
-- Ideally we'd only emit the trace when we /use/ the declaration that
-- we fail to parse.
maybeEmitScopingMsg ::
RequiredForScoping -> C.PrelimDeclId -> SingleLoc C.DeclPath -> ParseDecl ()
maybeEmitScopingMsg scoping declId declLoc = case scoping of
RequiredForScoping ->
traceImmediate declId declLoc $
ParseOfDeclarationRequiredForScopingFailed
NotRequiredForScoping ->
pure ()