commit 8ccd4bc81ef2e5429949ac47eacce5c376c16763 Author: Bartek Kryza Date: Sun Feb 7 23:13:52 2021 +0100 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..aa5584c9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +AlignTrailingComments: true +BasedOnStyle: WebKit +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 80 +Cpp11BracedListStyle: true +IndentCaseLabels: true +NamespaceIndentation: None +PointerBindsToType: false +Standard: Cpp11 +BreakBeforeBinaryOperators: false +BreakBeforeBraces: Stroustrup diff --git a/.clanguml-class.example b/.clanguml-class.example new file mode 100644 index 00000000..3851374a --- /dev/null +++ b/.clanguml-class.example @@ -0,0 +1,58 @@ +glob: + - src/config/*.cc +compilation_database_dir: build +output_directory: puml +containers: + vector: + - std::vector + map: + - std::map +diagrams: + config_class_diagram: + type: class + glob: + - src/config/*.cc + - src/config/*.h + using_namespace: clanguml::config + namespaces: + - clanguml::config + classes: + - config + - diagram + - class_diagram + methods: + - public + - protected + - private + members: + - public + puml: + - 'note top of diagram: Aggregate template' + class_diagram_model_class_diagram: + type: class + glob: + - src/uml/class_diagram_model.h + using_namespace: clanguml::model::class_diagram + namespaces: + - clanguml::model::class_diagram + classes: + - diagram + - element + - class_member + - class_element + - method_argument + - class_method + - class_parent + - class_relationship + - class_ + - enum_ + - scope_t + - relationship_t + methods: + - public + - protected + - private + members: + - public + puml: + - 'note top of diagram: Aggregate template' diff --git a/.clanguml.example b/.clanguml.example new file mode 100644 index 00000000..4cfab82a --- /dev/null +++ b/.clanguml.example @@ -0,0 +1,61 @@ +glob: + - src/*.cc +compilation_database_dir: build +diagrams: + communication: + type: class + glob: + - src/communication/*.cc + - src/communication/codec/*.cc + - src/communication/layers/*.cc + using_namespace: one::communication + namespaces: + - one::communication + - one::communication::layers + - one::communication::codec + classes: + - ConnectionPool + - Communicator + - layers::AsyncResponder + - layers::BinaryTranslator + - layers::Inbox + - layers::Replier + - layers::Retrier + - layers::Sequencer + - layers::Translator + - codec::PacketLogger + - codec::PacketEncoder + - codec::PacketDecoder + methods: + - public + - private + members: + - public + puml: + - 'note top of Communicator : Aggregate template' + http_helper: + type: class + glob: + - src/httpHelper.cc + - src/httpHelperParams.cc + classes: + - FsLogic + args: + - '-I./src' + - '-I./include' + storage_helper: + type: class + classes: + - FileHandle + - StorageHelper + - KeyValueAdapter + - KeyValueHelper + storage_detection: + type: sequence + begin: + class: aaa + method: bbb + end: + class: ccc + method: ddd + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..fe54bc87 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,17 @@ + +name: build + +on: [push] + +jobs: + build-ubuntu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: build and run + run: | + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make -j + ctest diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9436d678 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +build/ +lib/ +bin/ +*.swp + +puml diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..2a03aa87 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.10) + +project(clang-uml) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") + +set(CLANG_UML_INSTALL_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) +set(CLANG_UML_INSTALL_BIN_DIR ${PROJECT_SOURCE_DIR}/bin) + +set(UML_HEADERS_DIR ${PROJECT_SOURCE_DIR}/src/uml) + +message(STATUS "Checking for spdlog...") +find_package(spdlog REQUIRED) + +message(STATUS "Checking for yaml-cpp...") +find_package(yaml-cpp REQUIRED) + +message(STATUS "Checking for libclang...") +find_package(LibClang REQUIRED) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3 ${LIBCLANG_CXXFLAGS}") + +# Thirdparty sources +set(THIRDPARTY_HEADERS_DIR ${PROJECT_SOURCE_DIR}/thirdparty/) + +find_package(LLVM REQUIRED CONFIG) +set(CLANG_INCLUDE_DIRS "llvm/clang/include") +set(CLANG_LIBS clang) + +include_directories(${CLANG_UML_INSTALL_INCLUDE_DIR}) +include_directories(${YAML_CPP_INCLUDE_DIR}) +include_directories(${UML_HEADERS_DIR}) +include_directories(${THIRDPARTY_HEADERS_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/src/) + +add_subdirectory(tests) + +file(GLOB_RECURSE SOURCES src/*.cc include/*.h) +set(MAIN_SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cc) +list(REMOVE_ITEM SOURCES ${MAIN_SOURCE_FILE}) + +add_executable(clang-uml ${SOURCES} ${MAIN_SOURCE_FILE}) +install(TARGETS clang-uml DESTINATION ${CLANG_UML_INSTALL_BIN_DIR}) +target_link_libraries(clang-uml ${LIBCLANG_LIBRARIES} ${YAML_CPP_LIBRARIES} spdlog::spdlog) + +install( + FILES + # add include after DESTINATION, then it works + DESTINATION include ${CMAKE_INSTALL_INCLUDEDIR} +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..7f929cce --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Bartek Kryza + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..da2c70ce --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# clang-uml - Clang based UML generator ![build](https://github.com/bkryza/clang-uml/workflows/build/badge.svg) + + +### Compiling, installing and running your code +To compile the project, run the following in the project root: +``` + mkdir build + cd build + cmake .. + make +``` +To install the project in `/usr/local/`, run the following in the `build/` +directory created above: +``` + make install +``` +To run unit tests via CTest, again run the following in the `build/` directory: +``` + make test +``` +or +``` + ctest +``` + diff --git a/cmake/FindLibClang.cmake b/cmake/FindLibClang.cmake new file mode 100644 index 00000000..46efae30 --- /dev/null +++ b/cmake/FindLibClang.cmake @@ -0,0 +1,139 @@ +# FindLibClang +# +# This module searches libclang and llvm-config, the llvm-config tool is used to +# get information about the installed llvm/clang package to compile LLVM based +# programs. +# +# It defines the following variables +# +# ``LIBCLANG_LLVM_CONFIG_EXECUTABLE`` +# the llvm-config tool to get various information. +# ``LIBCLANG_LIBRARIES`` +# the clang libraries to link against to use Clang/LLVM. +# ``LIBCLANG_LIBDIR`` +# the directory where the clang libraries are located. +# ``LIBCLANG_FOUND`` +# true if libclang was found +# ``LIBCLANG_VERSION_STRING`` +# version number as a string +# ``LIBCLANG_CXXFLAGS`` +# the compiler flags for files that include LLVM headers +# +#============================================================================= +# Copyright (C) 2011, 2012, 2013 Jan Erik Hanssen and Anders Bakken +# Copyright (C) 2015 Christian Schwarzgruber +# +# This file is part of RTags (https://github.com/Andersbakken/rtags). +# +# RTags is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RTags is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RTags. If not, see . + +if (NOT LIBCLANG_ROOT_DIR) + set(LIBCLANG_ROOT_DIR $ENV{LIBCLANG_ROOT_DIR}) +endif () + +if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) + set(LIBCLANG_LLVM_CONFIG_EXECUTABLE $ENV{LIBCLANG_LLVM_CONFIG_EXECUTABLE}) + if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) + find_program(LIBCLANG_LLVM_CONFIG_EXECUTABLE "llvm-config") + endif () + if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) + if (APPLE) + execute_process(COMMAND brew --prefix llvm OUTPUT_VARIABLE BREW_LLVM_PATH RESULT_VARIABLE BREW_LLVM_RESULT) + if (NOT ${BREW_LLVM_RESULT} EQUAL 0) + set(BREW_LLVM_PATH "/usr/local/opt/llvm") + endif () + string(STRIP ${BREW_LLVM_PATH} BREW_LLVM_PATH) + find_program(LIBCLANG_LLVM_CONFIG_EXECUTABLE NAMES llvm-config PATHS "${BREW_LLVM_PATH}/bin") + else () + set(llvm_config_names llvm-config) + foreach(major RANGE 11 3) + list(APPEND llvm_config_names "llvm-config${major}" "llvm-config-${major}") + foreach(minor RANGE 9 0) + list(APPEND llvm_config_names "llvm-config${major}${minor}" "llvm-config-${major}.${minor}" "llvm-config-mp-${major}.${minor}") + endforeach () + endforeach () + find_program(LIBCLANG_LLVM_CONFIG_EXECUTABLE NAMES ${llvm_config_names}) + endif () + endif () + if (LIBCLANG_LLVM_CONFIG_EXECUTABLE) + message(STATUS "llvm-config executable found: ${LIBCLANG_LLVM_CONFIG_EXECUTABLE}") + endif () +endif () + +if (NOT LIBCLANG_CXXFLAGS) + if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) + message(FATAL_ERROR "Could NOT find llvm-config executable and LIBCLANG_CXXFLAGS is not set ") + endif () + execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --cxxflags OUTPUT_VARIABLE LIBCLANG_CXXFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT LIBCLANG_CXXFLAGS) + find_path(LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT clang-c/Index.h HINTS ${LIBCLANG_ROOT_DIR}/include NO_DEFAULT_PATH) + if (NOT EXISTS ${LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT}) + find_path(LIBCLANG_CXXFLAGS clang-c/Index.h) + if (NOT EXISTS ${LIBCLANG_CXXFLAGS}) + message(FATAL_ERROR "Could NOT find clang include path. You can fix this by setting LIBCLANG_CXXFLAGS in your shell or as a cmake variable.") + endif () + else () + set(LIBCLANG_CXXFLAGS ${LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT}) + endif () + set(LIBCLANG_CXXFLAGS "-I${LIBCLANG_CXXFLAGS}") + endif () + string(REGEX MATCHALL "-(D__?[a-zA-Z_]*|I([^\" ]+|\"[^\"]+\"))" LIBCLANG_CXXFLAGS "${LIBCLANG_CXXFLAGS}") + string(REGEX REPLACE ";" " " LIBCLANG_CXXFLAGS "${LIBCLANG_CXXFLAGS}") + set(LIBCLANG_CXXFLAGS ${LIBCLANG_CXXFLAGS} CACHE STRING "The LLVM C++ compiler flags needed to compile LLVM based applications.") + unset(LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT CACHE) +endif () + +if (NOT EXISTS ${LIBCLANG_LIBDIR}) + if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) + message(FATAL_ERROR "Could NOT find llvm-config executable and LIBCLANG_LIBDIR is not set ") + endif () + execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --libdir OUTPUT_VARIABLE LIBCLANG_LIBDIR OUTPUT_STRIP_TRAILING_WHITESPACE) + if (NOT EXISTS ${LIBCLANG_LIBDIR}) + message(FATAL_ERROR "Could NOT find clang libdir. You can fix this by setting LIBCLANG_LIBDIR in your shell or as a cmake variable.") + endif () + set(LIBCLANG_LIBDIR ${LIBCLANG_LIBDIR} CACHE STRING "Path to the clang library.") +endif () + +if (NOT LIBCLANG_LIBRARIES) + find_library(LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT NAMES clang libclang HINTS ${LIBCLANG_LIBDIR} ${LIBCLANG_ROOT_DIR}/lib NO_DEFAULT_PATH) + if (LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT) + set(LIBCLANG_LIBRARIES "${LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT}") + else () + find_library(LIBCLANG_LIBRARIES NAMES clang libclang) + if (NOT EXISTS ${LIBCLANG_LIBRARIES}) + set (LIBCLANG_LIBRARIES "-L${LIBCLANG_LIBDIR}" "-lclang" "-Wl,-rpath,${LIBCLANG_LIBDIR}") + endif () + endif () + unset(LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT CACHE) +endif () +set(LIBCLANG_LIBRARY ${LIBCLANG_LIBRARIES} CACHE FILEPATH "Path to the libclang library") + +if (NOT LIBCLANG_SYSTEM_LIBS) + execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --system-libs OUTPUT_VARIABLE LIBCLANG_SYSTEM_LIBS OUTPUT_STRIP_TRAILING_WHITESPACE) + if (LIBCLANG_SYSTEM_LIBS) + set (LIBCLANG_LIBRARIES ${LIBCLANG_LIBRARIES} ${LIBCLANG_SYSTEM_LIBS}) + endif () +endif () + +if (LIBCLANG_LLVM_CONFIG_EXECUTABLE) + execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --version OUTPUT_VARIABLE LIBCLANG_VERSION_STRING OUTPUT_STRIP_TRAILING_WHITESPACE) +else () + set(LIBCLANG_VERSION_STRING "Unknown") +endif () +message("-- Using Clang version ${LIBCLANG_VERSION_STRING} from ${LIBCLANG_LIBDIR} with CXXFLAGS ${LIBCLANG_CXXFLAGS}") + +# Handly the QUIETLY and REQUIRED arguments and set LIBCLANG_FOUND to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibClang DEFAULT_MSG LIBCLANG_LIBRARY LIBCLANG_CXXFLAGS LIBCLANG_LIBDIR) +mark_as_advanced(LIBCLANG_CXXFLAGS LIBCLANG_LIBRARY LIBCLANG_LLVM_CONFIG_EXECUTABLE LIBCLANG_LIBDIR) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 00000000..ac951ebd --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,16 @@ + +# Make an explicit list of all source files in CLANGUML_INC. This is important +# because CMake is not a build system: it is a build system generator. Suppose +# you add a file foo.cpp to src/ after running cmake .. . If you set +# CLANGUML_INC with `file(GLOB ... )`, this is not passed to the makefile; it +# doesn't know that foo.cpp exists and will not re-run cmake. Your +# collaborator's builds will fail and it will be unclear why. Whether you use +# file(GLOB ...) or not, you will need to re-run cmake, but with an explicit +# file list, you know beforehand why your code isn't compiling. +set(CLANGUML_INC source_file.hpp) + +# Form the full path to the source files... +PREPEND(CLANGUML_INC) + +# ... and pass the variable to the parent scope. +set(CLANGUML_INC ${CLANGUML_INC} PARENT_SCOPE) diff --git a/include/source_file.hpp b/include/source_file.hpp new file mode 100644 index 00000000..2acce8b7 --- /dev/null +++ b/include/source_file.hpp @@ -0,0 +1,7 @@ + +#include + +template +T add(T a, T b){ + return a + b; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..6fa1f5d3 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,6 @@ + +set(SOURCE_FILES " + config/config.cc +") + + diff --git a/src/config/config.cc b/src/config/config.cc new file mode 100644 index 00000000..c91d4e69 --- /dev/null +++ b/src/config/config.cc @@ -0,0 +1,14 @@ +#include "config.h" + +namespace clanguml { +namespace config { + +config load(const std::string &config_file) +{ + YAML::Node doc = YAML::LoadFile(config_file); + + return doc.as(); +} +} +} + diff --git a/src/config/config.h b/src/config/config.h new file mode 100644 index 00000000..2576103b --- /dev/null +++ b/src/config/config.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace clanguml { +namespace config { + +struct diagram { + virtual ~diagram() = default; + + std::string name; + std::vector glob; + std::vector puml; + std::string using_namespace; +}; + +enum class class_scopes { public_, protected_, private_ }; + +struct class_diagram : public diagram { + virtual ~class_diagram() = default; + + std::vector classes; + std::vector methods; + std::vector members; + + bool has_class(std::string clazz) + { + spdlog::debug("CHECKING IF {} IS WHITE LISTED", clazz); + for (const auto &c : classes) { + std::string prefix{}; + if (!using_namespace.empty()) { + prefix = using_namespace + "::"; + } + if (prefix + c == clazz) + return true; + } + + return false; + } +}; + +struct config { + // the glob list is additive and relative to the current + // directory + std::vector glob; + std::string compilation_database_dir{"."}; + std::map> diagrams; +}; + +config load(const std::string &config_file); +} +} + +namespace YAML { +using clanguml::config::class_diagram; +using clanguml::config::config; +// +// class_diagram Yaml decoder +// +template <> struct convert { + static bool decode(const Node &node, class_diagram &rhs) + { + rhs.using_namespace = node["using_namespace"].as(); + rhs.glob = node["glob"].as>(); + rhs.puml = node["puml"].as>(); + rhs.classes = node["classes"].as>(); + return true; + } +}; + +// +// config Yaml decoder +// +template <> struct convert { + static bool decode(const Node &node, config &rhs) + { + rhs.glob = node["glob"].as>(); + if (node["compilation_database_dir"]) + rhs.compilation_database_dir = + node["compilation_database_dir"].as(); + + auto diagrams = node["diagrams"]; + + assert(diagrams.Type() == NodeType::Map); + + for (const auto &d : diagrams) { + const auto diagram_type = d.second["type"].as(); + if (diagram_type == "class") { + rhs.diagrams[d.first.as()] = + std::make_unique( + d.second.as()); + } + else { + spdlog::warn( + "Diagrams of type {} are not supported at the moment... ", + diagram_type); + } + } + + return true; + } +}; +} + diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 00000000..826eace8 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,108 @@ +#include "config/config.h" +#include "puml/class_diagram_generator.h" +#include "uml/class_diagram_model.h" +#include "uml/class_diagram_visitor.h" +#include "uml/compilation_database.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using clanguml::config::config; +using clanguml::cx::compilation_database; + +int main(int argc, const char *argv[]) +{ + spdlog::set_pattern("[%l] %v"); + + CLI::App app{"Clang-based PlantUML generator from C++ sources"}; + + std::string config_path{".clanguml"}; + std::string compilation_database_dir{'.'}; + bool verbose{false}; + + app.add_option( + "-c,--config", config_path, "Location of configuration file"); + app.add_option("-d,--compile-database", compilation_database_dir, + "Location of configuration file"); + app.add_flag("-v,--verbose", verbose, "Verbose logging"); + + CLI11_PARSE(app, argc, argv); + + if (verbose) + spdlog::set_level(spdlog::level::debug); + + spdlog::info("Loading clang-uml config from {}", config_path); + + auto config = clanguml::config::load(config_path); + + spdlog::info("Loading compilation database from {} directory", + config.compilation_database_dir); + + auto db = + compilation_database::from_directory(config.compilation_database_dir); + + for (const auto &[name, diagram] : config.diagrams) { + spdlog::info("Generating diagram {}.puml", name); + clanguml::model::class_diagram::diagram d; + d.name = name; + + // Get all translation units matching the glob from diagram + // configuration + std::vector translation_units{}; + for (const auto &g : diagram->glob) { + spdlog::debug("Processing glob: {}", g); + const auto matches = glob::glob(g); + std::copy(matches.begin(), matches.end(), + std::back_inserter(translation_units)); + } + + // Process all matching translation units + for (const auto &tu_path : translation_units) { + spdlog::debug("Processing translation unit: {}", + std::filesystem::canonical(tu_path).c_str()); + + auto tu = db.parse_translation_unit(tu_path); + + auto cursor = clang_getTranslationUnitCursor(tu); + + if (clang_Cursor_isNull(cursor)) { + spdlog::debug("CURSOR IS NULL"); + } + + spdlog::debug("Cursor kind: {}", + clang_getCString(clang_getCursorKindSpelling(cursor.kind))); + spdlog::debug("Cursor name: {}", + clang_getCString(clang_getCursorDisplayName(cursor))); + + clanguml::visitor::class_diagram::tu_context ctx(d); + auto res = clang_visitChildren(cursor, + clanguml::visitor::class_diagram::translation_unit_visitor, + &ctx); + + spdlog::debug("Processing result: {}", res); + } + + std::filesystem::path path{"puml/" + d.name + ".puml"}; + std::ofstream ofs; + ofs.open(path, std::ofstream::out | std::ofstream::trunc); + + auto generator = clanguml::generators::class_diagram::puml::generator( + dynamic_cast(*diagram), d); + + ofs << generator; + + ofs.close(); + } + return 0; +} diff --git a/src/uml/class_diagram_model.h b/src/uml/class_diagram_model.h new file mode 100644 index 00000000..479ecbbd --- /dev/null +++ b/src/uml/class_diagram_model.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace clanguml { +namespace model { +namespace class_diagram { + +enum class scope_t { kPublic, kProtected, kPrivate }; + +enum class relationship_t { + kExtension, + kComposition, + kAggregation, + kContainment, + kOwnership, + kAssociation +}; + +struct element { + std::string name; + std::vector namespace_; +}; + +struct class_element { + scope_t scope; + std::string name; + std::string type; +}; + +struct class_member : public class_element { + bool is_relationship{false}; +}; + +struct method_argument { + std::string type; + std::string name; +}; + +struct class_method : public class_element { + std::vector arguments; +}; + +struct class_parent { + enum class access_t { kPublic, kProtected, kPrivate }; + std::string name; + bool is_virtual{false}; + access_t access; +}; + +struct class_relationship { + relationship_t type{relationship_t::kAssociation}; + std::string destination; + std::string cardinality_source; + std::string cardinality_destination; + std::string label; +}; + +struct class_ : public element { + bool is_struct{false}; + bool is_template{false}; + std::vector members; + std::vector methods; + std::vector bases; + std::vector inner_classes; + + std::vector relationships; +}; + +struct enum_ : public element { + std::vector constants; +}; + +struct diagram { + std::string name; + std::vector classes; + std::vector enums; +}; +} +} +} diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h new file mode 100644 index 00000000..873972ce --- /dev/null +++ b/src/uml/class_diagram_visitor.h @@ -0,0 +1,353 @@ +#pragma once + +#include "class_diagram_model.h" + +#include +#include +#include + +#include +#include +#include + +namespace clanguml { +namespace visitor { +namespace class_diagram { + +using clanguml::model::class_diagram::class_; +using clanguml::model::class_diagram::class_member; +using clanguml::model::class_diagram::class_method; +using clanguml::model::class_diagram::class_parent; +using clanguml::model::class_diagram::class_relationship; +using clanguml::model::class_diagram::diagram; +using clanguml::model::class_diagram::enum_; +using clanguml::model::class_diagram::relationship_t; +using clanguml::model::class_diagram::scope_t; + +template struct element_visitor_context { + element_visitor_context(T &e) + : element(e) + { + } + CXCursorKind current_cursor_kind; + std::vector namespace_; + + T &element; +}; + +struct class_visitor_context : element_visitor_context { + class_visitor_context(class_ &c) + : element_visitor_context(c) + { + } + scope_t scope; +}; + +struct tu_context { + tu_context(diagram &d) + : diagram(d) + { + } + + std::vector namespace_; + diagram &diagram; +}; + +enum CXChildVisitResult visit_if_cursor_valid( + CXCursor cursor, std::function f) +{ + enum CXChildVisitResult ret = CXChildVisit_Break; + CXString cursorName = clang_getCursorSpelling(cursor); + if (clang_isCursorDefinition(cursor)) { + if (*clang_getCString(cursorName)) { + f(cursor); + ret = CXChildVisit_Continue; + } + else { + ret = CXChildVisit_Recurse; + } + } + else { + ret = CXChildVisit_Continue; + } + return ret; +} + +static enum CXChildVisitResult enum_visitor( + CXCursor cursor, CXCursor parent, CXClientData client_data) +{ + auto e = (struct enum_ *)client_data; + + CXString cursorName = clang_getCursorSpelling(cursor); + std::string cursor_name_str = clang_getCString(cursorName); + + spdlog::info( + "Visiting enum {}: {} - {}:{}", e->name, cursor_name_str, cursor.kind); + + enum CXChildVisitResult ret = CXChildVisit_Break; + switch (cursor.kind) { + case CXCursor_EnumConstantDecl: + visit_if_cursor_valid( + cursor, [e, cursor_name_str](CXCursor cursor) { + spdlog::info("Adding enum constant {}::{}", e->name, + cursor_name_str); + + e->constants.emplace_back(cursor_name_str); + }); + + ret = CXChildVisit_Continue; + break; + default: + ret = CXChildVisit_Continue; + break; + } + + return ret; +} + +static enum CXChildVisitResult class_visitor( + CXCursor cursor, CXCursor parent, CXClientData client_data) +{ + auto c = (struct class_ *)client_data; + + CXString cursorName = clang_getCursorSpelling(cursor); + std::string cursor_name_str = clang_getCString(cursorName); + + spdlog::info("Visiting {}: {} - {}:{}", c->is_struct ? "struct" : "class", + c->name, cursor_name_str, cursor.kind); + + enum CXChildVisitResult ret = CXChildVisit_Break; + switch (cursor.kind) { + case CXCursor_CXXMethod: + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_FunctionTemplate: { + visit_if_cursor_valid( + cursor, [c, cursor_name_str](CXCursor cursor) { + class_method m; + m.name = cursor_name_str; + m.type = clang_getCString(clang_getTypeSpelling( + clang_getResultType(clang_getCursorType(cursor)))); + + spdlog::info("Adding method {} {}::{}()", m.type, c->name, + cursor_name_str); + + c->methods.emplace_back(std::move(m)); + }); + ret = CXChildVisit_Continue; + break; + } + case CXCursor_FieldDecl: { + visit_if_cursor_valid(cursor, [c, cursor_name_str](CXCursor cursor) { + auto t = clang_getCursorType(cursor); + class_member m; + m.name = cursor_name_str; + m.type = clang_getCString(clang_getTypeSpelling(t)); + + spdlog::info("Adding member {} {}::{}", m.type, c->name, + cursor_name_str); + + relationship_t relationship_type = relationship_t::kOwnership; + + // Parse the field declaration to determine the relationship + // type + if (!clang_isPODType(t)) { + while (true) { + if (t.kind == CXType_Pointer) { + relationship_type = relationship_t::kAssociation; + t = clang_getPointeeType(t); + continue; + } + else if (t.kind == CXType_LValueReference) { + relationship_type = relationship_t::kAggregation; + t = clang_getPointeeType(t); + continue; + } + else if (t.kind == CXType_RValueReference) { + relationship_type = relationship_t::kAssociation; + t = clang_getPointeeType(t); + continue; + } + /*else if(t.kind == CXType_Elaborated) { + t = clang_Type_getNamedType(t); + continue; + }*/ + else /*if (t.kind == CXType_Record) */ { + spdlog::error("UNKNOWN CXTYPE: {}", t.kind); + class_relationship r; + auto template_argument_count = + clang_Type_getNumTemplateArguments(t); + std::string name{ + clang_getCString(clang_getTypeSpelling(t))}; + + if (template_argument_count > 0) { + std::vector template_arguments; + for (int i = 0; i < template_argument_count; + i++) { + auto tt = + clang_Type_getTemplateArgumentAsType( + t, i); + template_arguments.push_back(tt); + } + + if (name.rfind("vector") == 0 || + name.rfind("std::vector") == 0) { + r.type = relationship_t::kAggregation; + r.destination = + clang_getCString(clang_getTypeSpelling( + template_arguments[0])); + } + if (name.rfind("map") == 0 || + name.rfind("std::map") == 0) { + r.type = relationship_t::kAggregation; + r.destination = + clang_getCString(clang_getTypeSpelling( + template_arguments[1])); + } + r.label = m.name; + c->relationships.emplace_back(std::move(r)); + } + else { + r.destination = name; + r.type = relationship_type; + r.label = m.name; + c->relationships.emplace_back(std::move(r)); + } + + spdlog::debug( + "Adding relationship to: {}", r.destination); + } + // else { + // spdlog::error("UNKNOWN CXTYPE: {}", t.kind); + //} + break; + } + } + + c->members.emplace_back(std::move(m)); + }); + ret = CXChildVisit_Continue; + break; + } + case CXCursor_CXXBaseSpecifier: { + CXCursor ref_cursor = clang_getCursorReferenced(cursor); + CXString display_name = clang_getCursorDisplayName(ref_cursor); + + auto base_access = clang_getCXXAccessSpecifier(cursor); + + spdlog::error("Found base specifier: {} - {}", cursor_name_str, + clang_getCString(display_name)); + + class_parent cp; + cp.name = clang_getCString(display_name); + cp.is_virtual = false; + switch (base_access) { + case CX_CXXAccessSpecifier::CX_CXXPrivate: + cp.access = class_parent::access_t::kPrivate; + break; + case CX_CXXAccessSpecifier::CX_CXXPublic: + cp.access = class_parent::access_t::kPublic; + break; + case CX_CXXAccessSpecifier::CX_CXXProtected: + cp.access = class_parent::access_t::kProtected; + break; + default: + cp.access = class_parent::access_t::kPublic; + } + + c->bases.emplace_back(std::move(cp)); + + ret = CXChildVisit_Continue; + break; + } + default: + ret = CXChildVisit_Continue; + break; + } + + return ret; +}; + +static enum CXChildVisitResult translation_unit_visitor( + CXCursor cursor, CXCursor parent, CXClientData client_data) +{ + if (clang_Location_isFromMainFile(clang_getCursorLocation(cursor)) == 0) { + return CXChildVisit_Continue; + } + + struct tu_context *ctx = (struct tu_context *)client_data; + + enum CXChildVisitResult ret = CXChildVisit_Break; + + CXString cursorName = clang_getCursorSpelling(cursor); + std::string cursor_name_str = clang_getCString(cursorName); + + spdlog::debug("Visiting cursor: {}", cursor_name_str); + + bool is_struct{false}; + auto scope{scope_t::kPrivate}; + switch (cursor.kind) { + case CXCursor_StructDecl: + spdlog::debug("Found struct declaration: {}", cursor_name_str); + + is_struct = true; + + [[fallthrough]]; + case CXCursor_ClassTemplate: + [[fallthrough]]; + case CXCursor_ClassDecl: { + spdlog::debug("Found class or class template declaration: {}", + cursor_name_str); + + scope = scope_t::kPublic; + + visit_if_cursor_valid( + cursor, [ctx, is_struct, cursor_name_str](CXCursor cursor) { + class_ c{}; + c.is_struct = is_struct; + c.name = cursor_name_str; + c.namespace_ = ctx->namespace_; + + clang_visitChildren(cursor, class_visitor, &c); + + ctx->diagram.classes.emplace_back(std::move(c)); + }); + + ret = CXChildVisit_Continue; + break; + } + case CXCursor_EnumDecl: { + spdlog::debug("Found enum declaration: {}", cursor_name_str); + + visit_if_cursor_valid( + cursor, [ctx, is_struct, cursor_name_str](CXCursor cursor) { + enum_ e{}; + e.name = cursor_name_str; + e.namespace_ = ctx->namespace_; + + clang_visitChildren(cursor, enum_visitor, &e); + + ctx->diagram.enums.emplace_back(std::move(e)); + }); + ret = CXChildVisit_Continue; + + break; + } + case CXCursor_Namespace: { + spdlog::debug("Found namespace specifier: {}", cursor_name_str); + + ret = CXChildVisit_Recurse; + + break; + } + default: + spdlog::debug("Found cursor: {}", cursor_name_str); + + ret = CXChildVisit_Recurse; + } + + return ret; +} +} +} +} diff --git a/src/uml/compilation_database.cc b/src/uml/compilation_database.cc new file mode 100644 index 00000000..4d988777 --- /dev/null +++ b/src/uml/compilation_database.cc @@ -0,0 +1,88 @@ +#include "compilation_database.h" + +#include +#include +#include + +namespace clanguml { +namespace cx { +compilation_database::compilation_database(CXCompilationDatabase &&d) + : m_database{std::move(d)} + , m_index{clang_createIndex(0, 1)} +{ +} + +compilation_database::~compilation_database() +{ + clang_CompilationDatabase_dispose(m_database); +} + +compilation_database compilation_database::from_directory( + const std::string &dir) +{ + CXCompilationDatabase_Error error; + auto path = std::filesystem::path{dir}; + CXCompilationDatabase cdb = + clang_CompilationDatabase_fromDirectory(path.c_str(), &error); + + if (error != CXCompilationDatabase_Error::CXCompilationDatabase_NoError) { + throw std::runtime_error(fmt::format( + "Cannot load compilation database database from: {}", dir)); + } + + return compilation_database{std::move(cdb)}; +} + +CXTranslationUnit compilation_database::parse_translation_unit( + const std::string &path) +{ + const auto p = std::filesystem::canonical(path); + + CXCompileCommands compile_commands = + clang_CompilationDatabase_getCompileCommands(m_database, p.c_str()); + + unsigned int compile_commands_count = + clang_CompileCommands_getSize(compile_commands); + + int i; + // for (i = 0; i < compile_commands_count; i++) { + CXCompileCommand compile_command = + clang_CompileCommands_getCommand(compile_commands, 0); + + auto cc_filename = clang_CompileCommand_getFilename(compile_command); + spdlog::debug( + "Processing compile command file: {}", clang_getCString(cc_filename)); + + auto num_args = clang_CompileCommand_getNumArgs(compile_command); + + char **arguments = NULL; + if (num_args) { + int j; + arguments = (char **)malloc(sizeof(char *) * num_args); + for (j = 0; j < num_args; ++j) { + CXString arg = clang_CompileCommand_getArg(compile_command, j); + spdlog::debug("Processing argument: {}", clang_getCString(arg)); + arguments[j] = strdup(clang_getCString(arg)); + clang_disposeString(arg); + } + } + + CXTranslationUnit tu = clang_parseTranslationUnit(m_index, nullptr, + (const char *const *)arguments, num_args, NULL, 0, + CXTranslationUnit_None); + + if (num_args) { + int j; + for (j = 0; j < num_args; ++j) { + free(arguments[j]); + } + free(arguments); + } + //} + + return tu; +} + +CXCompilationDatabase &compilation_database::db() { return m_database; } +} +} diff --git a/src/uml/compilation_database.h b/src/uml/compilation_database.h new file mode 100644 index 00000000..91971ebc --- /dev/null +++ b/src/uml/compilation_database.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace clanguml { +namespace cx { + +class compilation_database { +public: + compilation_database(CXCompilationDatabase &&d); + ~compilation_database(); + + CXCompilationDatabase &db(); + + CXIndex &index(); + + CXTranslationUnit parse_translation_unit( + const std::string &path); + + static compilation_database from_directory(const std::string &dir); + +private: + CXCompilationDatabase m_database; + CXIndex m_index; +}; +} +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..1940295e --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) +set(CMAKE_CXX_STANDARD 20) + +# Explicitly list the test source code and headers. The Catch header-only unit +# test framework is stored in with the test source. +set(CLANG_UML_TEST_EXAMPLE_SRC + test_example.cpp +) +set(CLANG_UML_TEST_EXAMPLE_HEADER + catch.hpp +) + +# Make an executable target that depends on the test source code we specified +# above. +add_executable(test-example ${CLANG_UML_TEST_EXAMPLE_SRC} ${CLANG_UML_TEST_EXAMPL_HEADER}) + +# Enable testing via CTest +enable_testing() + +# Add our test as runnable via CTest +add_test(NAME test-example COMMAND test-example) diff --git a/tests/catch.hpp b/tests/catch.hpp new file mode 100644 index 00000000..51618b38 --- /dev/null +++ b/tests/catch.hpp @@ -0,0 +1,17618 @@ +/* + * Catch v2.11.3 + * Generated: 2020-03-19 13:44:21.042491 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 11 +#define CATCH_VERSION_PATCH 3 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +// We have to avoid both ICC and Clang, because they try to mask themselves +// as gcc, and we want only GCC in this block +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template