diff --git a/include/cppast/cpp_entity_ref.hpp b/include/cppast/cpp_entity_ref.hpp index 497c475..d99def9 100644 --- a/include/cppast/cpp_entity_ref.hpp +++ b/include/cppast/cpp_entity_ref.hpp @@ -32,13 +32,10 @@ namespace cppast return target_; } - /// \returns The [cppast::cpp_entity]() it refers to. - /// \requires An entity of matching kind must be registered in the [cppast::cpp_entity_index]() using the given id. - const T& get(const cpp_entity_index& idx) const noexcept + /// \returns An optional reference to the [cppast::cpp_entity]() it refers to. + type_safe::optional_ref get(const cpp_entity_index& idx) const noexcept { auto entity = idx.lookup(target_); - DEBUG_ASSERT(Predicate{}(entity.value()), detail::precondition_error_handler{}, - "invalid entity"); return static_cast(entity.value()); } diff --git a/include/cppast/cpp_preprocessor.hpp b/include/cppast/cpp_preprocessor.hpp index 0f321e4..fc3fbb2 100644 --- a/include/cppast/cpp_preprocessor.hpp +++ b/include/cppast/cpp_preprocessor.hpp @@ -74,6 +74,8 @@ namespace cppast class cpp_include_directive final : public cpp_entity { public: + static cpp_entity_kind kind() noexcept; + /// \returns A newly built include directive. /// \notes It is not meant to be registered in the [cppast::cpp_entity_index](), /// as no other [cppast::cpp_entity]() can refer to it. diff --git a/src/cpp_preprocessor.cpp b/src/cpp_preprocessor.cpp index c6266ca..e7bc5f0 100644 --- a/src/cpp_preprocessor.cpp +++ b/src/cpp_preprocessor.cpp @@ -18,7 +18,12 @@ cpp_entity_kind cpp_macro_definition::do_get_entity_kind() const noexcept return kind(); } -cpp_entity_kind cpp_include_directive::do_get_entity_kind() const noexcept +cpp_entity_kind cpp_include_directive::kind() noexcept { return cpp_entity_kind::include_directive_t; } + +cpp_entity_kind cpp_include_directive::do_get_entity_kind() const noexcept +{ + return kind(); +} diff --git a/src/libclang/preprocessor.cpp b/src/libclang/preprocessor.cpp index 8ce8edb..bf7ed07 100644 --- a/src/libclang/preprocessor.cpp +++ b/src/libclang/preprocessor.cpp @@ -35,9 +35,10 @@ namespace // -E: print preprocessor output // -CC: keep comments, even in macro // -dD: print macro definitions as well + // -dI: print include directives as well // -Wno-pragma-once-outside-header: hide wrong warning std::string cmd(detail::libclang_compile_config_access::clang_binary(c) - + " -E -CC -dD -Wno-pragma-once-outside-header "); + + " -E -CC -dD -dI -Wno-pragma-once-outside-header "); // add other flags for (auto& flag : detail::libclang_compile_config_access::flags(c)) @@ -240,6 +241,43 @@ namespace return result; } + std::unique_ptr parse_include(position& p) + { + // format (at new line, literal <>): #include + // or: #include "filename" + if (!p.was_newl() || !starts_with(p, "#include")) + return nullptr; + p.skip(std::strlen("#include")); + skip_spaces(p); + + auto include_kind = cpp_include_kind::system; + auto end_str = ""; + if (starts_with(p, "\"")) + { + include_kind = cpp_include_kind::local; + end_str = "\""; + } + else if (starts_with(p, "<")) + { + include_kind = cpp_include_kind::system; + end_str = ">"; + } + else + DEBUG_UNREACHABLE(detail::assert_handler{}); + p.skip(); + + std::string filename; + for (; !starts_with(p, "\"") && !starts_with(p, ">"); p.skip()) + filename += *p.ptr(); + DEBUG_ASSERT(starts_with(p, end_str), detail::assert_handler{}, "bad termination"); + p.skip(); + DEBUG_ASSERT(starts_with(p, "\n"), detail::assert_handler{}); + // don't skip newline + + return cpp_include_directive::build(cpp_file_ref(cpp_entity_id(filename), filename), + include_kind); + } + bool skip_pragma(position& p) { // format (at new line): #pragma \n @@ -356,6 +394,11 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co }), result.entities.end()); } + else if (auto include = parse_include(p)) + { + if (file_depth == 0u) + result.entities.push_back({std::move(include), p.cur_line()}); + } else if (skip_pragma(p)) continue; else if (auto lm = parse_linemarker(p)) @@ -370,16 +413,8 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co if (file_depth == 0u && lm.value().file.front() != '<') { // this file is directly included by the given file - // so build entity (first, the write updates the line count) - result.entities.push_back( - {cpp_include_directive::build(cpp_file_ref(cpp_entity_id(lm.value().file), - lm.value().file), - // not really correct, but nice approximation - lm.value().is_system ? - cpp_include_kind::system : - cpp_include_kind::local), - p.cur_line()}); - // but also write the include directive again + // write include with full path + // note: don't build include here, do it when an #include is encountered p.write_str("#include \"" + lm.value().file + "\"\n"); } diff --git a/test/cpp_preprocessor.cpp b/test/cpp_preprocessor.cpp index daba84b..1f10a19 100644 --- a/test/cpp_preprocessor.cpp +++ b/test/cpp_preprocessor.cpp @@ -57,3 +57,45 @@ baz }); REQUIRE(count == 6u); } + +// requires clang 4.0, currently not available for testing +#if 0 +TEST_CASE("cpp_include_directive") +{ + write_file("cpp_include_directive-header.hpp", R"( +#define FOO +)"); + + auto header_a = R"( +#include +#include "cpp_include_directive-header.hpp" +)"; + + auto header_b = R"( +#include "header_a.hpp" +)"; + + cpp_entity_index idx; + auto file_a = parse(idx, "header_a.hpp", header_a); + auto file_b = parse(idx, "header_b.hpp", header_b); + + auto count = + test_visit(*file_a, [&](const cpp_include_directive& include) { + if (include.name() == "iostream") + { + REQUIRE(include.target().name() == include.name()); + REQUIRE(include.include_kind() == cppast::cpp_include_kind::system); + REQUIRE(!include.target().get(idx)); + } + else if (include.name() == "cpp_include_directive-header.hpp") + { + REQUIRE(include.target().name() == include.name()); + REQUIRE(include.include_kind() == cppast::cpp_include_kind::local); + REQUIRE(!include.target().get(idx)); + } + else + REQUIRE(false); + }); + REQUIRE(count == 2u); +} +#endif