Fix issues with fast preprocessing option

This commit is contained in:
Jonathan Müller 2018-02-17 13:16:14 +01:00
commit d2dc8907e2
3 changed files with 154 additions and 7 deletions

View file

@ -230,7 +230,114 @@ namespace
return "standardese-macro-file-" + std::to_string(++counter) + ".delete-me";
}
std::string write_macro_file(const libclang_compile_config& c, const char* full_path,
template <std::size_t N>
void bump_until(std::istreambuf_iterator<char>& iter, const char (&str)[N])
{
auto ptr = &str[0];
while (ptr != &str[N - 1])
{
if (iter == std::istreambuf_iterator<char>{})
// end of file
break;
else if (*iter != *ptr)
{
// try again
ptr = &str[0];
if (*iter == *ptr)
++ptr; // it was the first character again
}
else
// okay, move forward
++ptr;
++iter;
}
}
template <typename Iter>
void skip_whitespace(Iter& begin, Iter end)
{
while (begin != end && (*begin == ' ' || *begin == '\t'))
++begin;
}
template <typename Iter>
std::string get_line(Iter& begin, Iter end)
{
std::string line;
while (begin != end && *begin != '\n')
line += *begin++;
++begin; // newline
return line;
}
type_safe::optional<std::string> get_include_guard_macro(const std::string& full_path)
{
std::ifstream file(full_path);
auto iter = std::istreambuf_iterator<char>(file);
while (iter != std::istreambuf_iterator<char>{})
{
if (*iter == '/')
{
++iter;
if (*iter == '/')
// C++ style comment, bump until \n
bump_until(iter, "\n");
else if (*iter == '*')
// C style comment
bump_until(iter, "*/");
}
else if (*iter == ' ' || *iter == '\t' || *iter == '\n')
++iter; // empty
else if (*iter == '#')
{
// preprocessor line
auto if_line = get_line(iter, {});
if (if_line.compare(0, 3, "#if") != 0)
// not something starting with #if
break;
skip_whitespace(iter, {});
auto macro_line = get_line(iter, {});
if (macro_line.compare(0, 7, "#define") != 0)
// not a corresponding define
break;
auto macro_name_begin = std::next(macro_line.begin(), 7);
// skip whitespace after define
skip_whitespace(macro_name_begin, macro_line.end());
auto macro_name_end = macro_name_begin;
// skip over identifier
while (macro_name_end != macro_line.end()
&& (*macro_name_end == '_' || std::isalnum(*macro_name_end)))
++macro_name_end;
auto trailing_ws = macro_line.rbegin();
skip_whitespace(trailing_ws, macro_line.rend());
if (macro_name_end != trailing_ws.base())
// anything else after macro
break;
std::string macro_name(macro_name_begin, macro_name_end);
if (if_line.find(macro_name) == std::string::npos)
// macro name doesn't occur in if line
break;
else
return macro_name;
}
else
// line is neither empty, comment, nor preprocessor
break;
}
// assume no include guard followed a bad line
return type_safe::nullopt;
}
std::string write_macro_file(const libclang_compile_config& c, const std::string& full_path,
const diagnostic_logger& logger)
{
std::string diagnostic;
@ -252,13 +359,17 @@ namespace
auto file = get_macro_file_name();
std::ofstream stream(file);
auto cmd = get_macro_command(c, full_path);
auto cmd = get_macro_command(c, full_path.c_str());
tpl::Process process(cmd, "",
[&](const char* str, std::size_t n) {
stream.write(str, std::streamsize(n));
},
diagnostic_logger);
if (auto include_guard = get_include_guard_macro(full_path))
// undefine include guard
stream << "#undef " << include_guard.value();
auto exit_code = process.get_exit_status();
DEBUG_ASSERT(diagnostic.empty(), detail::assert_handler{});
if (exit_code != 0)
@ -275,7 +386,8 @@ namespace
};
clang_preprocess_result clang_preprocess_impl(const libclang_compile_config& c,
const char* full_path, const char* macro_path)
const std::string& full_path,
const char* macro_path)
{
clang_preprocess_result result;
@ -299,7 +411,7 @@ namespace
diagnostic.push_back(*str);
};
auto cmd = get_preprocess_command(c, full_path, macro_path);
auto cmd = get_preprocess_command(c, full_path.c_str(), macro_path);
tpl::Process process(cmd, "",
[&](const char* str, std::size_t n) {
result.file.reserve(result.file.size() + n);

View file

@ -28,7 +28,38 @@ TEST_CASE("preprocessor_parses_escaped_character", "[!hide][clang4]")
libclang_compile_config config;
config.set_flags(cpp_standard::cpp_latest);
auto&& preprocessed = detail::preprocess(config, "ppec.cpp", default_logger().get());
auto preprocessed = detail::preprocess(config, "ppec.cpp", default_logger().get());
REQUIRE(preprocessed.includes.size() == 1);
REQUIRE(preprocessed.includes[0].file_name == "ppec.hpp");
}
TEST_CASE("fast_preprocessing include guard")
{
auto file_name = "fast_preprocessing_include_guard";
write_file(file_name, R"(
// This is a C++ comment that should get skipped.
/// This as well.
/* C style comments
#ifndef FALSE_POSITIVE
#define FALSE_POSITIVE
*/ #ifndef INCLUDE_GUARD // the include guard macro
#define INCLUDE_GUARD
struct foo {};
#endif
)");
libclang_compile_config config;
config.set_flags(cpp_standard::cpp_latest);
config.fast_preprocessing(true);
auto result = detail::preprocess(config, file_name, default_logger().get());
REQUIRE(result.macros.size() == 1u);
REQUIRE(result.macros[0].macro->name() == "INCLUDE_GUARD");
}

View file

@ -209,7 +209,8 @@ int main(int argc, char* argv[]) try
cxxopts::value<std::vector<std::string>>())
("gnu_extensions", "enable GNU extensions (equivalent to -std=gnu++XX)")
("msvc_extensions", "enable MSVC extensions (equivalent to -fms-extensions)")
("msvc_compatibility", "enable MSVC compatibility (equivalent to -fms-compatibility)");
("msvc_compatibility", "enable MSVC compatibility (equivalent to -fms-compatibility)")
("fast_preprocessing", "enable fast preprocessing, be careful, this breaks if you e.g. redefine macros in the same file!");
// clang-format on
options.parse_positional("file");
options.parse(argc, argv);
@ -219,7 +220,7 @@ int main(int argc, char* argv[]) try
else if (options.count("version"))
{
std::cout << "cppast version " << CPPAST_VERSION_STRING << "\n";
std::cout << "Copyright (C) Jonathan Müller 2017 <jonathanmueller.dev@gmail.com>\n";
std::cout << "Copyright (C) Jonathan Müller 2017-2018 <jonathanmueller.dev@gmail.com>\n";
std::cout << '\n';
std::cout << "Using libclang version " << CPPAST_CLANG_VERSION_STRING << '\n';
}
@ -248,6 +249,9 @@ int main(int argc, char* argv[]) try
if (options.count("verbose"))
config.write_preprocessed(true);
if (options.count("fast_preprocessing"))
config.fast_preprocessing(true);
if (options.count("include_directory"))
for (auto& include : options["include_directory"].as<std::vector<std::string>>())
config.add_include_dir(include);