Store full path of header as well

This commit is contained in:
Jonathan Müller 2017-06-11 22:16:56 +02:00
commit fd449dde4a
6 changed files with 169 additions and 131 deletions

View file

@ -80,9 +80,11 @@ namespace cppast
/// \notes It is not meant to be registered in the [cppast::cpp_entity_index](),
/// as no other [cppast::cpp_entity]() can refer to it.
static std::unique_ptr<cpp_include_directive> build(const cpp_file_ref& target,
cpp_include_kind kind)
cpp_include_kind kind,
std::string full_path)
{
return std::unique_ptr<cpp_include_directive>(new cpp_include_directive(target, kind));
return std::unique_ptr<cpp_include_directive>(
new cpp_include_directive(target, kind, std::move(full_path)));
}
/// \returns A reference to the [cppast::cpp_file]() it includes.
@ -97,17 +99,28 @@ namespace cppast
return kind_;
}
/// \returns The full path of the included file.
const std::string& full_path() const noexcept
{
return full_path_;
}
private:
cpp_entity_kind do_get_entity_kind() const noexcept override;
cpp_include_directive(const cpp_file_ref& target, cpp_include_kind kind)
: cpp_entity(target.name()), target_(target.id()[0u]), kind_(kind)
cpp_include_directive(const cpp_file_ref& target, cpp_include_kind kind,
std::string full_path)
: cpp_entity(target.name()),
target_(target.id()[0u]),
kind_(kind),
full_path_(std::move(full_path))
{
DEBUG_ASSERT(!target.is_overloaded(), detail::precondition_error_handler{});
}
cpp_entity_id target_;
cpp_include_kind kind_;
std::string full_path_;
};
} // namespace cppast

View file

