diff --git a/.travis.yml b/.travis.yml index 8bf24e4..046d43f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ addons: homebrew: packages: - cairo + - cairocffi - gsl matrix: diff --git a/inline-c-cpp/inline-c-cpp.cabal b/inline-c-cpp/inline-c-cpp.cabal index 2d38794..2cc7396 100644 --- a/inline-c-cpp/inline-c-cpp.cabal +++ b/inline-c-cpp/inline-c-cpp.cabal @@ -24,6 +24,7 @@ library , inline-c >= 0.6.1.0 , template-haskell , safe-exceptions + , containers hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall -optc-xc++ -optc-std=c++11 @@ -41,10 +42,13 @@ test-suite tests , inline-c-cpp , safe-exceptions , hspec + , containers default-language: Haskell2010 ghc-options: -optc-std=c++11 if os(darwin) ghc-options: -pgmc=clang++ extra-libraries: stdc++ - cc-options: -Wall -Werror -optc-xc++ -std=c++11 + cc-options: -Wall -Werror + if os(darwin) + ld-options: -Wl,-keep_dwarf_unwind diff --git a/inline-c-cpp/src/Language/C/Inline/Cpp.hs b/inline-c-cpp/src/Language/C/Inline/Cpp.hs index bf8bb5a..a0f9119 100644 --- a/inline-c-cpp/src/Language/C/Inline/Cpp.hs +++ b/inline-c-cpp/src/Language/C/Inline/Cpp.hs @@ -4,6 +4,7 @@ module Language.C.Inline.Cpp ( module Language.C.Inline , cppCtx + , cppTypePairs , using ) where @@ -13,6 +14,9 @@ import qualified Language.Haskell.TH.Syntax as TH import Language.C.Inline import Language.C.Inline.Context +import qualified Language.C.Types as CT + +import qualified Data.Map as Map -- | The equivalent of 'C.baseCtx' for C++. It specifies the @.cpp@ -- file extension for the C file, so that g++ will decide to build C++ @@ -31,3 +35,9 @@ cppCtx = baseCtx <> mempty -- @ using :: String -> TH.DecsQ using s = verbatim $ "using " ++ s ++ ";" + + +cppTypePairs :: [(CT.CIdentifier, TH.TypeQ)] -> Context +cppTypePairs typePairs = mempty { + ctxTypesTable = Map.fromList $ map (\(cpp_sym, haskell_sym) -> (CT.TypeName cpp_sym, haskell_sym)) typePairs + } diff --git a/inline-c-cpp/src/Language/C/Inline/Cpp/Exceptions.hs b/inline-c-cpp/src/Language/C/Inline/Cpp/Exceptions.hs index 1217b04..a653d9b 100644 --- a/inline-c-cpp/src/Language/C/Inline/Cpp/Exceptions.hs +++ b/inline-c-cpp/src/Language/C/Inline/Cpp/Exceptions.hs @@ -87,7 +87,85 @@ catchBlock = QuasiQuoter , quoteDec = unsupported } where unsupported _ = fail "Unsupported quasiquotation." - + + + +{- + +Some compilers can not parse "{}" as generalized initializer.(See following the example of test.cpp.) +"exceptionalValue"-function assigns initial value for numeric types. +Many numeric types can be initialized with 0. + +-- test.cpp -- +int +test(){ + try { + throw "Exception\n"; + }catch(...) { + return {}; + } +} + +---In case of clang ----------- +$ clang++ -c test.cpp +test.cpp:6:12: warning: generalized initializer lists are a C++11 extension [-Wc++11-extensions] + return {}; + ^~ +test.cpp:6:12: error: scalar initializer cannot be empty + return {}; + ^~ +1 warning and 1 error generated. + +---In case of gcc- ----------- +$ g++ -c test.cpp +test.cpp: In function ‘int test()’: +test.cpp:6:12: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11 + return {}; + +-} +exceptionalValue :: String -> String +exceptionalValue typeStr = + case typeStr of + "void" -> "" + "char" -> "0" + "short" -> "0" + "long" -> "0" + "int" -> "0" + "int8_t" -> "0" + "int16_t" -> "0" + "int32_t" -> "0" + "int64_t" -> "0" + "uint8_t" -> "0" + "uint16_t" -> "0" + "uint32_t" -> "0" + "uint64_t" -> "0" + "float" -> "0" + "double" -> "0" + "bool" -> "0" + "signed char" -> "0" + "signed short" -> "0" + "signed int" -> "0" + "signed long" -> "0" + "unsigned char" -> "0" + "unsigned short" -> "0" + "unsigned int" -> "0" + "unsigned long" -> "0" + "size_t" -> "0" + "wchar_t" -> "0" + "ptrdiff_t" -> "0" + "sig_atomic_t" -> "0" + "intptr_t" -> "0" + "uintptr_t" -> "0" + "intmax_t" -> "0" + "uintmax_t" -> "0" + "clock_t" -> "0" + "time_t" -> "0" + "useconds_t" -> "0" + "suseconds_t" -> "0" + "FILE" -> "0" + "fpos_t" -> "0" + "jmp_buf" -> "0" + _ -> "{}" tryBlockQuoteExp :: String -> Q Exp tryBlockQuoteExp blockStr = do @@ -130,7 +208,7 @@ tryBlockQuoteExp blockStr = do , " size_t message_len = message.size() + 1;" , " *__inline_c_cpp_error_message__ = static_cast(std::malloc(message_len));" , " std::memcpy(*__inline_c_cpp_error_message__, message.c_str(), message_len);" - , if ty == "void" then "return;" else "return {};" + , " return " ++ exceptionalValue ty ++ ";" , " } catch (...) {" , " *__inline_c_cpp_exception_type__ = " ++ show ExTypeOtherException ++ ";" , "#if defined(__GNUC__) || defined(__clang__)" @@ -142,7 +220,7 @@ tryBlockQuoteExp blockStr = do , "#else" , " *__inline_c_cpp_error_message__ = NULL;" , "#endif" - , if ty == "void" then "return;" else "return {};" + , " return " ++ exceptionalValue ty ++ ";" , " }" , "}" ] diff --git a/inline-c-cpp/test/test.h b/inline-c-cpp/test/test.h new file mode 100644 index 0000000..af8ea29 --- /dev/null +++ b/inline-c-cpp/test/test.h @@ -0,0 +1,8 @@ +namespace Test { + class Test { + public: + Test() {} + int get () {return 3;} + }; +}; +; diff --git a/inline-c-cpp/test/tests.hs b/inline-c-cpp/test/tests.hs index b84bbd1..ad10fdd 100644 --- a/inline-c-cpp/test/tests.hs +++ b/inline-c-cpp/test/tests.hs @@ -1,18 +1,36 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE PolyKinds #-} import Control.Exception.Safe import Control.Monad import qualified Language.C.Inline.Cpp as C +import qualified Language.C.Inline.Context as CC +import qualified Language.C.Types as CT import qualified Language.C.Inline.Cpp.Exceptions as C import qualified Test.Hspec as Hspec +import Foreign.Ptr (Ptr) import Data.List (isInfixOf) -C.context C.cppCtx +data Test +data Vector a +data Array a + +C.context $ C.cppCtx `mappend` C.cppTypePairs [ + ("Test::Test", [t|Test|]), + ("std::vector", [t|Vector|]), + ("std::array", [t|Array|]) + ] C.include "" +C.include "" +C.include "" +C.include "" C.include "" +C.include "test.h" main :: IO () main = Hspec.hspec $ do @@ -23,6 +41,41 @@ main = Hspec.hspec $ do std::cout << "Hello, world!" << $(int x) << std::endl; } |] + Hspec.describe "C++ Types" $ do + Hspec.it "Hello Namespace" $ do + pt <- [C.block| Test::Test* { + return new Test::Test(); + } |] :: IO (Ptr Test) + [C.block| void { + std::cout << $(Test::Test* pt)->get() << std::endl; + } |] + + Hspec.it "Hello Template" $ do + pt <- [C.block| std::vector* { + return new std::vector(); + } |] :: IO (Ptr (Vector C.CInt)) + [C.block| void { + $(std::vector* pt)->push_back(100); + std::cout << (*$(std::vector* pt))[0] << std::endl; + } |] + + Hspec.it "Template + Namespace" $ do + pt <- [C.block| std::vector* { + return new std::vector(); + } |] :: IO (Ptr (Vector Test)) + [C.block| void { + $(std::vector* pt)->push_back(Test::Test()); + } |] + + Hspec.it "Template with 2 arguments" $ do + pt <- [C.block| std::array* { + return new std::array(); + } |] :: IO (Ptr (Array '(C.CInt,10))) + [C.block| void { + (*$(std::array* pt))[0]=true; + std::cout << (*$(std::array* pt))[0] << std::endl; + } |] + Hspec.describe "Exception handling" $ do Hspec.it "std::exceptions are caught" $ do result <- try [C.catchBlock| diff --git a/inline-c/src/Language/C/Inline/Context.hs b/inline-c/src/Language/C/Inline/Context.hs index 108b11d..9a530f6 100644 --- a/inline-c/src/Language/C/Inline/Context.hs +++ b/inline-c/src/Language/C/Inline/Context.hs @@ -1,14 +1,20 @@ -{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE CPP #-} -{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PolyKinds #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} -- | A 'Context' is used to define the capabilities of the Template Haskell code -- that handles the inline C code. See the documentation of the data type for @@ -43,7 +49,7 @@ module Language.C.Inline.Context ) where import Control.Applicative ((<|>)) -import Control.Monad (mzero) +import Control.Monad (mzero, forM) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Maybe (MaybeT, runMaybeT) import qualified Data.ByteString as BS @@ -64,6 +70,7 @@ import qualified Language.Haskell.TH.Syntax as TH import qualified Text.Parser.Token as Parser import qualified Data.HashSet as HashSet + #if MIN_VERSION_base(4,9,0) import Data.Semigroup (Semigroup, (<>)) #else @@ -201,6 +208,7 @@ baseTypesTable = Map.fromList -- along with its documentation's section headers. -- -- Integral types + , (C.Bool, [t| CBool |]) , (C.Char Nothing, [t| CChar |]) , (C.Char (Just C.Signed), [t| CSChar |]) , (C.Char (Just C.Unsigned), [t| CUChar |]) @@ -263,7 +271,30 @@ convertType purity cTypes = runMaybeT . go goDecl = go . C.parameterDeclarationType go :: C.Type C.CIdentifier -> MaybeT TH.Q TH.Type - go cTy = case cTy of + go cTy = do + case cTy of + C.TypeSpecifier _specs (C.CxxTemplate ident' cTys) -> do +-- let symbol = TH.LitT (TH.StrTyLit (C.unCIdentifier ident')) + symbol <- case Map.lookup (C.TypeName ident') cTypes of + Nothing -> mzero + Just ty -> return ty + hsTy <- forM cTys $ \cTys' -> go (C.TypeSpecifier undefined cTys') + case hsTy of + (a:[]) -> + lift [t| $(symbol) $(return a) |] + (a:b:[]) -> + lift [t| $(symbol) '($(return a),$(return b))|] + (a:b:c:[]) -> + lift [t| $(symbol) '($(return a),$(return b),$(return c))|] + (a:b:c:d:[]) -> + lift [t| $(symbol) '($(return a),$(return b),$(return c),$(return d))|] + (a:b:c:d:e:[]) -> + lift [t| $(symbol) '($(return a),$(return b),$(return c),$(return d),$(return e))|] + [] -> fail $ "Can not find template parameters." + _ -> fail $ "Find too many template parameters. num = " ++ show (length hsTy) + C.TypeSpecifier _specs (C.CxxTemplateConst num) -> do + let n = (TH.LitT (TH.NumTyLit (read num))) + lift [t| $(return n) |] C.TypeSpecifier _specs cSpec -> case Map.lookup cSpec cTypes of Nothing -> mzero @@ -451,7 +482,8 @@ vecLenAntiQuoter :: AntiQuoter HaskellIdentifier vecLenAntiQuoter = AntiQuoter { aqParser = do hId <- C.parseIdentifier - let cId = mangleHaskellIdentifier hId + useCpp <- C.parseEnableCpp + let cId = mangleHaskellIdentifier useCpp hId return (cId, C.TypeSpecifier mempty (C.Long C.Signed), hId) , aqMarshaller = \_purity _cTypes cTy cId -> do case cTy of @@ -486,7 +518,8 @@ bsPtrAntiQuoter :: AntiQuoter HaskellIdentifier bsPtrAntiQuoter = AntiQuoter { aqParser = do hId <- C.parseIdentifier - let cId = mangleHaskellIdentifier hId + useCpp <- C.parseEnableCpp + let cId = mangleHaskellIdentifier useCpp hId return (cId, C.Ptr [] (C.TypeSpecifier mempty (C.Char Nothing)), hId) , aqMarshaller = \_purity _cTypes cTy cId -> do case cTy of @@ -503,7 +536,8 @@ bsLenAntiQuoter :: AntiQuoter HaskellIdentifier bsLenAntiQuoter = AntiQuoter { aqParser = do hId <- C.parseIdentifier - let cId = mangleHaskellIdentifier hId + useCpp <- C.parseEnableCpp + let cId = mangleHaskellIdentifier useCpp hId return (cId, C.TypeSpecifier mempty (C.Long C.Signed), hId) , aqMarshaller = \_purity _cTypes cTy cId -> do case cTy of @@ -521,7 +555,8 @@ bsCStrAntiQuoter :: AntiQuoter HaskellIdentifier bsCStrAntiQuoter = AntiQuoter { aqParser = do hId <- C.parseIdentifier - let cId = mangleHaskellIdentifier hId + useCpp <- C.parseEnableCpp + let cId = mangleHaskellIdentifier useCpp hId return (cId, C.Ptr [] (C.TypeSpecifier mempty (C.Char Nothing)), hId) , aqMarshaller = \_purity _cTypes cTy cId -> do case cTy of @@ -543,10 +578,11 @@ cDeclAqParser => m (C.CIdentifier, C.Type C.CIdentifier, HaskellIdentifier) cDeclAqParser = do cTy <- Parser.parens C.parseParameterDeclaration + useCpp <- C.parseEnableCpp case C.parameterDeclarationId cTy of Nothing -> fail "Every captured function must be named (funCtx)" Just hId -> do - let cId = mangleHaskellIdentifier hId + let cId = mangleHaskellIdentifier useCpp hId cTy' <- deHaskellifyCType $ C.parameterDeclarationType cTy return (cId, cTy', hId) @@ -554,7 +590,8 @@ deHaskellifyCType :: C.CParser HaskellIdentifier m => C.Type HaskellIdentifier -> m (C.Type C.CIdentifier) deHaskellifyCType = traverse $ \hId -> do - case C.cIdentifierFromString (unHaskellIdentifier hId) of + useCpp <- C.parseEnableCpp + case C.cIdentifierFromString useCpp (unHaskellIdentifier hId) of Left err -> fail $ "Illegal Haskell identifier " ++ unHaskellIdentifier hId ++ " in C type:\n" ++ err Right x -> return x diff --git a/inline-c/src/Language/C/Inline/HaskellIdentifier.hs b/inline-c/src/Language/C/Inline/HaskellIdentifier.hs index 57b48c9..137f4ee 100644 --- a/inline-c/src/Language/C/Inline/HaskellIdentifier.hs +++ b/inline-c/src/Language/C/Inline/HaskellIdentifier.hs @@ -44,27 +44,28 @@ newtype HaskellIdentifier = HaskellIdentifier {unHaskellIdentifier :: String} instance IsString HaskellIdentifier where fromString s = - case haskellIdentifierFromString s of + case haskellIdentifierFromString True s of Left err -> error $ "HaskellIdentifier fromString: invalid string " ++ s ++ ":\n" ++ err Right x -> x instance PP.Pretty HaskellIdentifier where pretty = PP.text . unHaskellIdentifier -haskellIdentifierFromString :: String -> Either String HaskellIdentifier -haskellIdentifierFromString s = +haskellIdentifierFromString :: Bool -> String -> Either String HaskellIdentifier +haskellIdentifierFromString useCpp s = case C.runCParser cpc "haskellIdentifierFromString" s (parseHaskellIdentifier <* eof) of Left err -> Left $ show err Right x -> Right x where - cpc = haskellCParserContext HashSet.empty + cpc = haskellCParserContext useCpp HashSet.empty -haskellCParserContext :: C.TypeNames -> C.CParserContext HaskellIdentifier -haskellCParserContext typeNames = C.CParserContext +haskellCParserContext :: Bool -> C.TypeNames -> C.CParserContext HaskellIdentifier +haskellCParserContext useCpp typeNames = C.CParserContext { C.cpcTypeNames = typeNames , C.cpcParseIdent = parseHaskellIdentifier , C.cpcIdentName = "Haskell identifier" , C.cpcIdentToString = unHaskellIdentifier + , C.cpcEnableCpp = useCpp } -- | See @@ -121,15 +122,15 @@ parseHaskellIdentifier = do -- | Mangles an 'HaskellIdentifier' to produce a valid 'C.CIdentifier' -- which still sort of resembles the 'HaskellIdentifier'. -mangleHaskellIdentifier :: HaskellIdentifier -> C.CIdentifier -mangleHaskellIdentifier (HaskellIdentifier hs) = +mangleHaskellIdentifier :: Bool -> HaskellIdentifier -> C.CIdentifier +mangleHaskellIdentifier useCpp (HaskellIdentifier hs) = -- The leading underscore if we have no valid chars is because then -- we'd have an identifier starting with numbers. let cs = (if null valid then "_" else "") ++ valid ++ (if null mangled || null valid then "" else "_") ++ mangled - in case C.cIdentifierFromString cs of + in case C.cIdentifierFromString useCpp cs of Left err -> error $ "mangleHaskellIdentifier: produced bad C identifier\n" ++ err Right x -> x where diff --git a/inline-c/src/Language/C/Inline/Internal.hs b/inline-c/src/Language/C/Inline/Internal.hs index bf1b288..57832c7 100644 --- a/inline-c/src/Language/C/Inline/Internal.hs +++ b/inline-c/src/Language/C/Inline/Internal.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} @@ -155,7 +156,11 @@ initialiseModuleState mbContext = do 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 @@ -392,8 +397,9 @@ inlineItems 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 funName of + cFunName <- case C.cIdentifierFromString (ctxForeignSrcLang ctx == Just TH.LangCxx) funName of Left err -> fail $ "inlineItems: impossible, generated bad C identifier " ++ "funName:\n" ++ err Right x -> return x @@ -463,9 +469,9 @@ data ParseTypedC = ParseTypedC -- the root. parseTypedC :: forall m. C.CParser HaskellIdentifier m - => AntiQuoters -> m ParseTypedC + => Bool -> AntiQuoters -> m ParseTypedC -- ^ Returns the return type, the captured variables, and the body. -parseTypedC antiQs = do +parseTypedC useCpp antiQs = do -- Parse return type (consume spaces first) Parser.spaces cRetType <- purgeHaskellIdentifiers =<< C.parseType @@ -525,14 +531,14 @@ parseTypedC antiQs = do Nothing -> fail $ pretty80 $ "Un-named captured variable in decl" <+> PP.pretty decl Just hId -> return hId - id' <- freshId $ mangleHaskellIdentifier 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 (C.unCIdentifier s ++ "_inline_c_" ++ show c) of + case C.cIdentifierFromString useCpp (C.unCIdentifier s ++ "_inline_c_" ++ show c) of Left _err -> error "freshId: The impossible happened" Right x -> return x @@ -547,7 +553,7 @@ parseTypedC antiQs = do => C.Type HaskellIdentifier -> n (C.Type C.CIdentifier) purgeHaskellIdentifiers cTy = for cTy $ \hsIdent -> do let hsIdentS = unHaskellIdentifier hsIdent - case C.cIdentifierFromString hsIdentS of + 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 @@ -575,8 +581,8 @@ genericQuote purity build = quoteCode $ \s -> do here <- TH.location ParseTypedC cType cParams cExp <- runParserInQ s - (haskellCParserContext (typeNamesFromTypesTable (ctxTypesTable ctx))) - (parseTypedC (ctxAntiQuoters ctx)) + (haskellCParserContext (ctxForeignSrcLang ctx == Just TH.LangCxx) (typeNamesFromTypesTable (ctxTypesTable ctx))) + (parseTypedC (ctxForeignSrcLang ctx == Just TH.LangCxx) (ctxAntiQuoters ctx)) hsType <- cToHs ctx cType hsParams <- forM cParams $ \(_cId, cTy, parTy) -> do case parTy of @@ -648,7 +654,7 @@ funPtrQuote :: TH.Safety -> TH.QuasiQuoter funPtrQuote callSafety = quoteCode $ \code -> do loc <- TH.location ctx <- getContext - FunPtrDecl{..} <- runParserInQ code (C.cCParserContext (typeNamesFromTypesTable (ctxTypesTable ctx))) parse + FunPtrDecl{..} <- runParserInQ code (C.cCParserContext (ctxForeignSrcLang ctx == Just TH.LangCxx) (typeNamesFromTypesTable (ctxTypesTable ctx))) parse hsRetType <- cToHs ctx funPtrReturnType hsParams <- forM funPtrParameters (\(_ident, typ_) -> cToHs ctx typ_) let hsFunType = convertCFunSig hsRetType hsParams diff --git a/inline-c/src/Language/C/Types.hs b/inline-c/src/Language/C/Types.hs index a5c4d39..51cedbb 100644 --- a/inline-c/src/Language/C/Types.hs +++ b/inline-c/src/Language/C/Types.hs @@ -48,6 +48,7 @@ module Language.C.Types , parseParameterDeclaration , parseParameterList , parseIdentifier + , parseEnableCpp , parseType -- * Convert to and from high-level views @@ -61,9 +62,10 @@ module Language.C.Types ) where import Control.Arrow (second) -import Control.Monad (when, unless, forM_) +import Control.Monad (when, unless, forM_, forM) import Control.Monad.State (execState, modify) -import Data.List (partition) +import Control.Monad.Reader (ask) +import Data.List (partition, intersperse) import Data.Maybe (fromMaybe) import Data.Typeable (Typeable) import Text.PrettyPrint.ANSI.Leijen ((), (<+>)) @@ -89,6 +91,7 @@ import qualified Language.C.Types.Parse as P data TypeSpecifier = Void + | Bool | Char (Maybe Sign) | Short Sign | Int Sign @@ -100,6 +103,8 @@ data TypeSpecifier | TypeName P.CIdentifier | Struct P.CIdentifier | Enum P.CIdentifier + | CxxTemplate P.CIdentifier [TypeSpecifier] + | CxxTemplateConst String deriving (Typeable, Show, Eq, Ord) data Specifiers = Specifiers @@ -203,43 +208,54 @@ untangleDeclarationSpecifiers declSpecs = do unless (null specs) $ illegalSpecifiers "expecting no specifiers" let checkNoLength = when (longs > 0 || shorts > 0) $ illegalSpecifiers "unexpected long/short" - tySpec <- case dataType of - P.TypeName s -> do - checkNoSpecs - return $ TypeName s - P.Struct s -> do - checkNoSpecs - return $ Struct s - P.Enum s -> do - checkNoSpecs - return $ Enum s - P.VOID -> do - checkNoSpecs - return Void - P.CHAR -> do - checkNoLength - return $ Char mbSign - P.INT | longs == 0 && shorts == 0 -> do - return $ Int sign - P.INT | longs == 1 -> do - return $ Long sign - P.INT | longs == 2 -> do - return $ LLong sign - P.INT | shorts == 1 -> do - return $ Short sign - P.INT -> do - illegalSpecifiers "too many long/short" - P.FLOAT -> do - checkNoLength - return Float - P.DOUBLE -> do - if longs == 1 - then return LDouble - else do + let type2type dat = case dat of + P.CxxTemplate s args -> do + checkNoSpecs + args' <- forM args type2type + return $ CxxTemplate s args' + P.CxxTemplateConst s -> do + checkNoSpecs + return $ CxxTemplateConst s + P.TypeName s -> do + checkNoSpecs + return $ TypeName s + P.Struct s -> do + checkNoSpecs + return $ Struct s + P.Enum s -> do + checkNoSpecs + return $ Enum s + P.VOID -> do + checkNoSpecs + return Void + P.BOOL -> do checkNoLength - return Double - _ -> do - error $ "untangleDeclarationSpecifiers impossible: " ++ show dataType + return $ Bool + P.CHAR -> do + checkNoLength + return $ Char mbSign + P.INT | longs == 0 && shorts == 0 -> do + return $ Int sign + P.INT | longs == 1 -> do + return $ Long sign + P.INT | longs == 2 -> do + return $ LLong sign + P.INT | shorts == 1 -> do + return $ Short sign + P.INT -> do + illegalSpecifiers "too many long/short" + P.FLOAT -> do + checkNoLength + return Float + P.DOUBLE -> do + if longs == 1 + then return LDouble + else do + checkNoLength + return Double + _ -> do + error $ "untangleDeclarationSpecifiers impossible: " ++ show dataType + tySpec <- type2type dataType return (Specifiers pStorage pTyQuals pFunSpecs, tySpec) untangleDeclarator @@ -368,8 +384,9 @@ tangleParameterDeclaration (ParameterDeclaration mbId ty00) = tangleTypeSpecifier :: Specifiers -> TypeSpecifier -> [P.DeclarationSpecifier] tangleTypeSpecifier (Specifiers storages tyQuals funSpecs) tySpec = - let pTySpecs = case tySpec of + let pTySpecs ty = case ty of Void -> [P.VOID] + Bool -> [P.BOOL] Char Nothing -> [P.CHAR] Char (Just Signed) -> [P.SIGNED, P.CHAR] Char (Just Unsigned) -> [P.UNSIGNED, P.CHAR] @@ -387,10 +404,12 @@ tangleTypeSpecifier (Specifiers storages tyQuals funSpecs) tySpec = TypeName s -> [P.TypeName s] Struct s -> [P.Struct s] Enum s -> [P.Enum s] + CxxTemplate s types -> [P.CxxTemplate s (concat (map pTySpecs types))] + CxxTemplateConst s -> [P.CxxTemplateConst s] in map P.StorageClassSpecifier storages ++ map P.TypeQualifier tyQuals ++ map P.FunctionSpecifier funSpecs ++ - map P.TypeSpecifier pTySpecs + map P.TypeSpecifier (pTySpecs tySpec) ------------------------------------------------------------------------ -- To english @@ -458,6 +477,11 @@ parseParameterList = parseIdentifier :: P.CParser i m => m i parseIdentifier = P.identifier_no_lex +parseEnableCpp :: P.CParser i m => m Bool +parseEnableCpp = do + ctx <- ask + return (P.cpcEnableCpp ctx) + parseType :: (P.CParser i m, PP.Pretty i) => m (Type i) parseType = parameterDeclarationType <$> parseParameterDeclaration @@ -467,6 +491,7 @@ parseType = parameterDeclarationType <$> parseParameterDeclaration instance PP.Pretty TypeSpecifier where pretty tySpec = case tySpec of Void -> "void" + Bool -> "bool" Char Nothing -> "char" Char (Just Signed) -> "signed char" Char (Just Unsigned) -> "unsigned char" @@ -484,6 +509,8 @@ instance PP.Pretty TypeSpecifier where TypeName s -> PP.pretty s Struct s -> "struct" <+> PP.pretty s Enum s -> "enum" <+> PP.pretty s + CxxTemplate s args -> PP.pretty s <+> "<" <+> mconcat (intersperse "," (map PP.pretty args)) <+> " >" + CxxTemplateConst s -> PP.pretty s instance PP.Pretty UntangleErr where pretty err = case err of diff --git a/inline-c/src/Language/C/Types/Parse.hs b/inline-c/src/Language/C/Types/Parse.hs index 028130e..222441f 100644 --- a/inline-c/src/Language/C/Types/Parse.hs +++ b/inline-c/src/Language/C/Types/Parse.hs @@ -89,6 +89,7 @@ module Language.C.Types.Parse import Control.Applicative import Control.Monad (msum, void, MonadPlus, unless, when) import Control.Monad.Reader (MonadReader, runReaderT, ReaderT, asks, ask) +import Data.List (intersperse) import Data.Functor.Identity (Identity) import qualified Data.HashSet as HashSet import Data.Hashable (Hashable) @@ -122,14 +123,15 @@ data CParserContext i = CParserContext , cpcParseIdent :: forall m. CParser i m => m i -- ^ Parses an identifier, *without consuming whitespace afterwards*. , cpcIdentToString :: i -> String + , cpcEnableCpp :: Bool } -- | A type for C identifiers. newtype CIdentifier = CIdentifier {unCIdentifier :: String} deriving (Typeable, Eq, Ord, Show, Hashable) -cIdentifierFromString :: String -> Either String CIdentifier -cIdentifierFromString s = +cIdentifierFromString :: Bool -> String -> Either String CIdentifier +cIdentifierFromString useCpp s = -- Note: it's important not to use 'cidentifier_raw' here, otherwise -- we go in a loop: -- @@ -139,19 +141,22 @@ cIdentifierFromString s = case Parsec.parse (identNoLex cIdentStyle <* eof) "cIdentifierFromString" s of Left err -> Left $ show err Right x -> Right $ CIdentifier x + where + identNoLex = if useCpp then cxxIdentNoLex else cIdentNoLex instance IsString CIdentifier where fromString s = - case cIdentifierFromString s of + case cIdentifierFromString True s of Left err -> error $ "CIdentifier fromString: invalid string " ++ show s ++ "\n" ++ err Right x -> x -cCParserContext :: TypeNames -> CParserContext CIdentifier -cCParserContext typeNames = CParserContext +cCParserContext :: Bool -> TypeNames -> CParserContext CIdentifier +cCParserContext useCpp typeNames = CParserContext { cpcTypeNames = typeNames - , cpcParseIdent = cidentifier_no_lex + , cpcParseIdent = if useCpp then cxxidentifier_no_lex else cidentifier_no_lex , cpcIdentToString = unCIdentifier , cpcIdentName = "C identifier" + , cpcEnableCpp = useCpp } ------------------------------------------------------------------------ @@ -212,13 +217,14 @@ quickCParser typeNames s p = case runCParser typeNames "quickCParser" s p of -- | Like 'quickCParser', but uses @'cCParserContext' ('const' 'False')@ as -- 'CParserContext'. quickCParser_ - :: String + :: Bool + -> String -- ^ String to parse. -> (ReaderT (CParserContext CIdentifier) (Parsec.Parsec String ()) a) -- ^ Parser. Anything with type @forall m. CParser i m => m a@ is a -- valid argument. -> a -quickCParser_ = quickCParser (cCParserContext HashSet.empty) +quickCParser_ useCpp = quickCParser (cCParserContext useCpp HashSet.empty) cReservedWords :: HashSet.HashSet String cReservedWords = HashSet.fromList @@ -282,6 +288,7 @@ storage_class_specifier = msum data TypeSpecifier = VOID + | BOOL | CHAR | SHORT | INT @@ -293,30 +300,39 @@ data TypeSpecifier | Struct CIdentifier | Enum CIdentifier | TypeName CIdentifier + | CxxTemplate CIdentifier [TypeSpecifier] + -- See "Template non-type arguments" in https://en.cppreference.com/w/cpp/language/template_parameters + -- Generally, const value of template-arguments supports numerical value, pointer and reference, + -- but almost classes use numerial value only. + | CxxTemplateConst String deriving (Typeable, Eq, Show) type_specifier :: CParser i m => m TypeSpecifier -type_specifier = msum - [ VOID <$ reserve cIdentStyle "void" - , CHAR <$ reserve cIdentStyle "char" - , SHORT <$ reserve cIdentStyle "short" - , INT <$ reserve cIdentStyle "int" - , LONG <$ reserve cIdentStyle "long" - , FLOAT <$ reserve cIdentStyle "float" - , DOUBLE <$ reserve cIdentStyle "double" - , SIGNED <$ reserve cIdentStyle "signed" - , UNSIGNED <$ reserve cIdentStyle "unsigned" - , Struct <$> (reserve cIdentStyle "struct" >> cidentifier) - , Enum <$> (reserve cIdentStyle "enum" >> cidentifier) - , TypeName <$> type_name - ] +type_specifier = do + ctx <- ask + let identifier' = if (cpcEnableCpp ctx) then token cxxidentifier_no_lex else token cidentifier_no_lex + msum [ VOID <$ reserve cIdentStyle "void" + , BOOL <$ reserve cIdentStyle "bool" + , CHAR <$ reserve cIdentStyle "char" + , SHORT <$ reserve cIdentStyle "short" + , INT <$ reserve cIdentStyle "int" + , LONG <$ reserve cIdentStyle "long" + , FLOAT <$ reserve cIdentStyle "float" + , DOUBLE <$ reserve cIdentStyle "double" + , SIGNED <$ reserve cIdentStyle "signed" + , UNSIGNED <$ reserve cIdentStyle "unsigned" + , Struct <$> (reserve cIdentStyle "struct" >> identifier') + , Enum <$> (reserve cIdentStyle "enum" >> identifier') + , template_parser + , TypeName <$> type_name + ] identifier :: CParser i m => m i identifier = token identifier_no_lex -isTypeName :: TypeNames -> String -> Bool -isTypeName typeNames id_ = - case cIdentifierFromString id_ of +isTypeName :: Bool -> TypeNames -> String -> Bool +isTypeName useCpp typeNames id_ = + case cIdentifierFromString useCpp id_ of -- If it's not a valid C identifier, then it's definitely not a C type name. Left _err -> False Right s -> HashSet.member s typeNames @@ -325,14 +341,17 @@ identifier_no_lex :: CParser i m => m i identifier_no_lex = try $ do ctx <- ask id_ <- cpcParseIdent ctx cpcIdentName ctx - when (isTypeName (cpcTypeNames ctx) (cpcIdentToString ctx id_)) $ + when (isTypeName (cpcEnableCpp ctx) (cpcTypeNames ctx) (cpcIdentToString ctx id_)) $ unexpected $ "type name " ++ cpcIdentToString ctx id_ return id_ -- | Same as 'cidentifier_no_lex', but does not check that the -- identifier is not a type name. cidentifier_raw :: (TokenParsing m, Monad m) => m CIdentifier -cidentifier_raw = identNoLex cIdentStyle +cidentifier_raw = cIdentNoLex cIdentStyle + +cxxidentifier_raw :: (TokenParsing m, Monad m) => m CIdentifier +cxxidentifier_raw = cxxIdentNoLex cIdentStyle -- | This parser parses a 'CIdentifier' and nothing else -- it does not consume -- trailing spaces and the like. @@ -344,17 +363,39 @@ cidentifier_no_lex = try $ do unexpected $ "type name " ++ unCIdentifier s return s -cidentifier :: CParser i m => m CIdentifier -cidentifier = token cidentifier_no_lex +cxxidentifier_no_lex :: CParser i m => m CIdentifier +cxxidentifier_no_lex = try $ do + s <- cxxidentifier_raw + typeNames <- asks cpcTypeNames + when (HashSet.member s typeNames) $ + unexpected $ "type name " ++ unCIdentifier s + return s type_name :: CParser i m => m CIdentifier type_name = try $ do - s <- ident cIdentStyle "type name" + ctx <- ask + s <- if (cpcEnableCpp ctx) then token (cxxIdentNoLex cIdentStyle) else token (cIdentNoLex cIdentStyle) typeNames <- asks cpcTypeNames unless (HashSet.member s typeNames) $ unexpected $ "identifier " ++ unCIdentifier s return s +templateParser :: (Monad m, CharParsing m, CParser i m) => m TypeSpecifier +templateParser = parse' + where + parse' = do + id' <- cxxIdentParser s + _ <- string "<" + args <- templateArgParser + _ <- string ">" + return $ CxxTemplate (CIdentifier id') args + templateArgType = try type_specifier <|> (CxxTemplateConst <$> (many $ oneOf ['0'..'9'])) + templateArgParser = sepBy templateArgType (symbol ",") + s = cIdentStyle + +template_parser :: CParser i m => m TypeSpecifier +template_parser = try $ templateParser "template name" + data TypeQualifier = CONST | RESTRICT @@ -514,6 +555,7 @@ instance Pretty StorageClassSpecifier where instance Pretty TypeSpecifier where pretty tySpec = case tySpec of VOID -> "void" + BOOL -> "bool" CHAR -> "char" SHORT -> "short" INT -> "int" @@ -525,6 +567,8 @@ instance Pretty TypeSpecifier where Struct x -> "struct" <+> pretty x Enum x -> "enum" <+> pretty x TypeName x -> pretty x + CxxTemplate x args -> pretty x <+> "<" <+> mconcat (intersperse "," (map pretty args)) <+> " >" + CxxTemplateConst x -> pretty x instance Pretty TypeQualifier where pretty tyQual = case tyQual of @@ -746,9 +790,23 @@ many1 p = (:) <$> p <*> many p -- Utils ------------------------------------------------------------------------ -identNoLex :: (TokenParsing m, Monad m, IsString s) => IdentifierStyle m -> m s -identNoLex s = fmap fromString $ try $ do +cIdentNoLex :: (TokenParsing m, Monad m, IsString s) => IdentifierStyle m -> m s +cIdentNoLex s = fmap fromString $ try $ do name <- highlight (_styleHighlight s) ((:) <$> _styleStart s <*> many (_styleLetter s) _styleName s) when (HashSet.member name (_styleReserved s)) $ unexpected $ "reserved " ++ _styleName s ++ " " ++ show name return name + +cxxIdentParser :: (Monad m, CharParsing m) => IdentifierStyle m -> m [Char] +cxxIdentParser s = cidentParserWithNamespace + where + cidentParser = ((:) <$> _styleStart s <*> many (_styleLetter s) _styleName s) + cidentParserWithNamespace = + try (concat <$> sequence [cidentParser, (string "::"), cidentParserWithNamespace]) <|> + cidentParser + +cxxIdentNoLex :: (TokenParsing m, Monad m, IsString s) => IdentifierStyle m -> m s +cxxIdentNoLex s = fmap fromString $ try $ do + name <- highlight (_styleHighlight s) (cxxIdentParser s) + when (HashSet.member name (_styleReserved s)) $ unexpected $ "reserved " ++ _styleName s ++ " " ++ show name + return name diff --git a/inline-c/test/Language/C/Inline/ContextSpec.hs b/inline-c/test/Language/C/Inline/ContextSpec.hs index 9c7ec2b..890b197 100644 --- a/inline-c/test/Language/C/Inline/ContextSpec.hs +++ b/inline-c/test/Language/C/Inline/ContextSpec.hs @@ -6,10 +6,12 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DataKinds #-} module Language.C.Inline.ContextSpec (spec) where import Control.Monad.Trans.Class (lift) import Data.Word +import qualified Data.Map as Map import qualified Test.Hspec as Hspec import Text.Parser.Char import Text.Parser.Combinators @@ -22,7 +24,12 @@ import Control.Applicative ((<*), (*>)) #endif import qualified Language.C.Types as C +import qualified Language.C.Types.Parse as P import Language.C.Inline.Context +import GHC.Exts( IsString(..) ) + +data Vec a +data Ary a spec :: Hspec.SpecWith () spec = do @@ -30,6 +37,8 @@ spec = do shouldBeType (cty "int") [t| CInt |] Hspec.it "converts simple type correctly (2)" $ do shouldBeType (cty "char") [t| CChar |] + Hspec.it "converts bool" $ do + shouldBeType (cty "bool") [t| CBool |] Hspec.it "converts void" $ do shouldBeType (cty "void") [t| () |] Hspec.it "converts standard library types (1)" $ do @@ -77,6 +86,16 @@ spec = do shouldBeType (cty "char *(*(**foo [])(int x))[]") [t| CArray (Ptr (FunPtr (CInt -> IO (Ptr (CArray (Ptr CChar)))))) |] + Hspec.it "converts vector" $ do + shouldBeType (cty "vector") [t| Vec CInt |] + Hspec.it "converts std::vector" $ do + shouldBeType (cty "std::vector") [t| Vec CInt |] + Hspec.it "converts std::vector*" $ do + shouldBeType (cty "std::vector*") [t| Ptr (Vec CInt) |] + Hspec.it "converts array" $ do + shouldBeType (cty "array") [t| Ary '(CInt,10) |] + Hspec.it "converts array*" $ do + shouldBeType (cty "array*") [t| Ptr (Ary '(CInt,10)) |] where goodConvert cTy = do mbHsTy <- TH.runQ $ convertType IO baseTypes cTy @@ -90,10 +109,14 @@ spec = do x `Hspec.shouldBe` y assertParse p s = - case C.runCParser (C.cCParserContext (typeNamesFromTypesTable baseTypes)) "spec" s (lift spaces *> p <* lift eof) of + case C.runCParser (C.cCParserContext True (typeNamesFromTypesTable baseTypes)) "spec" s (lift spaces *> p <* lift eof) of Left err -> error $ "Parse error (assertParse): " ++ show err Right x -> x cty s = C.parameterDeclarationType $ assertParse C.parseParameterDeclaration s - baseTypes = ctxTypesTable baseCtx + baseTypes = ctxTypesTable baseCtx `mappend` Map.fromList [ + (C.TypeName (fromString "vector" :: P.CIdentifier), [t|Vec|]), + (C.TypeName (fromString "std::vector" :: P.CIdentifier), [t|Vec|]), + (C.TypeName (fromString "array" :: P.CIdentifier), [t|Ary|]) + ] diff --git a/inline-c/test/Language/C/Inline/ParseSpec.hs b/inline-c/test/Language/C/Inline/ParseSpec.hs index 92c943a..8936f44 100644 --- a/inline-c/test/Language/C/Inline/ParseSpec.hs +++ b/inline-c/test/Language/C/Inline/ParseSpec.hs @@ -85,7 +85,7 @@ spec = do -> IO (C.Type C.CIdentifier, [(C.CIdentifier, C.Type C.CIdentifier, ParameterType)], String) strictParse s = do let ParseTypedC retType pars body = - assertParse haskellCParserContext (parseTypedC (ctxAntiQuoters ctx)) s + assertParse (haskellCParserContext True) (parseTypedC True (ctxAntiQuoters ctx)) s void $ evaluate $ length $ show (retType, pars, body) return (retType, pars, body) @@ -94,7 +94,7 @@ spec = do cty :: String -> C.Type C.CIdentifier cty s = C.parameterDeclarationType $ - assertParse C.cCParserContext C.parseParameterDeclaration s + assertParse (C.cCParserContext True) C.parseParameterDeclaration s shouldMatchParameters :: [(C.CIdentifier, C.Type C.CIdentifier, ParameterType)] diff --git a/inline-c/test/Language/C/Types/ParseSpec.hs b/inline-c/test/Language/C/Types/ParseSpec.hs index b98b777..a6e0870 100644 --- a/inline-c/test/Language/C/Types/ParseSpec.hs +++ b/inline-c/test/Language/C/Types/ParseSpec.hs @@ -39,7 +39,7 @@ spec = do ParameterDeclarationWithTypeNames typeNames ty <- arbitraryParameterDeclarationWithTypeNames unCIdentifier return $ isGoodType ty QC.==> - let ty' = assertParse (cCParserContext typeNames) parameter_declaration (prettyOneLine ty) + let ty' = assertParse (cCParserContext True typeNames) parameter_declaration (prettyOneLine ty) in Types.untangleParameterDeclaration ty == Types.untangleParameterDeclaration ty' Hspec.it "parses everything which is pretty-printable (Haskell)" $ do #if MIN_VERSION_QuickCheck(2,9,0) @@ -50,7 +50,7 @@ spec = do ParameterDeclarationWithTypeNames typeNames ty <- arbitraryParameterDeclarationWithTypeNames unHaskellIdentifier return $ isGoodHaskellIdentifierType typeNames ty QC.==> - let ty' = assertParse (haskellCParserContext typeNames) parameter_declaration (prettyOneLine ty) + let ty' = assertParse (haskellCParserContext True typeNames) parameter_declaration (prettyOneLine ty) in Types.untangleParameterDeclaration ty == Types.untangleParameterDeclaration ty' ------------------------------------------------------------------------ @@ -83,7 +83,7 @@ isGoodHaskellIdentifierType typeNames ty0 = Just i -> let -- see leadingSegment : _ = splitOn "." (unHaskellIdentifier i) - in case cIdentifierFromString leadingSegment of + in case cIdentifierFromString False leadingSegment of Left{} -> True Right seg -> not (seg `HashSet.member` typeNames) @@ -195,7 +195,7 @@ arbitraryIdentifierFrom :: (Hashable i, QC.Arbitrary i) => ArbitraryContext i -> QC.Gen i arbitraryIdentifierFrom ctx = do id' <- QC.arbitrary - if isTypeName (acTypeNames ctx) (acIdentToString ctx id') + if isTypeName True (acTypeNames ctx) (acIdentToString ctx id') then arbitraryIdentifierFrom ctx else return id' diff --git a/inline-c/test/tests.hs b/inline-c/test/tests.hs index 1bc8e4a..67b0c43 100644 --- a/inline-c/test/tests.hs +++ b/inline-c/test/tests.hs @@ -66,8 +66,8 @@ main = Hspec.hspec $ do Nothing -- no postfix here [t| CInt -> CInt |] - (C.quickCParser_ "int" C.parseType) - [("x", C.quickCParser_ "int" C.parseType)] + (C.quickCParser_ True "int" C.parseType) + [("x", C.quickCParser_ True "int" C.parseType)] [r| return x + 3; |]) c_add3 1 `Hspec.shouldBe` 1 + 3 Hspec.it "inlineExp" $ do @@ -77,7 +77,7 @@ main = Hspec.hspec $ do TH.Safe here [t| CInt |] - (C.quickCParser_ "int" C.parseType) + (C.quickCParser_ True "int" C.parseType) [] [r| 1 + 4 |]) x `Hspec.shouldBe` 1 + 4 @@ -223,3 +223,7 @@ main = Hspec.hspec $ do [C.exp| void { $(void (*fp)(int *))($(int *x_ptr)) } |] x <- peek x_ptr x `Hspec.shouldBe` 42 + Hspec.it "cpp namespace identifiers" $ do + C.cIdentifierFromString True "Test::Test" `Hspec.shouldBe` Right "Test::Test" + Hspec.it "cpp template identifiers" $ do + C.cIdentifierFromString True "std::vector" `Hspec.shouldBe` Right "std::vector"