Add parse_files() utility function
This commit is contained in:
parent
6b7ca183f9
commit
38831db073
7 changed files with 286 additions and 14 deletions
|
|
@ -104,6 +104,9 @@ namespace cppast
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compile_config(const compile_config&) = default;
|
||||||
|
compile_config& operator=(const compile_config&) = default;
|
||||||
|
|
||||||
~compile_config() noexcept = default;
|
~compile_config() noexcept = default;
|
||||||
|
|
||||||
void add_flag(std::string flag)
|
void add_flag(std::string flag)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
namespace cppast
|
namespace cppast
|
||||||
{
|
{
|
||||||
|
class cpp_file;
|
||||||
|
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
@ -122,22 +124,20 @@ namespace cppast
|
||||||
intrusive_list() = default;
|
intrusive_list() = default;
|
||||||
|
|
||||||
//=== modifiers ===//
|
//=== modifiers ===//
|
||||||
template <typename U>
|
template <
|
||||||
|
typename Dummy = T,
|
||||||
|
typename = typename std::enable_if<std::is_same<Dummy, cpp_file>::value>::type>
|
||||||
|
void push_back(std::unique_ptr<T> obj) noexcept
|
||||||
|
{
|
||||||
|
push_back_impl(std::move(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename U,
|
||||||
|
typename = typename std::enable_if<!std::is_same<T, cpp_file>::value, U>::type>
|
||||||
void push_back(const U& parent, std::unique_ptr<T> obj) noexcept
|
void push_back(const U& parent, std::unique_ptr<T> obj) noexcept
|
||||||
{
|
{
|
||||||
DEBUG_ASSERT(obj != nullptr, detail::assert_handler{});
|
push_back_impl(std::move(obj));
|
||||||
|
|
||||||
if (last_)
|
|
||||||
{
|
|
||||||
auto ptr = intrusive_list_access<T>::set_next(last_.value(), std::move(obj));
|
|
||||||
last_ = type_safe::ref(*ptr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
first_ = std::move(obj);
|
|
||||||
last_ = type_safe::opt_ref(first_.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
intrusive_list_access<T>::on_insert(last_.value(), parent);
|
intrusive_list_access<T>::on_insert(last_.value(), parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +192,22 @@ namespace cppast
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void push_back_impl(std::unique_ptr<T> obj)
|
||||||
|
{
|
||||||
|
DEBUG_ASSERT(obj != nullptr, detail::assert_handler{});
|
||||||
|
|
||||||
|
if (last_)
|
||||||
|
{
|
||||||
|
auto ptr = intrusive_list_access<T>::set_next(last_.value(), std::move(obj));
|
||||||
|
last_ = type_safe::ref(*ptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first_ = std::move(obj);
|
||||||
|
last_ = type_safe::opt_ref(first_.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<T> first_;
|
std::unique_ptr<T> first_;
|
||||||
type_safe::optional_ref<T> last_;
|
type_safe::optional_ref<T> last_;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,16 @@ namespace cppast
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \returns Whether or not the database contains information about the given file.
|
||||||
|
/// \group has_config
|
||||||
|
bool has_config(const char* file_name) const;
|
||||||
|
|
||||||
|
/// \group has_config
|
||||||
|
bool has_config(const std::string& file_name) const
|
||||||
|
{
|
||||||
|
return has_config(file_name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using database = void*;
|
using database = void*;
|
||||||
database database_;
|
database database_;
|
||||||
|
|
@ -94,6 +104,9 @@ namespace cppast
|
||||||
libclang_compile_config(const libclang_compilation_database& database,
|
libclang_compile_config(const libclang_compilation_database& database,
|
||||||
const std::string& file);
|
const std::string& file);
|
||||||
|
|
||||||
|
libclang_compile_config(const libclang_compile_config& other) = default;
|
||||||
|
libclang_compile_config& operator=(const libclang_compile_config& other) = default;
|
||||||
|
|
||||||
/// \effects Sets the path to the location of the `clang++` binary and the version of that binary.
|
/// \effects Sets the path to the location of the `clang++` binary and the version of that binary.
|
||||||
/// \notes It will be used for preprocessing.
|
/// \notes It will be used for preprocessing.
|
||||||
void set_clang_binary(std::string binary, int major, int minor, int patch)
|
void set_clang_binary(std::string binary, int major, int minor, int patch)
|
||||||
|
|
@ -122,10 +135,21 @@ namespace cppast
|
||||||
friend detail::libclang_compile_config_access;
|
friend detail::libclang_compile_config_access;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Finds a configuration for a given file.
|
||||||
|
///
|
||||||
|
/// \returns If the database contains a configuration for the given file, returns that configuration.
|
||||||
|
/// Otherwise removes the file extension of the file and tries the same procedure
|
||||||
|
/// for common C++ header and source file extensions.
|
||||||
|
/// \notes This function is intended to be used as the basis for a `get_config` function of [cppast::parse_files](standardese://cppast::parse_files_basic/).
|
||||||
|
type_safe::optional<libclang_compile_config> find_config_for(
|
||||||
|
const libclang_compilation_database& database, std::string file_name);
|
||||||
|
|
||||||
/// A parser that uses libclang.
|
/// A parser that uses libclang.
|
||||||
class libclang_parser final : public parser
|
class libclang_parser final : public parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using config = libclang_compile_config;
|
||||||
|
|
||||||
explicit libclang_parser(type_safe::object_ref<const diagnostic_logger> logger);
|
explicit libclang_parser(type_safe::object_ref<const diagnostic_logger> logger);
|
||||||
~libclang_parser() noexcept override;
|
~libclang_parser() noexcept override;
|
||||||
|
|
||||||
|
|
@ -136,6 +160,29 @@ namespace cppast
|
||||||
struct impl;
|
struct impl;
|
||||||
std::unique_ptr<impl> pimpl_;
|
std::unique_ptr<impl> pimpl_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Parses multiple files using a [cppast::libclang_parser]() and a compilation database.
|
||||||
|
///
|
||||||
|
/// \effects Invokes [cppast::parse_files](standardese://parse_files_basic/) passing it the parser and file names,
|
||||||
|
/// and a `get_config` function using [cppast::find_config_for]().
|
||||||
|
///
|
||||||
|
/// \throws [cppast::libclang_error]() if no configuration for a given file could be found in the database.
|
||||||
|
///
|
||||||
|
/// \requires `FileParser` must use [cppast::libclang_parser](),
|
||||||
|
/// i.e. `FileParser::parser` must be an alias of [cppast::libclang_parser]().
|
||||||
|
template <class FileParser, class Range>
|
||||||
|
void parse_files(FileParser& parser, Range&& file_names,
|
||||||
|
const libclang_compilation_database& database)
|
||||||
|
{
|
||||||
|
static_assert(std::is_same<typename FileParser::parser, libclang_parser>::value,
|
||||||
|
"must use the libclang parser");
|
||||||
|
parse_files(parser, std::forward<Range>(file_names), [&](const std::string& file) {
|
||||||
|
auto config = find_config_for(database, file);
|
||||||
|
if (!config)
|
||||||
|
throw libclang_error("unable to find configuration for file '" + file + "'");
|
||||||
|
return config.value();
|
||||||
|
});
|
||||||
|
}
|
||||||
} // namespace cppast
|
} // namespace cppast
|
||||||
|
|
||||||
#endif // CPPAST_LIBCLANG_PARSER_HPP_INCLUDED
|
#endif // CPPAST_LIBCLANG_PARSER_HPP_INCLUDED
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,9 @@ namespace cppast
|
||||||
/// Base class for a parser.
|
/// Base class for a parser.
|
||||||
///
|
///
|
||||||
/// It reads a C++ source file and creates the matching [cppast::cpp_file]().
|
/// It reads a C++ source file and creates the matching [cppast::cpp_file]().
|
||||||
|
/// Derived classes can implement how the file is parsed.
|
||||||
|
///
|
||||||
|
/// \requires A derived class must provide an alias `config` which is the corresponding derived class of the [cppast::compile_config]().
|
||||||
class parser
|
class parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -136,6 +139,104 @@ namespace cppast
|
||||||
type_safe::object_ref<const diagnostic_logger> logger_;
|
type_safe::object_ref<const diagnostic_logger> logger_;
|
||||||
mutable std::atomic<bool> error_;
|
mutable std::atomic<bool> error_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A simple `FileParser` that parses all files synchronously.
|
||||||
|
///
|
||||||
|
/// More advanced parsers could use a thread pool, for example.
|
||||||
|
template <class Parser>
|
||||||
|
class simple_file_parser
|
||||||
|
{
|
||||||
|
static_assert(std::is_base_of<cppast::parser, Parser>::value,
|
||||||
|
"Parser must be derived from cppast::parser");
|
||||||
|
|
||||||
|
public:
|
||||||
|
using parser = Parser;
|
||||||
|
using config = typename Parser::config;
|
||||||
|
|
||||||
|
/// \effects Creates a file parser populating the given index
|
||||||
|
/// and using the parser created by forwarding the given arguments.
|
||||||
|
template <typename... Args>
|
||||||
|
explicit simple_file_parser(type_safe::object_ref<const cpp_entity_index> idx,
|
||||||
|
Args&&... args)
|
||||||
|
: parser_(std::forward<Args>(args)...), idx_(idx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse(std::string path, const config& c)
|
||||||
|
{
|
||||||
|
auto file = parser_.parse(*idx_, std::move(path), c);
|
||||||
|
if (file)
|
||||||
|
files_.push_back(std::move(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \returns The result of [cppast::parser::error]().
|
||||||
|
bool error() const noexcept
|
||||||
|
{
|
||||||
|
return parser_.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \effects Calls [cppast::parser::reset_error]().
|
||||||
|
void reset_error() noexcept
|
||||||
|
{
|
||||||
|
parser_.reset_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \returns The index that is being populated.
|
||||||
|
const cpp_entity_index& index() const noexcept
|
||||||
|
{
|
||||||
|
return *idx_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \returns An iteratable object iterating over all the files that have been parsed so far.
|
||||||
|
/// \exclude return
|
||||||
|
detail::iteratable_intrusive_list<cpp_file> files() const noexcept
|
||||||
|
{
|
||||||
|
return type_safe::ref(files_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Parser parser_;
|
||||||
|
detail::intrusive_list<cpp_file> files_;
|
||||||
|
type_safe::object_ref<const cpp_entity_index> idx_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Parses multiple files using a given `FileParser`.
|
||||||
|
///
|
||||||
|
/// \effects Will call the `parse()` function for each path specified in the `file_names`,
|
||||||
|
/// using `get_confg` to determine the configuration.
|
||||||
|
///
|
||||||
|
/// \requires `FileParser` must be a class with the following members:
|
||||||
|
/// * `parser` - A typedef for the parser being used to do the parsing.
|
||||||
|
/// * `config` - The same as `parser::config`.
|
||||||
|
/// * `parse(path, config)` - Parses the given file with the configuration using its parser.
|
||||||
|
/// The parsing can be executed in a separated thread, but then a copy of the configuration and path must be created.
|
||||||
|
/// \requires `Range` must be some type that can be iterated in a range-based for loop.
|
||||||
|
/// \requires `Configuration` must be a function that returns a configuration of type `FileParser::config` when given a path.
|
||||||
|
/// \unique_name parse_files_basic
|
||||||
|
/// \synopsis_return void
|
||||||
|
template <class FileParser, class Range, class Configuration>
|
||||||
|
auto parse_files(FileParser& parser, Range&& file_names, const Configuration& get_config) ->
|
||||||
|
typename std::enable_if<std::is_same<typename std::decay<decltype(get_config(
|
||||||
|
*std::forward<Range>(file_names).begin()))>::type,
|
||||||
|
typename FileParser::config>::value>::type
|
||||||
|
{
|
||||||
|
for (auto&& file : std::forward<Range>(file_names))
|
||||||
|
{
|
||||||
|
auto&& config = get_config(file);
|
||||||
|
parser.parse(std::forward<decltype(file)>(file),
|
||||||
|
std::forward<decltype(config)>(config));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses multiple files using a given `FileParser` and configuration.
|
||||||
|
/// \effects Invokes [cppast::parse_files](standardese://parse_files_basic/) passing it the parser and file names,
|
||||||
|
/// and a function that returns the same configuration for each file.
|
||||||
|
template <class FileParser, class Range>
|
||||||
|
void parse_files(FileParser& parser, Range&& file_names, typename FileParser::config config)
|
||||||
|
{
|
||||||
|
parse_files(parser, std::forward<Range>(file_names),
|
||||||
|
[&](const std::string&) { return config; });
|
||||||
|
}
|
||||||
} // namespace cppast
|
} // namespace cppast
|
||||||
|
|
||||||
#endif // CPPAST_PARSER_HPP_INCLUDED
|
#endif // CPPAST_PARSER_HPP_INCLUDED
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,15 @@ libclang_compilation_database::~libclang_compilation_database()
|
||||||
clang_CompilationDatabase_dispose(database_);
|
clang_CompilationDatabase_dispose(database_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool libclang_compilation_database::has_config(const char* file_name) const
|
||||||
|
{
|
||||||
|
auto cxcommands = clang_CompilationDatabase_getCompileCommands(database_, file_name);
|
||||||
|
if (!cxcommands)
|
||||||
|
return false;
|
||||||
|
clang_CompileCommands_dispose(cxcommands);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
int parse_number(const char*& str)
|
int parse_number(const char*& str)
|
||||||
|
|
@ -260,6 +269,30 @@ void libclang_compile_config::do_remove_macro_definition(std::string name)
|
||||||
add_flag("-U" + std::move(name));
|
add_flag("-U" + std::move(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type_safe::optional<libclang_compile_config> cppast::find_config_for(
|
||||||
|
const libclang_compilation_database& database, std::string file_name)
|
||||||
|
{
|
||||||
|
if (database.has_config(file_name))
|
||||||
|
return libclang_compile_config(database, std::move(file_name));
|
||||||
|
|
||||||
|
auto dot = file_name.rfind('.');
|
||||||
|
if (dot != std::string::npos)
|
||||||
|
file_name.erase(dot);
|
||||||
|
|
||||||
|
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"};
|
||||||
|
for (auto ext : extensions)
|
||||||
|
{
|
||||||
|
auto name = file_name + ext;
|
||||||
|
if (database.has_config(name))
|
||||||
|
return libclang_compile_config(database, std::move(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return type_safe::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
struct libclang_parser::impl
|
struct libclang_parser::impl
|
||||||
{
|
{
|
||||||
detail::cxindex index;
|
detail::cxindex index;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ set(tests
|
||||||
cpp_variable.cpp
|
cpp_variable.cpp
|
||||||
integration.cpp
|
integration.cpp
|
||||||
libclang_parser.cpp
|
libclang_parser.cpp
|
||||||
|
parser.cpp
|
||||||
visitor.cpp)
|
visitor.cpp)
|
||||||
|
|
||||||
add_executable(cppast_test test.cpp test_parser.hpp ${tests})
|
add_executable(cppast_test test.cpp test_parser.hpp ${tests})
|
||||||
|
|
|
||||||
71
test/parser.cpp
Normal file
71
test/parser.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
|
||||||
|
// This file is subject to the license terms in the LICENSE file
|
||||||
|
// found in the top-level directory of this distribution.
|
||||||
|
|
||||||
|
#include <cppast/parser.hpp>
|
||||||
|
|
||||||
|
#include <catch.hpp>
|
||||||
|
|
||||||
|
using namespace cppast;
|
||||||
|
|
||||||
|
TEST_CASE("parse_files")
|
||||||
|
{
|
||||||
|
class null_compile_config : public compile_config
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
null_compile_config() : compile_config({})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void do_set_flags(cpp_standard, compile_flags) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_add_include_dir(std::string) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_add_macro_definition(std::string, std::string) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_remove_macro_definition(std::string) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* do_get_name() const noexcept override
|
||||||
|
{
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
} config;
|
||||||
|
|
||||||
|
class null_parser : public parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using config = null_compile_config;
|
||||||
|
|
||||||
|
null_parser() : parser(type_safe::ref(logger_))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<cpp_file> do_parse(const cpp_entity_index& idx, std::string path,
|
||||||
|
const compile_config&) const override
|
||||||
|
{
|
||||||
|
return cpp_file::builder(std::move(path)).finish(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr_diagnostic_logger logger_;
|
||||||
|
};
|
||||||
|
|
||||||
|
cpp_entity_index idx;
|
||||||
|
simple_file_parser<null_parser> parser(type_safe::ref(idx));
|
||||||
|
|
||||||
|
auto file_names = {"a.cpp", "b.cpp", "c.cpp"};
|
||||||
|
parse_files(parser, file_names, config);
|
||||||
|
|
||||||
|
auto iter = file_names.begin();
|
||||||
|
for (auto& file : parser.files())
|
||||||
|
REQUIRE(file.name() == *iter++);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue