diff --git a/include/cppast/compile_config.hpp b/include/cppast/compile_config.hpp index 9f64fa1d..4cb76f1b 100644 --- a/include/cppast/compile_config.hpp +++ b/include/cppast/compile_config.hpp @@ -15,7 +15,7 @@ namespace cppast { -/// The C++ standard that should be used. +/// The C/C++ standard that should be used. enum class cpp_standard { cpp_98, @@ -28,7 +28,14 @@ enum class cpp_standard cpp_20, cpp_2b, + c_89, + c_99, + c_11, + c_17, + c_2x, + cpp_latest = cpp_standard::cpp_14, //< The latest supported C++ standard. + c_latest = cpp_standard::c_17, //< The latest supported C standard. }; /// \returns A human readable string representing the option, @@ -55,12 +62,50 @@ inline const char* to_string(cpp_standard standard) noexcept return "c++20"; case cpp_standard::cpp_2b: return "c++2b"; + + case cpp_standard::c_89: + return "c89"; + case cpp_standard::c_99: + return "c99"; + case cpp_standard::c_11: + return "c11"; + case cpp_standard::c_17: + return "c17"; + case cpp_standard::c_2x: + return "c2x"; } DEBUG_UNREACHABLE(detail::assert_handler{}); return "ups"; } +/// \returns whether the language standard is a C standard +inline bool is_c_standard(cpp_standard standard) noexcept +{ + switch (standard) + { + case cpp_standard::cpp_98: + case cpp_standard::cpp_03: + case cpp_standard::cpp_11: + case cpp_standard::cpp_14: + case cpp_standard::cpp_1z: + case cpp_standard::cpp_17: + case cpp_standard::cpp_2a: + case cpp_standard::cpp_20: + case cpp_standard::cpp_2b: + return false; + case cpp_standard::c_89: + case cpp_standard::c_99: + case cpp_standard::c_11: + case cpp_standard::c_17: + case cpp_standard::c_2x: + return true; + } + + DEBUG_UNREACHABLE(detail::assert_handler{}); + return false; +} + /// Other special compilation flags. enum class compile_flag { @@ -117,6 +162,12 @@ class compile_config return do_get_name(); } + /// \returns Whether to parse files as C rather than C++. + bool use_c() const noexcept + { + return do_use_c(); + } + protected: compile_config(std::vector def_flags) : flags_(std::move(def_flags)) {} @@ -160,6 +211,9 @@ class compile_config /// \notes This allows detecting mismatches of configurations and parsers. virtual const char* do_get_name() const noexcept = 0; + /// \returns Whether to parse files as C rather than C++. + virtual bool do_use_c() const noexcept = 0; + std::vector flags_; }; } // namespace cppast diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index 9ebab6d8..bb82d504 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -187,10 +187,13 @@ class libclang_compile_config final : public compile_config return "libclang"; } + bool do_use_c() const noexcept override; + std::string clang_binary_; bool write_preprocessed_ : 1; bool fast_preprocessing_ : 1; bool remove_comments_in_macro_ : 1; + bool use_c_ : 1; friend detail::libclang_compile_config_access; }; diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 132ef702..04a161cd 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -82,7 +82,7 @@ libclang_compile_config::libclang_compile_config() : libclang_compile_config(CPP libclang_compile_config::libclang_compile_config(std::string clang_binary) : compile_config({}), write_preprocessed_(false), fast_preprocessing_(false), - remove_comments_in_macro_(false) + remove_comments_in_macro_(false), use_c_(false) { // set given clang binary set_clang_binary(clang_binary); @@ -226,6 +226,11 @@ cppast::libclang_compile_config::libclang_compile_config( for (auto i = 0u; i != size; ++i) { auto cmd = clang_CompileCommands_getCommand(commands.get(), i); + + // If ++ exists within the compiler name (e.g. clang++, g++, etc), use C++ + std::string exe(clang_getCString(clang_CompileCommand_getArg(cmd, 0))); + use_c_ = (exe.find("++", 0) == std::string::npos); + auto dir = detail::cxstring(clang_CompileCommand_getDirectory(cmd)); parse_flags(cmd, [&](std::string flag, std::string args) { if (flag == "-I") @@ -243,11 +248,21 @@ cppast::libclang_compile_config::libclang_compile_config( add_flag(std::move(flag)); } else if (flag == "-std") - // standard + { + use_c_ = (args.find("++") == std::string::npos); add_flag(std::move(flag) + "=" + std::move(args)); + } else if (flag == "-f") // other options add_flag(std::move(flag) + std::move(args)); + else if (flag == "-x") + { + // language + if (args == "c") + use_c_ = true; + else + use_c_ = false; + } }); } } @@ -270,8 +285,9 @@ bool is_valid_binary(const std::string& binary) void add_default_include_dirs(libclang_compile_config& config) { std::string verbose_output; + std::string language = config.use_c() ? "-xc" : "-xc++"; tpl::Process process( - detail::libclang_compile_config_access::clang_binary(config) + " -x c++ -v -", "", + detail::libclang_compile_config_access::clang_binary(config) + " " + language + " -v -", "", [](const char*, std::size_t) {}, [&](const char* str, std::size_t n) { verbose_output.append(str, n); }, true); process.write("", 1); @@ -424,6 +440,57 @@ void libclang_compile_config::do_set_flags(cpp_standard standard, compile_flags add_flag("-std=c++2a"); break; } + else + throw std::invalid_argument("c++2b is not yet supported for current version of clang"); + case cpp_standard::c_89: + if (flags & compile_flag::gnu_extensions) + add_flag("-std=gnu89"); + else + add_flag("-std=c89"); + break; + case cpp_standard::c_99: + if (flags & compile_flag::gnu_extensions) + add_flag("-std=gnu99"); + else + add_flag("-std=c99"); + break; + case cpp_standard::c_11: + if (flags & compile_flag::gnu_extensions) + add_flag("-std=gnu11"); + else + add_flag("-std=c11"); + break; + case cpp_standard::c_17: + if (libclang_parser::libclang_minor_version() >= 45) + { // Corresponds to Clang version 6 + if (flags & compile_flag::gnu_extensions) + add_flag("-std=gnu17"); + else + add_flag("-std=c17"); + break; + } + else + throw std::invalid_argument("c17 is not yet supported for current version of clang"); + case cpp_standard::c_2x: + if (libclang_parser::libclang_minor_version() >= 59) + { // Corresponds to Clang version 9 + if (flags & compile_flag::gnu_extensions) + add_flag("-std=gnu2x"); + else + add_flag("-std=c2x"); + break; + } + else + throw std::invalid_argument("c2x is not yet supported for current version of clang"); + } + + // Add language flag for C or C++ + if (is_c_standard(standard)) { + add_flag("-xc"); + use_c_ = true; + } else { + add_flag("-xc++"); + use_c_ = false; } if (flags & compile_flag::ms_compatibility) @@ -461,6 +528,11 @@ void libclang_compile_config::do_remove_macro_definition(std::string name) add_flag("-U" + std::move(name)); } +bool libclang_compile_config::do_use_c() const noexcept +{ + return use_c_; +} + type_safe::optional cppast::find_config_for( const libclang_compilation_database& database, std::string file_name) { @@ -474,7 +546,7 @@ type_safe::optional cppast::find_config_for( if (database.has_config(file_name)) return libclang_compile_config(database, std::move(file_name)); static const char* extensions[] - = {".h", ".hpp", ".cpp", ".h++", ".c++", ".hxx", ".cxx", ".hh", ".cc", ".H", ".C"}; + = {".h", ".hpp", ".cpp", ".h++", ".c++", ".hxx", ".cxx", ".hh", ".cc", ".H", ".C", ".c"}; for (auto ext : extensions) { auto name = file_name + ext; @@ -511,8 +583,8 @@ namespace std::vector get_arguments(const libclang_compile_config& config) { std::vector args - // TODO: Why? and Why? - = {"-x", "c++", "-I."}; // force C++ and enable current directory for include search + // TODO: Why? + = {"-I."}; // enable current directory for include search for (auto& flag : detail::libclang_compile_config_access::flags(config)) args.push_back(flag.c_str()); return args; diff --git a/src/libclang/preprocessor.cpp b/src/libclang/preprocessor.cpp index dae6b18f..c02ce066 100644 --- a/src/libclang/preprocessor.cpp +++ b/src/libclang/preprocessor.cpp @@ -174,11 +174,12 @@ std::string diagnostics_flags() // get the command that returns all macros defined in the TU std::string get_macro_command(const libclang_compile_config& c, const char* full_path) { - // -x c++: force C++ as input language + // -xc/-xc++: force C or C++ as input language // -I.: add current working directory to include search path // -E: print preprocessor output // -dM: print macro definitions instead of preprocessed file - auto flags = std::string("-x c++ -I. -E -dM"); + std::string language = c.use_c() ? "-xc" : "-xc++"; + auto flags = language + " -I. -E -dM"; flags += diagnostics_flags(); std::string cmd(detail::libclang_compile_config_access::clang_binary(c) + " " + std::move(flags) @@ -198,10 +199,11 @@ std::string get_macro_command(const libclang_compile_config& c, const char* full std::string get_preprocess_command(const libclang_compile_config& c, const char* full_path, const char* macro_file_path) { - // -x c++: force C++ as input language + // -xc/-xc++: force C or C++ as input language // -E: print preprocessor output // -dD: keep macros - auto flags = std::string("-x c++ -E -dD"); + std::string language = c.use_c() ? "-xc" : "-xc++"; + auto flags = language + " -E -dD"; // -CC: keep comments, even in macro // -C: keep comments, but not in macro diff --git a/test/parser.cpp b/test/parser.cpp index b5dd667e..e7865061 100644 --- a/test/parser.cpp +++ b/test/parser.cpp @@ -27,6 +27,11 @@ TEST_CASE("parse_files") { return "null"; } + + bool do_use_c() const noexcept override + { + return false; + } } config; class null_parser : public parser