Fix include handling support

Can't be tested until clang 4.0.
This commit is contained in:
Jonathan Müller 2017-02-16 20:33:48 +01:00
commit 2e1dce2ffd
5 changed files with 98 additions and 17 deletions

View file

@ -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<const T> 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<const T&>(entity.value());
}

View file

@ -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.

View file

@ -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();
}

View file

@ -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<cpp_include_directive> parse_include(position& p)
{
// format (at new line, literal <>): #include <filename>
// 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 <stuff..>\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");
}

View file

@ -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 <iostream>
#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<cpp_include_directive>(*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