diff --git a/include/cppast/compile_config.hpp b/include/cppast/compile_config.hpp index 3e17ad6..484fc35 100644 --- a/include/cppast/compile_config.hpp +++ b/include/cppast/compile_config.hpp @@ -104,6 +104,9 @@ namespace cppast { } + compile_config(const compile_config&) = default; + compile_config& operator=(const compile_config&) = default; + ~compile_config() noexcept = default; void add_flag(std::string flag) diff --git a/include/cppast/detail/intrusive_list.hpp b/include/cppast/detail/intrusive_list.hpp index cbc4c1b..ab68837 100644 --- a/include/cppast/detail/intrusive_list.hpp +++ b/include/cppast/detail/intrusive_list.hpp @@ -14,6 +14,8 @@ namespace cppast { + class cpp_file; + namespace detail { template @@ -122,22 +124,20 @@ namespace cppast intrusive_list() = default; //=== modifiers ===// - template + template < + typename Dummy = T, + typename = typename std::enable_if::value>::type> + void push_back(std::unique_ptr obj) noexcept + { + push_back_impl(std::move(obj)); + } + + template < + typename U, + typename = typename std::enable_if::value, U>::type> void push_back(const U& parent, std::unique_ptr obj) noexcept { - DEBUG_ASSERT(obj != nullptr, detail::assert_handler{}); - - if (last_) - { - auto ptr = intrusive_list_access::set_next(last_.value(), std::move(obj)); - last_ = type_safe::ref(*ptr); - } - else - { - first_ = std::move(obj); - last_ = type_safe::opt_ref(first_.get()); - } - + push_back_impl(std::move(obj)); intrusive_list_access::on_insert(last_.value(), parent); } @@ -192,6 +192,22 @@ namespace cppast } private: + void push_back_impl(std::unique_ptr obj) + { + DEBUG_ASSERT(obj != nullptr, detail::assert_handler{}); + + if (last_) + { + auto ptr = intrusive_list_access::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 first_; type_safe::optional_ref last_; }; diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index a5f24ef..add114f 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -62,6 +62,16 @@ namespace cppast 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: using database = void*; database database_; @@ -94,6 +104,9 @@ namespace cppast libclang_compile_config(const libclang_compilation_database& database, 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. /// \notes It will be used for preprocessing. 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; }; + /// 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 find_config_for( + const libclang_compilation_database& database, std::string file_name); + /// A parser that uses libclang. class libclang_parser final : public parser { public: + using config = libclang_compile_config; + explicit libclang_parser(type_safe::object_ref logger); ~libclang_parser() noexcept override; @@ -136,6 +160,29 @@ namespace cppast struct impl; std::unique_ptr 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 + void parse_files(FileParser& parser, Range&& file_names, + const libclang_compilation_database& database) + { + static_assert(std::is_same::value, + "must use the libclang parser"); + parse_files(parser, std::forward(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 #endif // CPPAST_LIBCLANG_PARSER_HPP_INCLUDED diff --git a/include/cppast/parser.hpp b/include/cppast/parser.hpp index ef35fbb..5efa181 100644 --- a/include/cppast/parser.hpp +++ b/include/cppast/parser.hpp @@ -74,6 +74,9 @@ namespace cppast /// Base class for a parser. /// /// 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 { public: @@ -136,6 +139,104 @@ namespace cppast type_safe::object_ref logger_; mutable std::atomic error_; }; + + /// A simple `FileParser` that parses all files synchronously. + /// + /// More advanced parsers could use a thread pool, for example. + template + class simple_file_parser + { + static_assert(std::is_base_of::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 + explicit simple_file_parser(type_safe::object_ref idx, + Args&&... args) + : parser_(std::forward(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 files() const noexcept + { + return type_safe::ref(files_); + } + + private: + Parser parser_; + detail::intrusive_list files_; + type_safe::object_ref 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 + auto parse_files(FileParser& parser, Range&& file_names, const Configuration& get_config) -> + typename std::enable_if(file_names).begin()))>::type, + typename FileParser::config>::value>::type + { + for (auto&& file : std::forward(file_names)) + { + auto&& config = get_config(file); + parser.parse(std::forward(file), + std::forward(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 + void parse_files(FileParser& parser, Range&& file_names, typename FileParser::config config) + { + parse_files(parser, std::forward(file_names), + [&](const std::string&) { return config; }); + } } // namespace cppast #endif // CPPAST_PARSER_HPP_INCLUDED diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index df27ec7..7d3ab71 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -51,6 +51,15 @@ libclang_compilation_database::~libclang_compilation_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 { 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)); } +type_safe::optional 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 { detail::cxindex index; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1ac93b9..1d40796 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -28,6 +28,7 @@ set(tests cpp_variable.cpp integration.cpp libclang_parser.cpp + parser.cpp visitor.cpp) add_executable(cppast_test test.cpp test_parser.hpp ${tests}) diff --git a/test/parser.cpp b/test/parser.cpp new file mode 100644 index 0000000..3bc4944 --- /dev/null +++ b/test/parser.cpp @@ -0,0 +1,71 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +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 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 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++); +}