From e0d34a648227f9f701ea232724c23009e45c0b93 Mon Sep 17 00:00:00 2001 From: Lucas Jansen <7199136+staticintlucas@users.noreply.github.com> Date: Mon, 23 Jan 2023 21:54:45 +0000 Subject: [PATCH 1/2] Add C language standard support --- include/cppast/compile_config.hpp | 90 ++++++++++++++++++++++++++++- include/cppast/libclang_parser.hpp | 3 + src/libclang/libclang_parser.cpp | 91 ++++++++++++++++++++++++++++-- src/libclang/preprocessor.cpp | 10 ++-- test/parser.cpp | 5 ++ 5 files changed, 189 insertions(+), 10 deletions(-) diff --git a/include/cppast/compile_config.hpp b/include/cppast/compile_config.hpp index 9f64fa1d..d358ba34 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, @@ -27,8 +27,14 @@ enum class cpp_standard cpp_2a, 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 +61,85 @@ 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 The C/C++ standard corresponding to the string, e.g. `cpp_14` for `c++14` +/// \throws std::invalid_argument for an unknown language standard +inline cpp_standard to_standard(const std::string& str) +{ + if (str == "c++98") + return cpp_standard::cpp_98; + else if (str == "c++03") + return cpp_standard::cpp_03; + else if (str == "c++11") + return cpp_standard::cpp_11; + else if (str == "c++14") + return cpp_standard::cpp_14; + else if (str == "c++1z") + return cpp_standard::cpp_1z; + else if (str == "c++17") + return cpp_standard::cpp_17; + else if (str == "c++2a") + return cpp_standard::cpp_2a; + else if (str == "c++20") + return cpp_standard::cpp_20; + else if (str == "c++2b") + return cpp_standard::cpp_2b; + else if (str == "c89") + return cpp_standard::c_89; + else if (str == "c99") + return cpp_standard::c_99; + else if (str == "c11") + return cpp_standard::c_11; + else if (str == "c17") + return cpp_standard::c_17; + else if (str == "c2x") + return cpp_standard::c_2x; + else + throw std::invalid_argument("invalid C/C++ standard '" + str + "'"); +} + +/// \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 +196,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 +245,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..5ee9be48 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,30 @@ cppast::libclang_compile_config::libclang_compile_config( add_flag(std::move(flag)); } else if (flag == "-std") + { // standard + try + { + // detect whether the language standard is a C standard + use_c_ = is_c_standard(to_standard(args)); + } + catch (const std::invalid_argument&) + { + use_c_ = false; + } 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 +294,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 +449,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 +537,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 +555,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 +592,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 From 8740788a3090247274f7c8cfd615178b8f592080 Mon Sep 17 00:00:00 2001 From: Lucas Jansen <7199136+staticintlucas@users.noreply.github.com> Date: Wed, 25 Jan 2023 21:43:12 +0000 Subject: [PATCH 2/2] Updates based on PR feedback --- include/cppast/compile_config.hpp | 38 ++----------------------------- src/libclang/libclang_parser.cpp | 11 +-------- 2 files changed, 3 insertions(+), 46 deletions(-) diff --git a/include/cppast/compile_config.hpp b/include/cppast/compile_config.hpp index d358ba34..4cb76f1b 100644 --- a/include/cppast/compile_config.hpp +++ b/include/cppast/compile_config.hpp @@ -27,6 +27,7 @@ enum class cpp_standard cpp_2a, cpp_20, cpp_2b, + c_89, c_99, c_11, @@ -61,6 +62,7 @@ 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: @@ -77,42 +79,6 @@ inline const char* to_string(cpp_standard standard) noexcept return "ups"; } -/// \returns The C/C++ standard corresponding to the string, e.g. `cpp_14` for `c++14` -/// \throws std::invalid_argument for an unknown language standard -inline cpp_standard to_standard(const std::string& str) -{ - if (str == "c++98") - return cpp_standard::cpp_98; - else if (str == "c++03") - return cpp_standard::cpp_03; - else if (str == "c++11") - return cpp_standard::cpp_11; - else if (str == "c++14") - return cpp_standard::cpp_14; - else if (str == "c++1z") - return cpp_standard::cpp_1z; - else if (str == "c++17") - return cpp_standard::cpp_17; - else if (str == "c++2a") - return cpp_standard::cpp_2a; - else if (str == "c++20") - return cpp_standard::cpp_20; - else if (str == "c++2b") - return cpp_standard::cpp_2b; - else if (str == "c89") - return cpp_standard::c_89; - else if (str == "c99") - return cpp_standard::c_99; - else if (str == "c11") - return cpp_standard::c_11; - else if (str == "c17") - return cpp_standard::c_17; - else if (str == "c2x") - return cpp_standard::c_2x; - else - throw std::invalid_argument("invalid C/C++ standard '" + str + "'"); -} - /// \returns whether the language standard is a C standard inline bool is_c_standard(cpp_standard standard) noexcept { diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 5ee9be48..04a161cd 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -249,16 +249,7 @@ cppast::libclang_compile_config::libclang_compile_config( } else if (flag == "-std") { - // standard - try - { - // detect whether the language standard is a C standard - use_c_ = is_c_standard(to_standard(args)); - } - catch (const std::invalid_argument&) - { - use_c_ = false; - } + use_c_ = (args.find("++") == std::string::npos); add_flag(std::move(flag) + "=" + std::move(args)); } else if (flag == "-f")