@ -205,14 +205,16 @@ namespace
auto args = get_arguments(config);
CXTranslationUnit tu;
auto error =
auto flags = CXTranslationUnit_Incomplete | CXTranslationUnit_KeepGoing;
if (detail::libclang_compile_config_access::clang_version(config) >= 40000)
flags |= CXTranslationUnit_DetailedPreprocessingRecord;
auto error =
clang_parseTranslationUnit2(idx.get(), path, // index and path
args.data(),
static_cast<int>(args.size()), // arguments (ptr + size)
&file, 1, // unsaved files (ptr + size)
CXTranslationUnit_Incomplete
| CXTranslationUnit_KeepGoing, // flags
&tu);
unsigned(flags), &tu);
if (error != CXError_Success)
{
switch (error)
@ -259,25 +261,42 @@ std::unique_ptr<cpp_file> libclang_parser::do_parse(const cpp_entity_index& idx,
auto file = clang_getFile(tu.get(), path.c_str());
cpp_file::builder builder(path);
auto preprocessed_iter = preprocessed.entities.begin();
auto macro_iter = preprocessed.macros.begin();
auto include_iter = preprocessed.includes.begin();
// convert entity hierachies
detail::parse_context context{tu.get(), file, type_safe::ref(logger()), type_safe::ref(idx),
detail::comment_context(preprocessed.comments)};
detail::visit_tu(tu, path.c_str(), [&](const CXCursor& cur) {
// add macro if needed
for (auto line = get_line_no(cur);
preprocessed_iter != preprocessed.entities.end() && preprocessed_iter->line <= line;
++preprocessed_iter)
builder.add_child(std::move(preprocessed_iter->entity));
if (clang_getCursorKind(cur) == CXCursor_InclusionDirective)
{
DEBUG_ASSERT(include_iter != preprocessed.includes.end()
&& get_line_no(cur) >= include_iter->line,
detail::assert_handler{});
auto entity = detail::parse_entity(context, cur);
if (entity)
builder.add_child(std::move(entity));
auto include =
cpp_include_directive::build(std::move(include_iter->file), include_iter->kind,
detail::get_cursor_name(cur).c_str());
context.comments.match(*include, include_iter->line);
builder.add_child(std::move(include));
++include_iter;
}
else if (clang_getCursorKind(cur) != CXCursor_MacroDefinition)
{
// add macro if needed
for (auto line = get_line_no(cur);
macro_iter != preprocessed.macros.end() && macro_iter->line <= line; ++macro_iter)
builder.add_child(std::move(macro_iter->macro));
auto entity = detail::parse_entity(context, cur);
if (entity)
builder.add_child(std::move(entity));
}
});
for (; preprocessed_iter != preprocessed.entities.end(); ++preprocessed_iter)
builder.add_child(std::move(preprocessed_iter->entity));
for (; macro_iter != preprocessed.macros.end(); ++macro_iter)
builder.add_child(std::move(macro_iter->macro));
for (auto& c : preprocessed.comments)
{

View file

@ -104,6 +104,11 @@ std::unique_ptr<cpp_entity> detail::parse_entity(const detail::parse_context& co
return entity;
break;
case CXCursor_MacroDefinition:
case CXCursor_InclusionDirective:
DEBUG_UNREACHABLE(detail::assert_handler{}, "handle preprocessor in parser callback");
break;
case CXCursor_Namespace:
return parse_cpp_namespace(context, cur);
case CXCursor_NamespaceAlias:

View file

@ -268,6 +268,89 @@ namespace
p.skip(std::strlen(str));
}
void skip_spaces(position& p, bool bump = false)
{
while (starts_with(p, " "))
if (bump)
p.bump();
else
p.skip();
}
struct linemarker
{
std::string file;
unsigned line;
enum
{
line_directive, // no change in file
enter_new, // open a new file
enter_old, // return to an old file
} flag = line_directive;
bool is_system = false;
};
ts::optional<linemarker> parse_linemarker(position& p)
{
// format (at new line): # <line> "<filename>" <flags>
// flag 1: enter_new
// flag 2: enter_old
// flag 3: system file
// flag 4: ignored
if (!p.was_newl() || !starts_with(p, "#"))
return ts::nullopt;
p.skip();
DEBUG_ASSERT(!starts_with(p, "define") && !starts_with(p, "undef")
&& !starts_with(p, "pragma"),
detail::assert_handler{}, "handle macros first");
linemarker result;
std::string line;
for (skip_spaces(p); std::isdigit(*p.ptr()); p.skip())
line += *p.ptr();
result.line = unsigned(std::stoi(line));
skip_spaces(p);
DEBUG_ASSERT(*p.ptr() == '"', detail::assert_handler{});
p.skip();
std::string file_name;
for (; !starts_with(p, "\""); p.skip())
file_name += *p.ptr();
p.skip();
result.file = std::move(file_name);
for (; !starts_with(p, "\n"); p.skip())
{
skip_spaces(p);
switch (*p.ptr())
{
case '1':
DEBUG_ASSERT(result.flag == linemarker::line_directive, detail::assert_handler{});
result.flag = linemarker::enter_new;
break;
case '2':
DEBUG_ASSERT(result.flag == linemarker::line_directive, detail::assert_handler{});
result.flag = linemarker::enter_old;
break;
case '3':
result.is_system = true;
break;
case '4':
break; // ignored
default:
DEBUG_UNREACHABLE(detail::assert_handler{}, "invalid line marker");
break;
}
}
p.skip();
return result;
}
detail::pp_doc_comment parse_c_doc_comment(position& p)
{
detail::pp_doc_comment result;
@ -423,15 +506,6 @@ namespace
return true;
}
void skip_spaces(position& p, bool bump = false)
{
while (starts_with(p, " "))
if (bump)
p.bump();
else
p.skip();
}
std::unique_ptr<cpp_macro_definition> parse_macro(position& p,
detail::preprocessor_output& output,
bool in_main_file)
@ -504,15 +578,13 @@ namespace
return result;
}
std::unique_ptr<cpp_include_directive> parse_include(position& p,
detail::preprocessor_output& output,
bool in_main_file)
type_safe::optional<detail::pp_include> parse_include(position& p, bool in_main_file)
{
// format (at new line, literal <>): #include <filename>
// or: #include "filename"
// note: don't write include back
if (!p.was_newl() || !starts_with(p, "#include"))
return nullptr;
return type_safe::nullopt;
p.skip(std::strlen("#include"));
if (starts_with(p, "_next"))
p.skip(std::strlen("_next"));
@ -544,16 +616,10 @@ namespace
// don't skip newline
if (!in_main_file)
return nullptr;
return type_safe::nullopt;
auto result = cpp_include_directive::build(cpp_file_ref(cpp_entity_id(filename), filename),
include_kind);
if (!output.comments.empty() && output.comments.back().matches(*result, p.cur_line()))
{
result->set_comment(std::move(output.comments.back().comment));
output.comments.pop_back();
}
return result;
return detail::pp_include{cpp_file_ref(cpp_entity_id(filename), filename), include_kind,
p.cur_line()};
}
bool bump_pragma(position& p)
@ -568,80 +634,6 @@ namespace
return true;
}
struct linemarker
{
std::string file;
unsigned line;
enum
{
line_directive, // no change in file
enter_new, // open a new file
enter_old, // return to an old file
} flag = line_directive;
bool is_system = false;
};
ts::optional<linemarker> parse_linemarker(position& p)
{
// format (at new line): # <line> "<filename>" <flags>
// flag 1: enter_new
// flag 2: enter_old
// flag 3: system file
// flag 4: ignored
if (!p.was_newl() || !starts_with(p, "#"))
return ts::nullopt;
p.skip();
DEBUG_ASSERT(!starts_with(p, "define") && !starts_with(p, "undef")
&& !starts_with(p, "pragma"),
detail::assert_handler{}, "handle macros first");
linemarker result;
std::string line;
for (skip_spaces(p); std::isdigit(*p.ptr()); p.skip())
line += *p.ptr();
result.line = unsigned(std::stoi(line));
skip_spaces(p);
DEBUG_ASSERT(*p.ptr() == '"', detail::assert_handler{});
p.skip();
std::string file_name;
for (; !starts_with(p, "\""); p.skip())
file_name += *p.ptr();
p.skip();
result.file = std::move(file_name);
for (; !starts_with(p, "\n"); p.skip())
{
skip_spaces(p);
switch (*p.ptr())
{
case '1':
DEBUG_ASSERT(result.flag == linemarker::line_directive, detail::assert_handler{});
result.flag = linemarker::enter_new;
break;
case '2':
DEBUG_ASSERT(result.flag == linemarker::line_directive, detail::assert_handler{});
result.flag = linemarker::enter_old;
break;
case '3':
result.is_system = true;
break;
case '4':
break; // ignored
default:
DEBUG_UNREACHABLE(detail::assert_handler{}, "invalid line marker");
break;
}
}
p.skip();
return result;
}
}
detail::preprocessor_output detail::preprocess(const libclang_compile_config& config,
@ -682,7 +674,7 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
severity::debug});
}
result.entities.push_back({std::move(macro), p.cur_line()});
result.macros.push_back({std::move(macro), p.cur_line()});
}
else if (auto undef = parse_undef(p))
{
@ -695,26 +687,24 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
diagnostic{std::move(message), source_location::make_file(path),
severity::debug});
}
result.entities
.erase(std::remove_if(result.entities.begin(), result.entities.end(),
[&](const pp_entity& e) {
return e.entity->kind()
== cpp_entity_kind::macro_definition_t
&& e.entity->name() == undef.value();
}),
result.entities.end());
result.macros.erase(std::remove_if(result.macros.begin(), result.macros.end(),
[&](const pp_macro& e) {
return e.macro->name() == undef.value();
}),
result.macros.end());
}
}
else if (auto include = parse_include(p, result, file_depth == 0u))
else if (auto include = parse_include(p, file_depth == 0u))
{
if (logger.is_verbose())
{
auto message = detail::format("parsing include '", include->name(), "'");
auto message =
detail::format("parsing include '", include.value().file.name(), "'");
logger.log("preprocessor",
diagnostic{std::move(message), source_location::make_file(path),
severity::debug});
}
result.entities.push_back({std::move(include), p.cur_line()});
result.includes.push_back(std::move(include.value()));
}
else if (bump_pragma(p))
continue;

