{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE MonoLocalBinds #-}
module Language.C.Inline.Internal
(
setContext
, getContext
, Substitutions(..)
, substitute
, getHaskellType
, emitVerbatim
, Code(..)
, inlineCode
, inlineExp
, inlineItems
, SomeEq
, toSomeEq
, fromSomeEq
, ParameterType(..)
, ParseTypedC(..)
, parseTypedC
, runParserInQ
, splitTypedC
, genericQuote
, funPtrQuote
) where
import Control.Applicative
import Control.Monad (forM, void, msum)
import Control.Monad.State (evalStateT, StateT, get, put)
import Control.Monad.Trans.Class (lift)
import Data.Foldable (forM_)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.Traversable (for)
import Data.Typeable (Typeable, cast)
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Quote as TH
import qualified Language.Haskell.TH.Syntax as TH
import System.IO.Unsafe (unsafePerformIO, unsafeDupablePerformIO)
import qualified Text.Parsec as Parsec
import qualified Text.Parsec.Pos as Parsec
import qualified Text.Parser.Char as Parser
import qualified Text.Parser.Combinators as Parser
import qualified Text.Parser.LookAhead as Parser
import qualified Text.Parser.Token as Parser
import Text.PrettyPrint.ANSI.Leijen ((<+>))
import qualified Text.PrettyPrint.ANSI.Leijen as PP
import qualified Data.List as L
import qualified Data.Char as C
import Data.Hashable (Hashable)
import Foreign.Ptr (FunPtr)
import qualified Data.Map as M
#define USE_GETQ (__GLASGOW_HASKELL__ > 710 || (__GLASGOW_HASKELL__ == 710 && __GLASGOW_HASKELL_PATCHLEVEL1__ >= 3))
#if !USE_GETQ
import Control.Concurrent.MVar (MVar, newMVar, modifyMVar_, readMVar)
#endif
import Language.C.Inline.Context
import Language.C.Inline.FunPtr
import Language.C.Inline.HaskellIdentifier
import qualified Language.C.Types as C
data ModuleState = ModuleState
{ msContext :: Context
, msGeneratedNames :: Int
, msFileChunks :: [String]
} deriving (Typeable)
getModuleState :: TH.Q (Maybe ModuleState)
putModuleState :: ModuleState -> TH.Q ()
#if USE_GETQ
getModuleState = TH.getQ
putModuleState = TH.putQ
#else
type ModuleId = String
getModuleId :: TH.Q ModuleId
getModuleId = TH.loc_filename <$> TH.location
{-# NOINLINE moduleStatesVar #-}
moduleStatesVar :: MVar (Map.Map ModuleId ModuleState)
moduleStatesVar = unsafePerformIO $ newMVar Map.empty
getModuleState = do
moduleStates <- TH.runIO (readMVar moduleStatesVar)
moduleId <- getModuleId
return (Map.lookup moduleId moduleStates)
putModuleState ms = do
moduleId <- getModuleId
TH.runIO (modifyMVar_ moduleStatesVar (return . Map.insert moduleId ms))
#endif
initialiseModuleState
:: Maybe Context
-> TH.Q Context
initialiseModuleState mbContext = do
mbModuleState <- getModuleState
case mbModuleState of
Just moduleState -> return (msContext moduleState)
Nothing -> do
TH.addModFinalizer $ do
mbMs <- getModuleState
ms <- case mbMs of
Nothing -> fail "inline-c: ModuleState not present (initialiseModuleState)"
Just ms -> return ms
let lang = fromMaybe TH.LangC (ctxForeignSrcLang context)
#if MIN_VERSION_base(4,12,0)
TH.addForeignSource lang (concat (reverse (msFileChunks ms)))
#else
TH.addForeignFile lang (concat (reverse (msFileChunks ms)))
#endif
let moduleState = ModuleState
{ msContext = context
, msGeneratedNames = 0
, msFileChunks = mempty
}
putModuleState moduleState
return context
where
context = fromMaybe baseCtx mbContext
getContext :: TH.Q Context
getContext = initialiseModuleState Nothing
modifyModuleState :: (ModuleState -> (ModuleState, a)) -> TH.Q a
modifyModuleState f = do
mbModuleState <- getModuleState
case mbModuleState of
Nothing -> fail "inline-c: ModuleState not present (modifyModuleState)"
Just ms -> do
let (ms', x) = f ms
putModuleState ms'
return x
setContext :: Context -> TH.Q ()
setContext ctx = do
mbModuleState <- getModuleState
forM_ mbModuleState $ \_ms ->
fail "inline-c: The module has already been initialised (setContext)."
void $ initialiseModuleState $ Just ctx
bumpGeneratedNames :: TH.Q Int
bumpGeneratedNames = do
modifyModuleState $ \ms ->
let c' = msGeneratedNames ms
in (ms{msGeneratedNames = c' + 1}, c')
emitVerbatim :: String -> TH.DecsQ
emitVerbatim s = do
void (initialiseModuleState Nothing)
let chunk = "\n" ++ s ++ "\n"
modifyModuleState $ \ms ->
(ms{msFileChunks = chunk : msFileChunks ms}, ())
return []
data Code = Code
{ codeCallSafety :: TH.Safety
, codeLoc :: Maybe TH.Loc
, codeType :: TH.TypeQ
, codeFunName :: String
, codeDefs :: String
, codeFunPtr :: Bool
}
inlineCode :: Code -> TH.ExpQ
inlineCode Code{..} = do
ctx <- getContext
let out = fromMaybe id $ ctxOutput ctx
let directive = maybe "" (\l -> "#line " ++ show (fst $ TH.loc_start l) ++ " " ++ show (TH.loc_filename l ) ++ "\n") codeLoc
void $ emitVerbatim $ out $ directive ++ codeDefs
ffiImportName <- uniqueFfiImportName
dec <- if codeFunPtr
then
TH.forImpD TH.CCall codeCallSafety ("&" ++ codeFunName) ffiImportName [t| FunPtr $(codeType) |]
else TH.forImpD TH.CCall codeCallSafety codeFunName ffiImportName codeType
TH.addTopDecls [dec]
TH.varE ffiImportName
uniqueCName :: Maybe String -> TH.Q String
uniqueCName mbPostfix = do
c' <- bumpGeneratedNames
module_ <- TH.loc_module <$> TH.location
let replaceDot '.' = '_'
replaceDot c = c
let postfix = case mbPostfix of
Nothing -> ""
Just s -> "_" ++ s ++ "_"
return $ "inline_c_" ++ map replaceDot module_ ++ "_" ++ show c' ++ postfix
inlineExp
:: TH.Safety
-> TH.Loc
-> TH.TypeQ
-> C.Type C.CIdentifier
-> [(C.CIdentifier, C.Type C.CIdentifier)]
-> String
-> TH.ExpQ
inlineExp callSafety loc type_ cRetType cParams cExp =
inlineItems callSafety False Nothing loc type_ cRetType cParams cItems
where
cItems = case cRetType of
C.TypeSpecifier _quals C.Void -> cExp ++ ";"
_ -> "return (" ++ cExp ++ ");"
inlineItems
:: TH.Safety
-> Bool
-> Maybe String
-> TH.Loc
-> TH.TypeQ
-> C.Type C.CIdentifier
-> [(C.CIdentifier, C.Type C.CIdentifier)]
-> String
-> TH.ExpQ
inlineItems callSafety funPtr mbPostfix loc type_ cRetType cParams cItems = do
let mkParam (id', paramTy) = C.ParameterDeclaration (Just id') paramTy
let proto = C.Proto cRetType (map mkParam cParams)
ctx <- getContext
funName <- uniqueCName mbPostfix
cFunName <- case C.cIdentifierFromString (ctxEnableCpp ctx) funName of
Left err -> fail $ "inlineItems: impossible, generated bad C identifier " ++
"funName:\n" ++ err
Right x -> return x
let decl = C.ParameterDeclaration (Just cFunName) proto
let defs = prettyOneLine decl ++ " { " ++ cItems ++ " }\n"
inlineCode $ Code
{ codeCallSafety = callSafety
, codeLoc = Just loc
, codeType = type_
, codeFunName = funName
, codeDefs = defs
, codeFunPtr = funPtr
}
runParserInQ
:: (Hashable ident)
=> String
-> C.CParserContext ident
-> (forall m. C.CParser ident m => m a) -> TH.Q a
runParserInQ s ctx p = do
loc <- TH.location
let (line, col) = TH.loc_start loc
let parsecLoc = Parsec.newPos (TH.loc_filename loc) line col
let p' = lift (Parsec.setPosition parsecLoc) *> p <* lift Parser.eof
case C.runCParser ctx (TH.loc_filename loc) s p' of
Left err -> do
fail $ show err
Right res -> do
return res
data SomeEq = forall a. (Typeable a, Eq a) => SomeEq a
instance Eq SomeEq where
SomeEq x == SomeEq y = case cast x of
Nothing -> False
Just x' -> x' == y
instance Show SomeEq where
show _ = "<<SomeEq>>"
toSomeEq :: (Eq a, Typeable a) => a -> SomeEq
toSomeEq x = SomeEq x
fromSomeEq :: (Eq a, Typeable a) => SomeEq -> Maybe a
fromSomeEq (SomeEq x) = cast x
data ParameterType
= Plain HaskellIdentifier
| AntiQuote AntiQuoterId SomeEq
deriving (Show, Eq)
data ParseTypedC = ParseTypedC
{ ptcReturnType :: C.Type C.CIdentifier
, ptcParameters :: [(C.CIdentifier, C.Type C.CIdentifier, ParameterType)]
, ptcBody :: String
}
newtype Substitutions = Substitutions { unSubstitutions :: M.Map String (String -> String) }
applySubstitutions :: String -> TH.Q String
applySubstitutions str = do
subs <- maybe mempty unSubstitutions <$> TH.getQ
let substitution = msum $ flip map (M.toList subs) $ \( subName, subFunc ) ->
Parsec.try $ do
_ <- Parsec.string ('@' : subName ++ "(")
subArg <- Parsec.manyTill Parsec.anyChar (Parsec.char ')')
return (subFunc subArg)
let someChar = (:[]) <$> Parsec.anyChar
case Parsec.parse (many (substitution <|> someChar)) "" str of
Left _ -> fail "Substitution failed (should be impossible)"
Right chunks -> return (concat chunks)
substitute :: [ ( String, String -> String ) ] -> TH.Q a -> TH.Q a
substitute subsList cont = do
oldSubs <- maybe mempty unSubstitutions <$> TH.getQ
let subs = M.fromList subsList
let conflicting = M.intersection subs oldSubs
newSubs <-
if M.null conflicting
then return (Substitutions (M.union oldSubs subs))
else fail ("Conflicting substitutions `" ++ show (M.keys conflicting) ++ "`")
TH.putQ newSubs *> cont <* TH.putQ (Substitutions oldSubs)
getHaskellType :: Bool -> String -> TH.TypeQ
getHaskellType pureFunctions cTypeStr = do
ctx <- getContext
let cParseCtx = C.cCParserContext (ctxEnableCpp ctx) (typeNamesFromTypesTable (ctxTypesTable ctx))
cType <- runParserInQ cTypeStr cParseCtx C.parseType
cToHs ctx (if pureFunctions then Pure else IO) cType
parseTypedC
:: forall m. C.CParser HaskellIdentifier m
=> Bool -> AntiQuoters -> m ParseTypedC
parseTypedC useCpp antiQs = do
Parser.spaces
cRetType <- purgeHaskellIdentifiers =<< C.parseType
void $ Parser.char '{'
(cParams, cBody) <- evalStateT parseBody 0
return $ ParseTypedC cRetType cParams cBody
where
parseBody
:: StateT Int m ([(C.CIdentifier, C.Type C.CIdentifier, ParameterType)], String)
parseBody = do
s <- Parser.manyTill Parser.anyChar $
Parser.lookAhead (Parser.char '}' <|> Parser.char '$')
(decls, s') <- msum
[ do Parser.try $ do
void $ Parser.symbolic '}'
Parser.eof
return ([], "")
, do void $ Parser.char '}'
(decls, s') <- parseBody
return (decls, "}" ++ s')
, do void $ Parser.char '$'
(decls1, s1) <- parseEscapedDollar <|> parseAntiQuote <|> parseTypedCapture
(decls2, s2) <- parseBody
return (decls1 ++ decls2, s1 ++ s2)
]
return (decls, s ++ s')
where
parseAntiQuote
:: StateT Int m ([(C.CIdentifier, C.Type C.CIdentifier, ParameterType)], String)
parseAntiQuote = msum
[ do void $ Parser.try (Parser.string $ antiQId ++ ":") Parser.<?> "anti quoter id"
(s, cTy, x) <- aqParser antiQ
id' <- freshId s
return ([(id', cTy, AntiQuote antiQId (toSomeEq x))], C.unCIdentifier id')
| (antiQId, SomeAntiQuoter antiQ) <- Map.toList antiQs
]
parseEscapedDollar :: StateT Int m ([a], String)
parseEscapedDollar = do
void $ Parser.char '$'
return ([], "$")
parseTypedCapture
:: StateT Int m ([(C.CIdentifier, C.Type C.CIdentifier, ParameterType)], String)
parseTypedCapture = do
void $ Parser.symbolic '('
decl <- C.parseParameterDeclaration
declType <- purgeHaskellIdentifiers $ C.parameterDeclarationType decl
hId <- case C.parameterDeclarationId decl of
Nothing -> fail $ pretty80 $
"Un-named captured variable in decl" <+> PP.pretty decl
Just hId -> return hId
id' <- freshId $ mangleHaskellIdentifier useCpp hId
void $ Parser.char ')'
return ([(id', declType, Plain hId)], C.unCIdentifier id')
freshId s = do
c <- get
put $ c + 1
case C.cIdentifierFromString useCpp (C.unCIdentifier s ++ "_inline_c_" ++ show c) of
Left _err -> error "freshId: The impossible happened"
Right x -> return x
purgeHaskellIdentifiers
#if MIN_VERSION_base(4,13,0)
:: forall n. MonadFail n
#else
:: forall n. (Applicative n, Monad n)
#endif
=> C.Type HaskellIdentifier -> n (C.Type C.CIdentifier)
purgeHaskellIdentifiers cTy = for cTy $ \hsIdent -> do
let hsIdentS = unHaskellIdentifier hsIdent
case C.cIdentifierFromString useCpp hsIdentS of
Left err -> fail $ "Haskell identifier " ++ hsIdentS ++ " in illegal position" ++
"in C type\n" ++ pretty80 cTy ++ "\n" ++
"A C identifier was expected, but:\n" ++ err
Right cIdent -> return cIdent
quoteCode
:: (String -> TH.ExpQ)
-> TH.QuasiQuoter
quoteCode p = TH.QuasiQuoter
{ TH.quoteExp = p
, TH.quotePat = const $ fail "inline-c: quotePat not implemented (quoteCode)"
, TH.quoteType = const $ fail "inline-c: quoteType not implemented (quoteCode)"
, TH.quoteDec = const $ fail "inline-c: quoteDec not implemented (quoteCode)"
}
cToHs :: Context -> Purity -> C.Type C.CIdentifier -> TH.TypeQ
cToHs ctx purity cTy = do
mbHsTy <- convertType purity (ctxTypesTable ctx) cTy
case mbHsTy of
Nothing -> fail $ "Could not resolve Haskell type for C type " ++ pretty80 cTy
Just hsTy -> return hsTy
genericQuote
:: Purity
-> (TH.Loc -> TH.TypeQ -> C.Type C.CIdentifier -> [(C.CIdentifier, C.Type C.CIdentifier)] -> String -> TH.ExpQ)
-> TH.QuasiQuoter
genericQuote purity build = quoteCode $ \rawStr -> do
ctx <- getContext
here <- TH.location
s <- applySubstitutions rawStr
ParseTypedC cType cParams cExp <-
runParserInQ s
(haskellCParserContext (ctxEnableCpp ctx) (typeNamesFromTypesTable (ctxTypesTable ctx)))
(parseTypedC (ctxEnableCpp ctx) (ctxAntiQuoters ctx))
hsType <- cToHs ctx purity cType
hsParams <- forM cParams $ \(_cId, cTy, parTy) -> do
case parTy of
Plain s' -> do
hsTy <- cToHs ctx purity cTy
let hsName = TH.mkName (unHaskellIdentifier s')
hsExp <- [| \cont -> cont ($(TH.varE hsName) :: $(return hsTy)) |]
return (hsTy, hsExp)
AntiQuote antiId dyn -> do
case Map.lookup antiId (ctxAntiQuoters ctx) of
Nothing ->
fail $ "IMPOSSIBLE: could not find anti-quoter " ++ show antiId ++
". (genericQuote)"
Just (SomeAntiQuoter antiQ) -> case fromSomeEq dyn of
Nothing ->
fail $ "IMPOSSIBLE: could not cast value for anti-quoter " ++
show antiId ++ ". (genericQuote)"
Just x ->
aqMarshaller antiQ purity (ctxTypesTable ctx) cTy x
let hsFunType = convertCFunSig hsType $ map fst hsParams
let cParams' = [(cId, cTy) | (cId, cTy, _) <- cParams]
ioCall <- buildFunCall ctx (build here hsFunType cType cParams' cExp) (map snd hsParams) []
case purity of
Pure -> [| unsafeDupablePerformIO $(return ioCall) |]
IO -> return ioCall
where
buildFunCall :: Context -> TH.ExpQ -> [TH.Exp] -> [TH.Name] -> TH.ExpQ
buildFunCall _ctx f [] args =
foldl (\f' arg -> [| $f' $(TH.varE arg) |]) f args
buildFunCall ctx f (hsExp : params) args =
[| $(return hsExp) $ \arg ->
$(buildFunCall ctx f params (args ++ ['arg]))
|]
convertCFunSig :: TH.Type -> [TH.Type] -> TH.TypeQ
convertCFunSig retType params0 = do
go params0
where
go [] =
[t| IO $(return retType) |]
go (paramType : params) = do
[t| $(return paramType) -> $(go params) |]
splitTypedC :: String -> (String, String)
splitTypedC s = (trim ty, case body of
[] -> []
r -> r)
where (ty, body) = span (/= '{') s
trim x = L.dropWhileEnd C.isSpace (dropWhile C.isSpace x)
data FunPtrDecl = FunPtrDecl
{ funPtrReturnType :: C.Type C.CIdentifier
, funPtrParameters :: [(C.CIdentifier, C.Type C.CIdentifier)]
, funPtrBody :: String
, funPtrName :: Maybe String
} deriving (Eq, Show)
funPtrQuote :: TH.Safety -> TH.QuasiQuoter
funPtrQuote callSafety = quoteCode $ \rawCode -> do
loc <- TH.location
ctx <- getContext
code <- applySubstitutions rawCode
FunPtrDecl{..} <- runParserInQ code (C.cCParserContext (ctxEnableCpp ctx) (typeNamesFromTypesTable (ctxTypesTable ctx))) parse
hsRetType <- cToHs ctx IO funPtrReturnType
hsParams <- forM funPtrParameters (\(_ident, typ_) -> cToHs ctx IO typ_)
let hsFunType = convertCFunSig hsRetType hsParams
inlineItems callSafety True funPtrName loc hsFunType funPtrReturnType funPtrParameters funPtrBody
where
convertCFunSig :: TH.Type -> [TH.Type] -> TH.TypeQ
convertCFunSig retType params0 = do
go params0
where
go [] =
[t| IO $(return retType) |]
go (paramType : params) = do
[t| $(return paramType) -> $(go params) |]
parse :: C.CParser C.CIdentifier m => m FunPtrDecl
parse = do
Parser.spaces
C.ParameterDeclaration mbName protoTyp <- C.parseParameterDeclaration
case protoTyp of
C.Proto retType paramList -> do
args <- forM paramList $ \decl -> case C.parameterDeclarationId decl of
Nothing -> fail $ pretty80 $
"Un-named captured variable in decl" <+> PP.pretty decl
Just declId -> return (declId, C.parameterDeclarationType decl)
void (Parser.symbolic '{')
body <- parseBody
return FunPtrDecl
{ funPtrReturnType = retType
, funPtrParameters = args
, funPtrBody = body
, funPtrName = fmap C.unCIdentifier mbName
}
_ -> fail $ "Expecting function declaration"
parseBody :: C.CParser C.CIdentifier m => m String
parseBody = do
s <- Parser.manyTill Parser.anyChar $
Parser.lookAhead (Parser.char '}')
s' <- msum
[ do Parser.try $ do
void $ Parser.symbolic '}'
Parser.eof
return ""
, do void $ Parser.char '}'
s' <- parseBody
return ("}" ++ s')
]
return (s ++ s')
pretty80 :: PP.Pretty a => a -> String
pretty80 x = PP.displayS (PP.renderPretty 0.8 80 (PP.pretty x)) ""
prettyOneLine :: PP.Pretty a => a -> String
prettyOneLine x = PP.displayS (PP.renderCompact (PP.pretty x)) ""