View file

@ -12,10 +12,17 @@ namespace cppast
{
namespace detail
{
struct pp_entity
struct pp_macro
{
std::unique_ptr<cpp_entity> entity;
unsigned line;
std::unique_ptr<cpp_macro_definition> macro;
unsigned line;
};
struct pp_include
{
cpp_file_ref file;
cpp_include_kind kind;
unsigned line;
};
struct pp_doc_comment
@ -35,7 +42,8 @@ namespace cppast
struct preprocessor_output
{
std::string source;
std::vector<pp_entity> entities;
std::vector<pp_include> includes;
std::vector<pp_macro> macros;
std::vector<pp_doc_comment> comments;
};

View file

@ -107,12 +107,14 @@ TEST_CASE("cpp_include_directive", "[!hide][clang4]")
REQUIRE(include.target().name() == include.name());
REQUIRE(include.include_kind() == cppast::cpp_include_kind::system);
REQUIRE(include.target().get(idx).empty());
REQUIRE_THAT(include.full_path(), Catch::EndsWith("iostream"));
}
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).empty());
REQUIRE(include.full_path() == "./cpp_include_directive-header.hpp");
}
else
REQUIRE(false);
@ -126,6 +128,7 @@ TEST_CASE("cpp_include_directive", "[!hide][clang4]")
REQUIRE(include.include_kind() == cppast::cpp_include_kind::local);
REQUIRE(
equal_ref(idx, include.target(), cpp_file_ref(cpp_entity_id(""), "header_a.hpp")));
REQUIRE(include.full_path() == "./header_a.hpp");
}
else
REQUIRE(false);