diff --git a/.cs211/README.md b/.cs211/README.md new file mode 100644 index 0000000..2a2ca2e --- /dev/null +++ b/.cs211/README.md @@ -0,0 +1,17 @@ +# CS 211 project configuration + +This directory contains files for configuring this CS 211 project: + + cmake/ - build system setup + + idea/ - CLion IDE configuration + + README.md - this file + + lib/ - included libraries + + catch/ - testing framework + + ge211/ - game engine library + +You probably shouldn’t change anything in here. diff --git a/.cs211/cmake/211commands.cmake b/.cs211/cmake/211commands.cmake new file mode 100644 index 0000000..c4bfc92 --- /dev/null +++ b/.cs211/cmake/211commands.cmake @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.3) + +# Adds a program with the given name and source files, and sets the +# language to C++ 14 +# +# Usage: +# +# add_program(NAME [OPTION...] SRCFILE...) +# +# where OPTIONs include: +# +# ASAN enable address sanitizer +# UBSAN enable undefined behavior sanitizer +# CXX17 enable C++ 2017 +# +function (add_program name) + cmake_parse_arguments(pa "ASAN;UBSAN;CXX17;" "" "" ${ARGN}) + + add_executable(${name} ${pa_UNPARSED_ARGUMENTS}) + + if(pa_ASAN) + target_compile_options(${name} PRIVATE "-fsanitize=address") + target_link_options(${name} PRIVATE "-fsanitize=address") + endif(pa_ASAN) + + if(pa_UBSAN) + target_compile_options(${name} PRIVATE "-fsanitize=undefined") + target_link_options(${name} PRIVATE "-fsanitize=undefined") + endif(pa_UBSAN) + + if(pa_CXX17) + set_property(TARGET ${name} PROPERTY CXX_STANDARD 17) + else(pa_CXX17) + set_property(TARGET ${name} PROPERTY CXX_STANDARD 14) + endif(pa_CXX17) + + set_property(TARGET ${name} PROPERTY CXX_STANDARD_REQUIRED On) + set_property(TARGET ${name} PROPERTY CXX_EXTENSIONS Off) +endfunction(add_program) + +# Adds a test program with the given name and source files +# Options are the same as `add_program`, but the listed +# source files should not define `main()`. +function(add_test_program name) + add_program(${name} ${ARGN}) + target_link_libraries(${name} catch) + add_test(Test_${name} ${name}) +endfunction(add_test_program) + +# vim: ft=cmake diff --git a/.cs211/cmake/211helpers.cmake b/.cs211/cmake/211helpers.cmake new file mode 100644 index 0000000..238eb31 --- /dev/null +++ b/.cs211/cmake/211helpers.cmake @@ -0,0 +1,85 @@ +# Some helper functions. +cmake_minimum_required(VERSION 3.3) + +# find_local_package(name dir) +# +# Look for an installed package ${name}, otherwise load the vendored +# version from ${dir}. +function(find_local_package name dir) + cmake_parse_arguments(pa + "" + "VERSION" + "" + ${ARGN}) + find_package(${name} ${pa_VERSION} CONFIG QUIET) + if(${name}_FOUND) + message(STATUS "Using system ${name} library (v${${name}_VERSION})") + else() + message(STATUS "Using vendored ${name} library (${dir})") + add_subdirectory(${dir} EXCLUDE_FROM_ALL) + endif() +endfunction(find_local_package) + +macro(default_to var val) + if(NOT ${var}) + set(${var} "${val}") + endif() +endmacro(default_to) + +# glob_dirs(dest_list dir...) +# +# Sets dest_list to the names of all the files in all the given +# directories. +function(glob_dirs dest_list) + set(accum) + + foreach(dir ${ARGN}) + file(GLOB results "${dir}/*") + list(APPEND accum ${results}) + endforeach(dir) + + set(${dest_list} "${accum}" PARENT_SCOPE) +endfunction(glob_dirs) + +# find_file_nc(dest_var filename dir...) +# +# Like find_file but doesn't cache the result and unsets +# ${dest_var} first. +function(find_file_nc dest_var filename) + unset(${dest_var} PARENT_SCOPE) + foreach(dir ${ARGN}) + if(EXISTS "${dir}/${filename}") + set(${dest_var} "${dir}/${filename}" PARENT_SCOPE) + return() + endif() + endforeach() +endfunction(find_file_nc) + +function(find_all dest_list) + cmake_parse_arguments(pa + "OPTIONAL;VERBOSE" + "AS;CALLED" + "FILES;IN" + ${ARGN}) + default_to(pa_AS "find_all") + default_to(pa_CALLED "File") + default_to(pa_IN "${GE211_RESOURCE_PATH}") + + set(accum) + + foreach(arg ${pa_UNPARSED_ARGUMENTS} ${pa_FILES}) + find_file_nc(arg_file "${arg}" ${pa_IN}) + if(arg_file) + list(APPEND accum "${arg_file}") + elseif(NOT pa_OPTIONAL) + string(JOIN "\n - " tried ${pa_IN}) + message(SEND_ERROR "${pa_AS}:" + " ${pa_CALLED} ‘${arg}’ not found. Searched in:" + "\n - ${tried}") + endif() + endforeach() + + set(${dest_list} "${accum}" PARENT_SCOPE) +endfunction(find_all) + +# vim: ft=cmake diff --git a/.cs211/cmake/211installer.cmake b/.cs211/cmake/211installer.cmake new file mode 100644 index 0000000..7615c0c --- /dev/null +++ b/.cs211/cmake/211installer.cmake @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.3) + +function(add_installer name) + cmake_parse_arguments(pa "NO_RESOURCES" "TARGET" "RESOURCES" ${ARGN}) + default_to(pa_TARGET "${name}") + + set(search_path + "${CMAKE_CURRENT_SOURCE_DIR}/Resources" + ${GE211_RESOURCE_PATH}) + + if(pa_NO_RESOURCES) + set(resource_files) + elseif(pa_RESOURCES) + find_all(resource_files VERBOSE + FILES ${pa_RESOURCES} + CALLED Resource + IN ${search_path} + AS add_installer) + else() + glob_dirs(resource_files ${search_path}) + endif() + + ge211_installer_name("${name}") + ge211_installer_add("${pa_TARGET}" ${resource_files}) +endfunction(add_installer) + +# vim: ft=cmake diff --git a/.cs211/cmake/CMakeLists.txt b/.cs211/cmake/CMakeLists.txt new file mode 100644 index 0000000..eed8906 --- /dev/null +++ b/.cs211/cmake/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.3) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/.cs211/cmake") +include(211helpers) +include(211commands) + +enable_testing() + +find_local_package(Catch2 .cs211/lib/catch VERSION 2020.1) +find_local_package(Ge211 .cs211/lib/ge211 VERSION 2020.5) + +include_directories(src) + +# vim: ft=cmake diff --git a/.cs211/idea/codeStyles/Project.xml b/.cs211/idea/codeStyles/Project.xml new file mode 100644 index 0000000..ab160ac --- /dev/null +++ b/.cs211/idea/codeStyles/Project.xml @@ -0,0 +1,98 @@ + + + + \ No newline at end of file diff --git a/.cs211/idea/codeStyles/codeStyleConfig.xml b/.cs211/idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.cs211/idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.cs211/idea/inspectionProfiles/CS_211.xml b/.cs211/idea/inspectionProfiles/CS_211.xml new file mode 100644 index 0000000..5396b92 --- /dev/null +++ b/.cs211/idea/inspectionProfiles/CS_211.xml @@ -0,0 +1,31 @@ + + + + \ No newline at end of file diff --git a/.cs211/idea/inspectionProfiles/Project_Default.xml b/.cs211/idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..c084d7e --- /dev/null +++ b/.cs211/idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,21 @@ + + + + diff --git a/.cs211/idea/inspectionProfiles/profiles_settings.xml b/.cs211/idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..c3f3e01 --- /dev/null +++ b/.cs211/idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.cs211/idea/misc.xml b/.cs211/idea/misc.xml new file mode 100644 index 0000000..80aaaa0 --- /dev/null +++ b/.cs211/idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.cs211/lib/catch/.gitignore b/.cs211/lib/catch/.gitignore new file mode 100644 index 0000000..e28c4f5 --- /dev/null +++ b/.cs211/lib/catch/.gitignore @@ -0,0 +1,2 @@ +build/ +cmake-build-*/ diff --git a/.cs211/lib/catch/CMakeLists.txt b/.cs211/lib/catch/CMakeLists.txt new file mode 100644 index 0000000..7074753 --- /dev/null +++ b/.cs211/lib/catch/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 3.3) +project(Catch2 + VERSION 2020.1.1 + DESCRIPTION "A reusable main for Catch2" + LANGUAGES CXX) + +### +### MAIN LIBRARY SETUP +### + +add_library(catch + include/catch.hxx + src/catch-main.cxx) + +set_target_properties(catch PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED On + CXX_EXTENSIONS Off + VERSION ${PROJECT_VERSION}) + +target_include_directories(catch SYSTEM INTERFACE + $ + $) +target_include_directories(catch PRIVATE + include) + +### +### LIBRARY INSTALLATION +### + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +set(config_version_cmake + ${CMAKE_CURRENT_BINARY_DIR}/Catch2/Catch2ConfigVersion.cmake) +set(config_install_dir + ${CMAKE_INSTALL_DATADIR}/cmake/Catch2) + +install(TARGETS catch EXPORT Catch2Config + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(EXPORT Catch2Config + DESTINATION ${config_install_dir}) +install(FILES ${config_version_cmake} + DESTINATION ${config_install_dir}) + +export(TARGETS catch FILE Catch2Config.cmake) +write_basic_package_version_file(${config_version_cmake} + VERSION ${CMAKE_PROJECT_VERSION} + COMPATIBILITY SameMinorVersion) diff --git a/.cs211/lib/catch/include/catch.hxx b/.cs211/lib/catch/include/catch.hxx new file mode 100644 index 0000000..bdc2f74 --- /dev/null +++ b/.cs211/lib/catch/include/catch.hxx @@ -0,0 +1,13359 @@ +/* + * Catch v2.3.0 + * Generated: 2018-07-23 10:09:14.936841 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2018 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 3 +#define CATCH_VERSION_PATCH 0 + +#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__ + // GCC likes to warn on REQUIREs, and we cannot suppress them + // locally because g++'s support for _Pragma is lacking in older, + // still supported, versions +# pragma GCC diagnostic ignored "-Wparentheses" +# 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? +// **************** +// 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 +# define CATCH_CPP14_OR_GREATER +# endif + +# if __cplusplus >= 201703L +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#ifdef __clang__ + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#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 +#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 + +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +# 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 + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// + +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// + +// 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 + +#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_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_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_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#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 + +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( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; + + bool empty() const noexcept; + 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 ); + + // 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_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + using ITestCasePtr = std::shared_ptr; + + 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 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 + +namespace Catch { + + class StringData; + + /// 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. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + public: + using size_type = std::size_t; + + private: + friend struct StringRefTestAccess; + + char const* m_start; + size_type m_size; + + char* m_data = nullptr; + + void takeOwnership(); + + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + 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() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } + + operator std::string() const; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char; + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + // Returns the current start pointer. + // Note that the pointer can change when if the StringRef is a substring + auto currentData() const noexcept -> char const*; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + +} // namespace Catch + +// end catch_stringref.h +namespace Catch { + +template +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; + +template +auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsMethod( testAsMethod ); +} + +struct NameAndTags { + NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept; + StringRef name; + StringRef tags; +}; + +struct AutoReg : NonCopyable { + AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; + ~AutoReg(); +}; + +} // end namespace Catch + +#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 + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() + +#endif + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_test_registry.h +// start catch_capture.hpp + +// start catch_assertionhandler.h + +// start catch_assertioninfo.h + +// start catch_result_type.h + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +// end catch_result_type.h +namespace Catch { + + struct AssertionInfo + { + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; + }; + +} // end namespace Catch + +// end catch_assertioninfo.h +// start catch_decomposer.h + +// start catch_tostring.h + +#include +#include +#include +#include +// start catch_stream.h + +#include +#include +#include + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + + class StringRef; + + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; + }; + + auto makeStream( StringRef const &filename ) -> IStream const*; + + class ReusableStringStream { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + auto str() const -> std::string; + + template + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + auto get() -> std::ostream& { return *m_oss; } + + static void cleanup(); + }; +} + +// end catch_stream.h + +#ifdef __OBJC__ +// start catch_objc_arc.hpp + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +// end catch_objc_arc.hpp +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +// 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 { + // Bring in operator<< from global namespace into Catch namespace + using ::operator<<; + + namespace Detail { + + extern const std::string unprintableString; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype(std::declval() << std::declval(), std::true_type()); + + template + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; + + template + std::string convertUnknownEnumToString( E e ); + + template + typename std::enable_if< + !std::is_enum::value && !std::is_base_of::value, + std::string>::type convertUnstreamable( T const& ) { + return Detail::unprintableString; + } + template + typename std::enable_if< + !std::is_enum::value && std::is_base_of::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); + } + + template + typename std::enable_if< + std::is_enum::value + , std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); + } +#endif + + } // namespace Detail + + // If we decide for C++14, change these to enable_if_ts + template + struct StringMaker { + template + static + typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static + typename std::enable_if::value, std::string>::type + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template + std::string stringify(const T& e) { + return ::Catch::StringMaker::type>::type>::convert(e); + } + + template + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast::type>(e)); + } + +#if defined(_MANAGED) + template + std::string stringify( T^ e ) { + return ::Catch::StringMaker::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker { + static std::string convert(const std::string& str); + }; +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker { + static std::string convert(const std::wstring& wstr); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(char const * str); + }; + template<> + struct StringMaker { + static std::string convert(char * str); + }; + +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker { + static std::string convert(wchar_t * str); + }; +#endif + + // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, + // while keeping string semantics? + template + struct StringMaker { + static std::string convert(char const* str) { + return ::Catch::Detail::stringify(std::string{ str }); + } + }; + template + struct StringMaker { + static std::string convert(signed char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + } + }; + template + struct StringMaker { + static std::string convert(unsigned char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + } + }; + + template<> + struct StringMaker { + static std::string convert(int value); + }; + template<> + struct StringMaker { + static std::string convert(long value); + }; + template<> + struct StringMaker { + static std::string convert(long long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker { + static std::string convert(bool b); + }; + + template<> + struct StringMaker { + static std::string convert(char c); + }; + template<> + struct StringMaker { + static std::string convert(signed char c); + }; + template<> + struct StringMaker { + static std::string convert(unsigned char c); + }; + + template<> + struct StringMaker { + static std::string convert(std::nullptr_t); + }; + + template<> + struct StringMaker { + static std::string convert(float value); + }; + template<> + struct StringMaker { + static std::string convert(double value); + }; + + template + struct StringMaker { + template + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template + struct StringMaker { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template + struct StringMaker { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template + std::string rangeToString(InputIterator first, InputIterator last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +#ifdef __OBJC__ + template<> + struct StringMaker { + static std::string convert(NSString * nsstring) { + if (!nsstring) + return "nil"; + return std::string("@") + [nsstring UTF8String]; + } + }; + template<> + struct StringMaker { + static std::string convert(NSObject* nsObject) { + return ::Catch::Detail::stringify([nsObject description]); + } + + }; + namespace Detail { + inline std::string stringify( NSString* nsstring ) { + return StringMaker::convert( nsstring ); + } + + } // namespace Detail +#endif // __OBJC__ + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::pair& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get(tuple)); + TupleElementPrinter::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + template + struct StringMaker> { + static std::string convert(const std::tuple& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +namespace Catch { + struct not_this_one {}; // Tag type for detecting which begin/ end are being selected + + // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace + using std::begin; + using std::end; + + not_this_one begin( ... ); + not_this_one end( ... ); + + template + struct is_range { + static const bool value = + !std::is_same())), not_this_one>::value && + !std::is_same())), not_this_one>::value; + }; + +#if defined(_MANAGED) // Managed types are never ranges + template + struct is_range { + static const bool value = false; + }; +#endif + + template + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector specially + template + std::string rangeToString( std::vector const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>::type> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template + struct StringMaker { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + +} // namespace Catch + +// Separate std::chrono::duration specialization +#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#include +#include +#include + +namespace Catch { + +template +struct ratio_string { + static std::string symbol(); +}; + +template +std::string ratio_string::symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); +} +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; +template <> +struct ratio_string { + static std::string symbol(); +}; + + //////////// + // std::chrono::duration specializations + template + struct StringMaker> { + static std::string convert(std::chrono::duration const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point specialization + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_tostring.h +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#endif + +namespace Catch { + + struct ITransientExpression { + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; + + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + // We don't actually need a virtual destructor, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); + + bool m_isBinaryExpression; + bool m_result; + + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + }; + + template + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, lhs ? true : false }, + m_lhs( lhs ) + {} + }; + + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast(lhs == rhs); } + template + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } + template + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + template + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } + + template + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast(lhs != rhs); } + template + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } + template + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + template + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } + + template + class ExprLhs { + LhsT m_lhs; + public: + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + + template + auto operator == ( RhsT const& rhs ) -> BinaryExpr const { + return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; + } + auto operator == ( bool rhs ) -> BinaryExpr const { + return { m_lhs == rhs, m_lhs, "==", rhs }; + } + + template + auto operator != ( RhsT const& rhs ) -> BinaryExpr const { + return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; + } + auto operator != ( bool rhs ) -> BinaryExpr const { + return { m_lhs != rhs, m_lhs, "!=", rhs }; + } + + template + auto operator > ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs > rhs), m_lhs, ">", rhs }; + } + template + auto operator < ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs < rhs), m_lhs, "<", rhs }; + } + template + auto operator >= ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs >= rhs), m_lhs, ">=", rhs }; + } + template + auto operator <= ( RhsT const& rhs ) -> BinaryExpr const { + return { static_cast(m_lhs <= rhs), m_lhs, "<=", rhs }; + } + + auto makeUnaryExpr() const -> UnaryExpr { + return UnaryExpr{ m_lhs }; + } + }; + + void handleExpression( ITransientExpression const& expr ); + + template + void handleExpression( ExprLhs const& expr ) { + handleExpression( expr.makeUnaryExpr() ); + } + + struct Decomposer { + template + auto operator <= ( T const& lhs ) -> ExprLhs { + return ExprLhs{ lhs }; + } + + auto operator <=( bool value ) -> ExprLhs { + return ExprLhs{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_decomposer.h +// start catch_interfaces_capture.h + +#include + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct Counts; + struct BenchmarkInfo; + struct BenchmarkStats; + struct AssertionReaction; + + struct ITransientExpression; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +// end catch_interfaces_capture.h +namespace Catch { + + struct TestFailureException{}; + struct AssertionResultData; + struct IResultCapture; + class RunContext; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ); + LazyExpression( LazyExpression const& other ); + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const; + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + template + void handleExpr( ExprLhs const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef const& message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); + +} // namespace Catch + +// end catch_assertionhandler.h +// start catch_message.h + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const; + bool operator < ( MessageInfo const& other ) const; + private: + static unsigned int globalCount; + }; + + struct MessageStream { + + template + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ); + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder const& builder ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// end catch_message.h +#if !defined(CATCH_CONFIG_DISABLE) + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, false && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(expr); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_capture.hpp +// start catch_section.h + +// start catch_section_info.h + +// start catch_totals.h + +#include + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::size_t total() const; + bool allPassed() const; + bool allOk() const; + + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + int error = 0; + Counts assertions; + Counts testCases; + }; +} + +// end catch_totals.h +#include + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ); + + // Deprecated + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& ) : SectionInfo( _lineInfo, _name ) {} + + std::string name; + std::string description; // !Deprecated: this will always be empty + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// end catch_section_info.h +// start catch_timer.h + +#include + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t; + auto getEstimatedClockResolution() -> uint64_t; + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +// end catch_timer.h +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +// end catch_section.h +// start catch_benchmark.h + +#include +#include + +namespace Catch { + + class BenchmarkLooper { + + std::string m_name; + std::size_t m_count = 0; + std::size_t m_iterationsToRun = 1; + uint64_t m_resolution; + Timer m_timer; + + static auto getResolution() -> uint64_t; + public: + // Keep most of this inline as it's on the code path that is being timed + BenchmarkLooper( StringRef name ) + : m_name( name ), + m_resolution( getResolution() ) + { + reportStart(); + m_timer.start(); + } + + explicit operator bool() { + if( m_count < m_iterationsToRun ) + return true; + return needsMoreIterations(); + } + + void increment() { + ++m_count; + } + + void reportStart(); + auto needsMoreIterations() -> bool; + }; + +} // end namespace Catch + +#define BENCHMARK( name ) \ + for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) + +// end catch_benchmark.h +// start catch_interfaces_exception.h + +// start catch_interfaces_registry_hub.h + +#include +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + class StartupExceptionRegistry; + + using IReporterFactoryPtr = std::shared_ptr; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; + virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif + +#include +#include +#include + +namespace Catch { + using exceptionTranslateFunction = std::string(*)(); + + struct IExceptionTranslator; + using ExceptionTranslators = std::vector>; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { + try { + if( it == itEnd ) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// end catch_interfaces_exception.h +// start catch_approx.h + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template ::value>::type> + Approx operator()( T const& value ) { + Approx approx( static_cast(value) ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + template ::value>::type> + explicit Approx( T const& value ): Approx(static_cast(value)) + {} + + template ::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template ::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>::type> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template ::value>::type> + Approx& epsilon( T const& newEpsilon ) { + double epsilonAsDouble = static_cast(newEpsilon); + if( epsilonAsDouble < 0 || epsilonAsDouble > 1.0 ) { + throw std::domain_error + ( "Invalid Approx::epsilon: " + + Catch::Detail::stringify( epsilonAsDouble ) + + ", Approx::epsilon has to be between 0 and 1" ); + } + m_epsilon = epsilonAsDouble; + return *this; + } + + template ::value>::type> + Approx& margin( T const& newMargin ) { + double marginAsDouble = static_cast(newMargin); + if( marginAsDouble < 0 ) { + throw std::domain_error + ( "Invalid Approx::margin: " + + Catch::Detail::stringify( marginAsDouble ) + + ", Approx::Margin has to be non-negative." ); + + } + m_margin = marginAsDouble; + return *this; + } + + template ::value>::type> + Approx& scale( T const& newScale ) { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val); + Detail::Approx operator "" _a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker { + static std::string convert(Catch::Detail::Approx const& value); +}; + +} // end namespace Catch + +// end catch_approx.h +// start catch_string_manip.h + +#include +#include + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; +} + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include +#include + +namespace Catch { +namespace Matchers { + namespace Impl { + + template struct MatchAllOf; + template struct MatchAnyOf; + template struct MatchNotOf; + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + MatcherUntypedBase ( MatcherUntypedBase const& ) = default; + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + + template + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + template + struct MatcherMethod { + virtual bool match( PtrT* arg ) const = 0; + }; + + template + struct MatcherBase : MatcherUntypedBase, MatcherMethod { + + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; + }; + + template + struct MatchAllOf : MatcherBase { + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAllOf& operator && ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + template + struct MatchAnyOf : MatcherBase { + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf& operator || ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + + template + struct MatchNotOf : MatcherBase { + + MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase const& m_underlyingMatcher; + }; + + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; + } + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; + } + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); + } + + } // namespace Impl + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +// end catch_matchers.h +// start catch_matchers_floating.h + +#include +#include + +namespace Catch { +namespace Matchers { + + namespace Floating { + + enum class FloatingPointKind : uint8_t; + + struct WithinAbsMatcher : MatcherBase { + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + struct WithinUlpsMatcher : MatcherBase { + WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + int m_ulps; + FloatingPointKind m_type; + }; + + } // namespace Floating + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); + Floating::WithinAbsMatcher WithinAbs(double target, double margin); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h +// start catch_matchers_generic.hpp + +#include +#include + +namespace Catch { +namespace Matchers { +namespace Generic { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); +} + +template +class PredicateMatcher : public MatcherBase { + std::function m_predicate; + std::string m_description; +public: + + PredicateMatcher(std::function const& elem, std::string const& descr) + :m_predicate(std::move(elem)), + m_description(Detail::finalizeDescription(descr)) + {} + + bool match( T const& item ) const override { + return m_predicate(item); + } + + std::string describe() const override { + return m_description; + } +}; + +} // namespace Generic + + // The following functions create the actual matcher objects. + // The user has to explicitly specify type to the function, because + // infering std::function is hard (but possible) and + // requires a lot of TMP. + template + Generic::PredicateMatcher Predicate(std::function const& predicate, std::string const& description = "") { + return Generic::PredicateMatcher(predicate, description); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_generic.hpp +// start catch_matchers_string.h + +#include + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + std::string describe() const override; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + + struct RegexMatcher : MatcherBase { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_string.h +// start catch_matchers_vector.h + +#include + +namespace Catch { +namespace Matchers { + + namespace Vector { + namespace Detail { + template + size_t count(InputIterator first, InputIterator last, T const& item) { + size_t cnt = 0; + for (; first != last; ++first) { + if (*first == item) { + ++cnt; + } + } + return cnt; + } + template + bool contains(InputIterator first, InputIterator last, T const& item) { + for (; first != last; ++first) { + if (*first == item) { + return true; + } + } + return false; + } + } + + template + struct ContainsElementMatcher : MatcherBase> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector const &v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + T const& m_comparator; + }; + + template + struct ContainsMatcher : MatcherBase> { + + ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + std::vector const& m_comparator; + }; + + template + struct EqualsMatcher : MatcherBase> { + + EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + std::vector const& m_comparator; + }; + + template + struct UnorderedEqualsMatcher : MatcherBase> { + UnorderedEqualsMatcher(std::vector const& target) : m_target(target) {} + bool match(std::vector const& vec) const override { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include inside the common path + if (m_target.size() != vec.size()) { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst != *rfirst) { + ++lfirst; ++rfirst; + } + if (lfirst == llast) { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { + return false; + } + } + + return true; + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + private: + std::vector const& m_target; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template + Vector::ContainsMatcher Contains( std::vector const& comparator ) { + return Vector::ContainsMatcher( comparator ); + } + + template + Vector::ContainsElementMatcher VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher( comparator ); + } + + template + Vector::EqualsMatcher Equals( std::vector const& comparator ) { + return Vector::EqualsMatcher( comparator ); + } + + template + Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const& target) { + return Vector::UnorderedEqualsMatcher(target); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_vector.h +namespace Catch { + + template + class MatchExpr : public ITransientExpression { + ArgT const& m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + public: + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) + : ITransientExpression{ true, matcher.match( arg ) }, + m_arg( arg ), + m_matcher( matcher ), + m_matcherString( matcherString ) + {} + + void streamReconstructedExpression( std::ostream &os ) const override { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify( m_arg ) << ' '; + if( matcherAsString == Detail::unprintableString ) + os << m_matcherString; + else + os << matcherAsString; + } + }; + + using StringMatcher = Matchers::Impl::MatcherBase; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ); + + template + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) -> MatchExpr { + return MatchExpr( arg, matcher, matcherString ); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast(__VA_ARGS__ ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +// end catch_capture_matchers.h +#endif + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// start catch_test_case_info.h + +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestInvoker; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector const& _tags, + SourceLineInfo const& _lineInfo ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::vector tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string tagsAsString() const; + + std::string name; + std::string className; + std::string description; + std::vector tags; + std::vector lcaseTags; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestInvoker* testCase, TestCaseInfo&& info ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + + private: + std::shared_ptr test; + }; + + TestCase makeTestCase( ITestInvoker* testCase, + std::string const& className, + NameAndTags const& nameAndTags, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch { + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +// end catch_interfaces_runner.h + +#ifdef __OBJC__ +// start catch_objc.hpp + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public ITestInvoker { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline std::size_t registerTestMethods() { + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList( nullptr, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + bool match( NSString* arg ) const override { + return false; + } + + NSString* CATCH_ARC_STRONG m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + std::string describe() const override { + return "equals string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + std::string describe() const override { + return "contains string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + std::string describe() const override { + return "starts with: " + Catch::Detail::stringify( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + std::string describe() const override { + return "ends with: " + Catch::Detail::stringify( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix +#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ +{ \ +return @ name; \ +} \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ +{ \ +return @ desc; \ +} \ +-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) + +#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) + +// end catch_objc.hpp +#endif + +#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// start catch_external_interfaces.h + +// start catch_reporter_bases.hpp + +// start catch_interfaces_reporter.h + +// start catch_config.hpp + +// start catch_test_spec_parser.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_test_spec.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_wildcard_pattern.h + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); + virtual ~WildcardPattern() = default; + virtual bool matches( std::string const& str ) const; + + private: + std::string adjustCase( std::string const& str ) const; + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +// end catch_wildcard_pattern.h +#include +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + using PatternPtr = std::shared_ptr; + + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ); + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ); + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( PatternPtr const& underlyingPattern ); + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + PatternPtr m_underlyingPattern; + }; + + struct Filter { + std::vector m_patterns; + + bool matches( TestCaseInfo const& testCase ) const; + }; + + public: + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h + +#include + +namespace Catch { + + struct TagAlias; + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode = None; + bool m_exclusion = false; + std::size_t m_start = std::string::npos, m_pos = 0; + std::string m_arg; + std::vector m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases = nullptr; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); + + private: + void visitChar( char c ); + void startNewMode( Mode mode, std::size_t start ); + void escape(); + std::string subString() const; + + template + void addPattern() { + std::string token = subString(); + for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + TestSpec::PatternPtr pattern = std::make_shared( token ); + if( m_exclusion ) + pattern = std::make_shared( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + + void addFilter(); + }; + TestSpec parseTestSpec( std::string const& arg ); + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec_parser.h +// start catch_interfaces_config.h + +#include +#include +#include +#include + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : NonCopyable { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutNoTests() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual int benchmarkResolutionMultiple() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + }; + + using IConfigPtr = std::shared_ptr; +} + +// end catch_interfaces_config.h +// Libstdc++ doesn't like incomplete classes for unique_ptr + +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct IStream; + + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + int benchmarkResolutionMultiple = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string outputFilename; + std::string name; + std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER + + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + virtual ~Config() = default; + + std::string const& getFilename() const; + + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; + + std::string getProcessName() const; + std::string const& getReporterName() const; + + std::vector const& getTestsOrTags() const; + std::vector const& getSectionsToRun() const override; + + virtual TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + std::ostream& stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutNoTests() const override; + ShowDurations::OrNot showDurations() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + int benchmarkResolutionMultiple() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + + private: + + IStream const* openStream(); + ConfigData m_data; + + std::unique_ptr m_stream; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; + +} // end namespace Catch + +// end catch_config.hpp +// start catch_assertionresult.h + +#include + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// end catch_assertionresult.h +// start catch_option.hpp + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( nullptr ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +// end catch_option.hpp +#include +#include +#include +#include +#include + +namespace Catch { + + struct ReporterConfig { + explicit ReporterConfig( IConfigPtr const& _fullConfig ); + + ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); + + std::ostream& stream() const; + IConfigPtr fullConfig() const; + + private: + std::ostream* m_stream; + IConfigPtr m_fullConfig; + }; + + struct ReporterPreferences { + bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; + }; + + template + struct LazyStat : Option { + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used = false; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ); + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ); + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ); + + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; + virtual ~AssertionStats(); + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; + virtual ~SectionStats(); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); + + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; + virtual ~TestCaseStats(); + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ); + TestGroupStats( GroupInfo const& _groupInfo ); + + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; + virtual ~TestGroupStats(); + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; + virtual ~TestRunStats(); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct BenchmarkInfo { + std::string name; + }; + struct BenchmarkStats { + BenchmarkInfo info; + std::size_t iterations; + uint64_t elapsedTimeInNanoseconds; + }; + + struct IStreamingReporter { + virtual ~IStreamingReporter() = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set getSupportedVerbosities() + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + // *** experimental *** + virtual void benchmarkStarting( BenchmarkInfo const& ) {} + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + // *** experimental *** + virtual void benchmarkEnded( BenchmarkStats const& ) {} + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + // Default empty implementation provided + virtual void fatalErrorEncountered( StringRef name ); + + virtual bool isMulti() const; + }; + using IStreamingReporterPtr = std::unique_ptr; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = std::shared_ptr; + + struct IReporterRegistry { + using FactoryMap = std::map; + using Listeners = std::vector; + + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + +} // end namespace Catch + +// end catch_interfaces_reporter.h +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result); + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); + + template + struct StreamingReporterBase : IStreamingReporter { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + throw std::domain_error( "Verbosity level not supported by this reporter" ); + } + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + ~StreamingReporterBase() override = default; + + void noMatchingTestCases(std::string const&) override {} + + void testRunStarting(TestRunInfo const& _testRunInfo) override { + currentTestRunInfo = _testRunInfo; + } + void testGroupStarting(GroupInfo const& _groupInfo) override { + currentGroupInfo = _groupInfo; + } + + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = _testInfo; + } + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const& /* _testRunStats */) override { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + IConfigPtr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template + struct CumulativeReporterBase : IStreamingReporter { + template + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + using ChildNodes = std::vector>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + virtual ~SectionNode() = default; + + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == (std::shared_ptr const& other) const { + return operator==(*other); + } + + SectionStats stats; + using ChildSections = std::vector>; + using Assertions = std::vector; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() (std::shared_ptr const& node) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const&) = delete; + + private: + SectionInfo const& m_other; + }; + + using TestCaseNode = Node; + using TestGroupNode = Node; + using TestRunNode = Node; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + throw std::domain_error( "Verbosity level not supported by this reporter" ); + } + ~CumulativeReporterBase() override = default; + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + void testRunStarting( TestRunInfo const& ) override {} + void testGroupStarting( GroupInfo const& ) override {} + + void testCaseStarting( TestCaseInfo const& ) override {} + + void sectionStarting( SectionInfo const& sectionInfo ) override { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + std::shared_ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = std::make_shared( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = std::make_shared( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = std::move(node); + } + + void assertionStarting(AssertionInfo const&) override {} + + bool assertionEnded(AssertionStats const& assertionStats) override { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast( assertionStats.assertionResult ) ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; + } + void sectionEnded(SectionStats const& sectionStats) override { + assert(!m_sectionStack.empty()); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& testCaseStats) override { + auto node = std::make_shared(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(m_rootSection); + m_testCases.push_back(node); + m_rootSection.reset(); + + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + void testGroupEnded(TestGroupStats const& testGroupStats) override { + auto node = std::make_shared(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const& testRunStats) override { + auto node = std::make_shared(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const&) override {} + + IConfigPtr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector>> m_sections; + std::vector> m_testCases; + std::vector> m_testGroups; + + std::vector> m_testRuns; + + std::shared_ptr m_rootSection; + std::shared_ptr m_deepestSection; + std::vector> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ); + + void assertionStarting(AssertionInfo const&) override; + bool assertionEnded(AssertionStats const&) override; + }; + +} // end namespace Catch + +// end catch_reporter_bases.hpp +// start catch_console_colour.h + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour&& other ) noexcept; + Colour& operator=( Colour&& other ) noexcept; + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved = false; + }; + + std::ostream& operator << ( std::ostream& os, Colour const& ); + +} // end namespace Catch + +// end catch_console_colour.h +// start catch_reporter_registrars.hpp + + +namespace Catch { + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr( new T( config ) ); + } + + virtual std::string getDescription() const override { + return T::getDescription(); + } + }; + + public: + + explicit ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, std::make_shared() ); + } + }; + + template + class ListenerRegistrar { + + class ListenerFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr( new T( config ) ); + } + virtual std::string getDescription() const override { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( std::make_shared() ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + }; + +} // end namespace Catch + +// end catch_reporter_compact.h +// start catch_reporter_console.h + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + // Fwd decls + struct SummaryColumn; + class TablePrinter; + + struct ConsoleReporter : StreamingReporterBase { + std::unique_ptr m_tablePrinter; + + ConsoleReporter(ReporterConfig const& config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats const& stats) override; + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testGroupEnded(TestGroupStats const& _testGroupStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + + private: + + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); + + void printTotals(Totals const& totals); + void printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row); + + void printTotalsDivider(Totals const& totals); + void printSummaryDivider(); + + private: + bool m_headerPrinted = false; + }; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + +#include + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + XmlWriter& writeComment( std::string const& text ); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +// end catch_xmlwriter.h +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter(ReporterConfig const& _config); + + ~JunitReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const& /*spec*/) override; + + void testRunStarting(TestRunInfo const& runInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + bool assertionEnded(AssertionStats const& assertionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEndedCumulative() override; + + void writeGroup(TestGroupNode const& groupNode, double suiteTime); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection(std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode); + + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +// end catch_reporter_junit.h +// start catch_reporter_xml.h + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter(ReporterConfig const& _config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const& sourceInfo); + + public: // StreamingReporterBase + + void noMatchingTestCases(std::string const& s) override; + + void testRunStarting(TestRunInfo const& testInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void sectionStarting(SectionInfo const& sectionInfo) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& assertionStats) override; + + void sectionEnded(SectionStats const& sectionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEnded(TestRunStats const& testRunStats) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +// end catch_reporter_xml.h + +// end catch_external_interfaces.h +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +#ifdef CATCH_IMPL +// start catch_impl.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// Keep these here for external reporters +// start catch_test_case_tracker.h + +#include +#include +#include + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + }; + + struct ITracker; + + using ITrackerPtr = std::shared_ptr; + + struct ITracker { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( ITrackerPtr const& child ) = 0; + virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; + + public: + + static TrackerContext& instance(); + + ITracker& startRun(); + void endRun(); + + void startCycle(); + void completeCycle(); + + bool completedCycle() const; + ITracker& currentTracker(); + void setCurrentTracker( ITracker* tracker ); + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + class TrackerHasName { + NameAndLocation m_nameAndLocation; + public: + TrackerHasName( NameAndLocation const& nameAndLocation ); + bool operator ()( ITrackerPtr const& tracker ) const; + }; + + using Children = std::vector; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState = NotStarted; + + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + NameAndLocation const& nameAndLocation() const override; + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; + + void addChild( ITrackerPtr const& child ) override; + + ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; + ITracker& parent() override; + + void openChild() override; + + bool isSectionTracker() const override; + bool isIndexTracker() const override; + + void open(); + + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; + + private: + void moveToParent(); + void moveToThis(); + }; + + class SectionTracker : public TrackerBase { + std::vector m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + bool isSectionTracker() const override; + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); + + void tryOpen(); + + void addInitialFilters( std::vector const& filters ); + void addNextFilters( std::vector const& filters ); + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index = -1; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); + + bool isIndexTracker() const override; + void close() override; + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); + + int index() const; + + void moveNext(); + }; + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +// end catch_test_case_tracker.h + +// start catch_leak_detector.h + +namespace Catch { + + struct LeakDetector { + LeakDetector(); + }; + +} +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_approx.cpp + +#include +#include + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} + +namespace Catch { +namespace Detail { + + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} + + Approx Approx::custom() { + return Approx( 0 ); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); + } + +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val) { + return Detail::Approx(val); + } + Detail::Approx operator "" _a(unsigned long long val) { + return Detail::Approx(val); + } +} // end namespace literals + +std::string StringMaker::convert(Catch::Detail::Approx const& value) { + return value.toString(); +} + +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp + +// start catch_context.h + +#include + +namespace Catch { + + struct IResultCapture; + struct IRunner; + struct IConfig; + struct IMutableContext; + + using IConfigPtr = std::shared_ptr; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr const& getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; + + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } + + void cleanUpContext(); +} + +// end catch_context.h +// start catch_debugger.h + +namespace Catch { + bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + namespace Catch { + inline void doNothing() {} + } + #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() +#endif + +// end catch_debugger.h +// start catch_run_context.h + +// start catch_fatal_condition.h + +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); + + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + +} // namespace Catch + +#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) + +#include + +namespace Catch { + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions[]; + static stack_t oldSigStack; + static char altStackMem[]; + + static void handleSignal( int sig ); + + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); + }; + +} // namespace Catch + +#else + +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} + +#endif + +// end catch_fatal_condition.h +#include + +namespace Catch { + + struct IMutableContext; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); + + ~RunContext() override; + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); + + Totals runTest(TestCase const& testCase); + + IConfigPtr config() const; + IStreamingReporter& reporter() const; + + public: // IResultCapture + + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; + + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; + + void sectionEnded( SectionEndInfo const& endInfo ) override; + void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats const& stats ) override; + + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const final; + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); + + void assertionEnded( AssertionResult const& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); + + void populateReaction( AssertionReaction& reaction ); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker; + Option m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + +} // end namespace Catch + +// end catch_run_context.h +namespace Catch { + + namespace { + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } + } + + LazyExpression::LazyExpression( bool isNegated ) + : m_isNegated( isNegated ) + {} + + LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} + + LazyExpression::operator bool() const { + return m_transientExpression != nullptr; + } + + auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { + if( lazyExpr.m_isNegated ) + os << "!"; + + if( lazyExpr ) { + if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; + } + else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + + AssertionHandler::AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if( m_reaction.shouldThrow ) + throw Catch::TestFailureException(); + } + void AssertionHandler::setCompleted() { + m_completed = true; + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } + +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp + +namespace Catch { + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!(" + m_info.capturedExpression + ")"; + else + return m_info.capturedExpression; + } + + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName[0] == 0 ) + expr = m_info.capturedExpression; + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch +// end catch_assertionresult.cpp +// start catch_benchmark.cpp + +namespace Catch { + + auto BenchmarkLooper::getResolution() -> uint64_t { + return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); + } + + void BenchmarkLooper::reportStart() { + getResultCapture().benchmarkStarting( { m_name } ); + } + auto BenchmarkLooper::needsMoreIterations() -> bool { + auto elapsed = m_timer.getElapsedNanoseconds(); + + // Exponentially increasing iterations until we're confident in our timer resolution + if( elapsed < m_resolution ) { + m_iterationsToRun *= 10; + return true; + } + + getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); + return false; + } + +} // end namespace Catch +// end catch_benchmark.cpp +// start catch_capture_matchers.cpp + +namespace Catch { + + using StringMatcher = Matchers::Impl::MatcherBase; + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr expr( exceptionMessage, matcher, matcherString ); + handler.handleExpr( expr ); + } + +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp + +// start catch_commandline.h + +// start catch_clara.h + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// start clara.hpp +// Copyright 2017 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) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.4 + + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include() && __cplusplus >= 201703L +#include +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// This work is licensed under the BSD 2-Clause license. +// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause +// +// This project is hosted at https://github.com/philsquared/textflowcpp + + +#include +#include +#include +#include + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { namespace clara { namespace TextFlow { + + inline auto isWhitespace( char c ) -> bool { + static std::string chars = " \t\n\r"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableBefore( char c ) -> bool { + static std::string chars = "[({<|"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableAfter( char c ) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find( c ) != std::string::npos; + } + + class Columns; + + class Column { + std::vector m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + + public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator( Column const& column, size_t stringIndex ) + : m_column( column ), + m_stringIndex( stringIndex ) + {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary( size_t at ) const -> bool { + assert( at > 0 ); + assert( at <= line().size() ); + + return at == line().size() || + ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || + isBreakableBefore( line()[at] ) || + isBreakableAfter( line()[at-1] ); + } + + void calcLength() { + assert( m_stringIndex < m_column.m_strings.size() ); + + m_suffix = false; + auto width = m_column.m_width-indent(); + m_end = m_pos; + while( m_end < line().size() && line()[m_end] != '\n' ) + ++m_end; + + if( m_end < m_pos + width ) { + m_len = m_end - m_pos; + } + else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); + } + + public: + explicit iterator( Column const& column ) : m_column( column ) { + assert( m_column.m_width > m_column.m_indent ); + assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); + calcLength(); + if( m_len == 0 ) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert( m_stringIndex < m_column.m_strings.size() ); + assert( m_pos <= m_end ); + if( m_pos + m_column.m_width < m_end ) + return addIndentAndSuffix(line().substr(m_pos, m_len)); + else + return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + if( m_pos < line().size() && line()[m_pos] == '\n' ) + m_pos += 1; + else + while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) + ++m_pos; + + if( m_pos == line().size() ) { + m_pos = 0; + ++m_stringIndex; + } + if( m_stringIndex < m_column.m_strings.size() ) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + + auto operator ==( iterator const& other ) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=( iterator const& other ) const -> bool { + return !operator==( other ); + } + }; + using const_iterator = iterator; + + explicit Column( std::string const& text ) { m_strings.push_back( text ); } + + auto width( size_t newWidth ) -> Column& { + assert( newWidth > 0 ); + m_width = newWidth; + return *this; + } + auto indent( size_t newIndent ) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent( size_t newIndent ) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, m_strings.size() }; } + + inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { + bool first = true; + for( auto line : col ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + ( Column const& other ) -> Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + class Spacer : public Column { + + public: + explicit Spacer( size_t spaceWidth ) : Column( "" ) { + width( spaceWidth ); + } + }; + + class Columns { + std::vector m_columns; + + public: + + class iterator { + friend Columns; + struct EndTag {}; + + std::vector const& m_columns; + std::vector m_iterators; + size_t m_activeIterators; + + iterator( Columns const& columns, EndTag ) + : m_columns( columns.m_columns ), + m_activeIterators( 0 ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.end() ); + } + + public: + explicit iterator( Columns const& columns ) + : m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) + { + m_iterators.reserve( m_columns.size() ); + + for( auto const& col : m_columns ) + m_iterators.push_back( col.begin() ); + } + + auto operator ==( iterator const& other ) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=( iterator const& other ) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for( size_t i = 0; i < m_columns.size(); ++i ) { + auto width = m_columns[i].width(); + if( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding + col; + if( col.size() < width ) + padding = std::string( width - col.size(), ' ' ); + else + padding = ""; + } + else { + padding += std::string( width, ' ' ); + } + } + return row; + } + auto operator ++() -> iterator& { + for( size_t i = 0; i < m_columns.size(); ++i ) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } + + auto operator += ( Column const& col ) -> Columns& { + m_columns.push_back( col ); + return *this; + } + auto operator + ( Column const& col ) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { + + bool first = true; + for( auto line : cols ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + }; + + inline auto Column::operator + ( Column const& other ) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; + } +}}} // namespace Catch::clara::TextFlow + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + +#include +#include +#include + +#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CATCH_PLATFORM_WINDOWS +#endif + +namespace Catch { namespace clara { +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template + struct UnaryLambdaTraits : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = typename std::remove_const::type>::type; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector m_args; + + public: + Args( int argc, char const* const* argv ) + : m_exeName(argv[0]), + m_args(argv + 1, argv + argc) {} + + Args( std::initializer_list args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; + } + }; + + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; + + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } + } + + public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); + } + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + }; + + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + } + + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if( m_type == Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult( BasicResult const &other ) + : ResultValueBase( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } + + template + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultBase::LogicError ); + assert( m_type != ResultBase::RuntimeError ); + if( m_type != ResultBase::Ok ) + std::abort(); + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + } +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template + inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if( result ) + target = std::move(temp); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; + + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } + }; + + template + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector &m_ref; + + explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + struct LambdaInvoker { + static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); + + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); + } + }; + + template<> + struct LambdaInvoker { + template + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + } + + template + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda::ArgType>( m_lambda, arg ); + } + }; + + template + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + struct Parser; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); + } + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|( T const &other ) const -> Parser; + + template + auto operator+( T const &other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} + + public: + template + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint( hint ) + {} + + template + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared>( ref ) ), + m_hint(hint) + {} + + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast( *this ); + }; + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast( *this ); + }; + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } + }; + + class ExeName : public ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + template + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { + return std::make_shared>( lambda) ; + } + + public: + ExeName() : m_name( std::make_shared( "" ) ) {} + + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared>( ref ); + } + + template + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared>( lambda ); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + } + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + class Arg : public ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + + assert( !m_ref->isFlag() ); + auto valueRef = static_cast( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + }; + + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CATCH_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } + + class Opt : public ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} + + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} + + template + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + template + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } + + auto getHelpColumns() const -> std::vector { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; + } + + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto flagRef = static_cast( m_ref.get() ); + auto result = flagRef->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + auto valueRef = static_cast( m_ref.get() ); + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = valueRef->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + } + + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CATCH_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif + } + return ParserRefImpl::validate(); + } + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; + + struct Parser : ParserBase { + + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + + auto getHelpColumns() const -> std::vector { + std::vector cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } + + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; + } + } + + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; + } + + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + }; + + template + template + auto ComposableParserImpl::operator|( T const &other ) const -> Parser { + return Parser() | static_cast( *this ) | other; + } +} // namespace detail + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + +}} // namespace Catch::clara + +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// end catch_clara.h +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +// end catch_commandline.h +#include +#include + +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace clara; + + auto const setWarning = [&]( std::string const& warning ) { + auto warningSet = [&]() { + if( warning == "NoAssertions" ) + return WarnAbout::NoAssertions; + + if ( warning == "NoTests" ) + return WarnAbout::NoTests; + + return WarnAbout::Nothing; + }(); + + if (warningSet == WarnAbout::Nothing) + return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast( config.warnings | warningSet ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line + ',' ); + } + } + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast( std::time(nullptr) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setColourUsage = [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + | Opt( config.reporterName, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setColourUsage, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.benchmarkResolutionMultiple, "multiplier" ) + ["--benchmark-resolution-multiple"] + ( "multiple of clock resolution to run benchmarks" ) + + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp + +#include +#include + +namespace Catch { + + bool SourceLineInfo::empty() const noexcept { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + std::string StreamEndStop::operator+() const { + return std::string(); + } + + NonCopyable::NonCopyable() = default; + NonCopyable::~NonCopyable() = default; + +} +// end catch_common.cpp +// start catch_config.cpp + +// start catch_enforce.h + +#include + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( ( Catch::ReusableStringStream() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); +#define CATCH_ERROR( msg ) \ + throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h +namespace Catch { + + Config::Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + TestSpecParser parser(ITagAliasRegistry::get()); + if (data.testsOrTags.empty()) { + parser.parse("~[.]"); // All not hidden tests + } + else { + m_hasTestFilters = true; + for( auto const& testOrTags : data.testsOrTags ) + parser.parse( testOrTags ); + } + m_testSpec = parser.testSpec(); + } + + std::string const& Config::getFilename() const { + return m_data.outputFilename ; + } + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + + std::string Config::getProcessName() const { return m_data.processName; } + std::string const& Config::getReporterName() const { return m_data.reporterName; } + + std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + std::ostream& Config::stream() const { return m_stream->stream(); } + std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } + bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } + ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } + unsigned int Config::rngSeed() const { return m_data.rngSeed; } + int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } + UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + IStream const* Config::openStream() { + return Catch::makeStream(m_data.outputFilename); + } + +} // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// start catch_errno_guard.h + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard(); + ~ErrnoGuard(); + private: + int m_oldErrno; + }; + +} + +// end catch_errno_guard.h +#include + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() = default; + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + bool useColourOnPlatform() { + return +#ifdef CATCH_PLATFORM_MAC + !isDebuggerActive() && +#endif +#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + isatty(STDOUT_FILENO) +#else + false +#endif + ; + } + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) { use( _colourCode ); } + Colour::Colour( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + } + Colour& Colour::operator=( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + return *this; + } + + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + + std::ostream& operator << ( std::ostream& os, Colour const& ) { + return os; + } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_console_colour.cpp +// start catch_context.cpp + +namespace Catch { + + class Context : public IMutableContext, NonCopyable { + + public: // IContext + virtual IResultCapture* getResultCapture() override { + return m_resultCapture; + } + virtual IRunner* getRunner() override { + return m_runner; + } + + virtual IConfigPtr const& getConfig() const override { + return m_config; + } + + virtual ~Context() override; + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) override { + m_runner = runner; + } + virtual void setConfig( IConfigPtr const& config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfigPtr m_config; + IRunner* m_runner = nullptr; + IResultCapture* m_resultCapture = nullptr; + }; + + IMutableContext *IMutableContext::currentContext = nullptr; + + void IMutableContext::createContext() + { + currentContext = new Context(); + } + + void cleanUpContext() { + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; +} +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h + +#include + +namespace Catch { + void writeToDebugConsole( std::string const& text ); +} + +// end catch_debug_console.h +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } + +#else + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } + +#endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp + +#ifdef CATCH_PLATFORM_MAC + +# include +# include +# include +# include +# include +# include +# include + +namespace Catch { + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include + #include + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp + +namespace Catch { + + ITransientExpression::~ITransientExpression() = default; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" << op << "\n" << rhs; + } +} +// end catch_decomposer.cpp +// start catch_errno_guard.cpp + +#include + +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp + +// start catch_exception_translator_registry.h + +#include +#include +#include + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator( const IExceptionTranslator* translator ); + virtual std::string translateActiveException() const override; + std::string tryTranslators() const; + + private: + std::vector> m_translators; + }; +} + +// end catch_exception_translator_registry.h +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { + } + + void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( std::unique_ptr( translator ) ); + } + + std::string ExceptionTranslatorRegistry::translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::Detail::stringify( [exception description] ); + } +#else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if( m_translators.empty() ) + std::rethrow_exception(std::current_exception()); + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } +} +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + // Report the error condition + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } +} + +#endif // signals/SEH handling + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + void FatalConditionHandler::reset() { + if (isSet) { + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + +bool FatalConditionHandler::isSet = false; +ULONG FatalConditionHandler::guaranteeSize = 0; +PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + +} // namespace Catch + +#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + // 32kb for the alternate stack seems to be sufficient. However, this value + // is experimentally determined, so that's not guaranteed. + constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + void FatalConditionHandler::handleSignal( int sig ) { + char const * name = ""; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sigStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + + void FatalConditionHandler::reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[sigStackSize] = {}; + +} // namespace Catch + +#else + +namespace Catch { + void FatalConditionHandler::reset() {} +} + +#endif // signals/SEH handling + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +// end catch_fatal_condition.cpp +// start catch_interfaces_capture.cpp + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp + +namespace Catch { + IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp + +// start catch_reporter_listening.h + +namespace Catch { + + class ListeningReporter : public IStreamingReporter { + using Reporters = std::vector; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; + + public: + ListeningReporter(); + + void addListener( IStreamingReporterPtr&& listener ); + void addReporter( IStreamingReporterPtr&& reporter ); + + public: // IStreamingReporter + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases( std::string const& spec ) override; + + static std::set getSupportedVerbosities(); + + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testGroupStarting( GroupInfo const& groupInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; + + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testGroupEnded( TestGroupStats const& testGroupStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + + void skipTest( TestCaseInfo const& testInfo ) override; + bool isMulti() const override; + + }; + +} // end namespace Catch + +// end catch_reporter_listening.h +namespace Catch { + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& ReporterConfig::stream() const { return *m_stream; } + IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } + + TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} + + GroupInfo::GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + + AssertionStats::~AssertionStats() = default; + + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + + SectionStats::~SectionStats() = default; + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + + TestCaseStats::~TestCaseStats() = default; + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + + TestGroupStats::~TestGroupStats() = default; + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestRunStats::~TestRunStats() = default; + + void IStreamingReporter::fatalErrorEncountered( StringRef ) {} + bool IStreamingReporter::isMulti() const { return false; } + + IReporterFactory::~IReporterFactory() = default; + IReporterRegistry::~IReporterRegistry() = default; + +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp + +namespace Catch { + IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp + +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; +} +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else + + Catch::LeakDetector::LeakDetector() {} + +#endif +// end catch_leak_detector.cpp +// start catch_list.cpp + +// start catch_list.h + +#include + +namespace Catch { + + std::size_t listTests( Config const& config ); + + std::size_t listTestsNamesOnly( Config const& config ); + + struct TagInfo { + void add( std::string const& spelling ); + std::string all() const; + + std::set spellings; + std::size_t count = 0; + }; + + std::size_t listTags( Config const& config ); + + std::size_t listReporters( Config const& /*config*/ ); + + Option list( Config const& config ); + +} // end namespace Catch + +// end catch_list.h +// start catch_text.h + +namespace Catch { + using namespace clara::TextFlow; +} + +// end catch_text.h +#include +#include +#include + +namespace Catch { + + std::size_t listTests( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + } + + auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; + if( config.verbosity() >= Verbosity::High ) { + Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column( description ).indent(4) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; + } + + if( !config.hasTestFilters() ) + Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; + return matchedTestCases.size(); + } + + std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + matchedTests++; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.verbosity() >= Verbosity::High ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + void TagInfo::add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + std::string out; + for( auto const& spelling : spellings ) + out += "[" + spelling + "]"; + return out; + } + + std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + } + + std::map tagCounts; + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCase : matchedTestCases ) { + for( auto const& tagName : testCase.getTestCaseInfo().tags ) { + std::string lcaseTagName = toLower( tagName ); + auto countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( auto const& tagCount : tagCounts ) { + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); + auto wrapper = Column( tagCount.second.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); + Catch::cout() << str << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for( auto const& factoryKvp : factories ) + maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); + + for( auto const& factoryKvp : factories ) { + Catch::cout() + << Column( factoryKvp.first + ":" ) + .indent(2) + .width( 5+maxNameLen ) + + Column( factoryKvp.second->getDescription() ) + .initialIndent(0) + .indent(2) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch { +namespace Matchers { + namespace Impl { + + std::string MatcherUntypedBase::toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + + } // namespace Impl +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_floating.cpp + +// start catch_to_string.hpp + +#include + +namespace Catch { + template + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +// end catch_to_string.hpp +#include +#include +#include +#include + +namespace Catch { +namespace Matchers { +namespace Floating { +enum class FloatingPointKind : uint8_t { + Float, + Double +}; +} +} +} + +namespace { + +template +struct Converter; + +template <> +struct Converter { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + Converter(float f) { + std::memcpy(&i, &f, sizeof(f)); + } + int32_t i; +}; + +template <> +struct Converter { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + Converter(double d) { + std::memcpy(&i, &d, sizeof(d)); + } + int64_t i; +}; + +template +auto convert(T t) -> Converter { + return Converter(t); +} + +template +bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (std::isnan(lhs) || std::isnan(rhs)) { + return false; + } + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc.i < 0) != (rc.i < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + auto ulpDiff = std::abs(lc.i - rc.i); + return ulpDiff <= maxUlpDiff; +} + +} + +namespace Catch { +namespace Matchers { +namespace Floating { + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + if (m_margin < 0) { + throw std::domain_error("Allowed margin difference has to be >= 0"); + } + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + if (m_ulps < 0) { + throw std::domain_error("Allowed ulp difference has to be >= 0"); + } + } + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case FloatingPointKind::Float: + return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps(matchee, m_target, m_ulps); + default: + throw std::domain_error("Unknown FloatingPointKind value"); + } + } + + std::string WithinUlpsMatcher::describe() const { + return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + } + +}// namespace Floating + +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) { + return Floating::WithinAbsMatcher(target, margin); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.cpp +// start catch_matchers_generic.cpp + +std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} +// end catch_matchers_generic.cpp +// start catch_matchers_string.cpp + +#include + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp + +// start catch_uncaught_exceptions.h + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() ){ + getResultCapture().popScopedMessage(m_info); + } + } +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp + +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H + +#include +#include +#include + +namespace Catch { + + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; + + public: + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + ~RedirectedStream(); + }; + + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut(); + auto str() const -> std::string; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + public: + RedirectedStdErr(); + auto str() const -> std::string; + }; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const&) = delete; + TempFile& operator=(TempFile const&) = delete; + TempFile(TempFile&&) = delete; + TempFile& operator=(TempFile&&) = delete; + + TempFile(); + ~TempFile(); + + std::FILE* getFile(); + std::string getContents(); + + private: + std::FILE* m_file = nullptr; + #if defined(_MSC_VER) + char m_buffer[L_tmpnam] = { 0 }; + #endif + }; + + class OutputRedirect { + public: + OutputRedirect(OutputRedirect const&) = delete; + OutputRedirect& operator=(OutputRedirect const&) = delete; + OutputRedirect(OutputRedirect&&) = delete; + OutputRedirect& operator=(OutputRedirect&&) = delete; + + OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + ~OutputRedirect(); + + private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string& m_stdoutDest; + std::string& m_stderrDest; + }; + +#endif + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include +#include +#include +#include +#include + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include // dup and dup2 + #endif +#endif + +namespace Catch { + + RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + + RedirectedStdErr::RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + throw std::runtime_error("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + throw std::runtime_error("Could not translate errno to string"); + } + throw std::runtime_error("Could not open the temp file: " + std::string(m_buffer) + buffer); + } + } +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + throw std::runtime_error("Could not create a temp file."); + } + } + +#endif + + TempFile::~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } + + FILE* TempFile::getFile() { + return m_file; + } + + std::string TempFile::getContents() { + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) { + sstr << buffer; + } + return sstr.str(); + } + + OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : + m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) { + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); + } + + OutputRedirect::~OutputRedirect() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); + } + +#endif // CATCH_CONFIG_NEW_CAPTURE + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif +// end catch_output_redirect.cpp +// start catch_random_number_generator.cpp + +// start catch_random_number_generator.h + +#include +#include + +namespace Catch { + + struct IConfig; + + std::mt19937& rng(); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + +} + +// end catch_random_number_generator.h +namespace Catch { + + std::mt19937& rng() { + static std::mt19937 s_rng; + return s_rng; + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) { + std::srand( config.rngSeed() ); + rng().seed( config.rngSeed() ); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include +#include +#include +#include + +namespace Catch { + + class TestCase; + struct IConfig; + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + + void enforceNoDuplicateTestCases( std::vector const& functions ); + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + virtual ~TestRegistry() = default; + + virtual void registerTest( TestCase const& testCase ); + + std::vector const& getAllTests() const override; + std::vector const& getAllTestsSorted( IConfig const& config ) const override; + + private: + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class TestInvokerAsFunction : public ITestInvoker { + void(*m_testAsFunction)(); + public: + TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; + + void invoke() const override; + }; + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ); + + /////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + ~ReporterRegistry() override; + + IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; + + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); + void registerListener( IReporterFactoryPtr const& factory ); + + FactoryMap const& getFactories() const override; + Listeners const& getListeners() const override; + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h + +// start catch_tag_alias.h + +#include + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// end catch_tag_alias.h +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include +#include + +namespace Catch { + + class StartupExceptionRegistry { + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector const& getExceptions() const noexcept; + private: + std::vector m_exceptions; + }; + +} // end namespace Catch + +// end catch_startup_exception_registry.h +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub, + private NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerReporter( name, factory ); + } + void registerListener( IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerListener( factory ); + } + void registerTest( TestCase const& testInfo ) override { + m_testCaseRegistry.registerTest( testInfo ); + } + void registerTranslator( const IExceptionTranslator* translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { + m_exceptionRegistry.add(std::current_exception()); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + }; + + // Single, global, instance + RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = nullptr; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = nullptr; + cleanUpContext(); + ReusableStringStream::cleanup(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp + +namespace Catch { + + ReporterRegistry::~ReporterRegistry() = default; + + IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( ReporterConfig( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { + m_factories.emplace(name, factory); + } + void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { + m_listeners.push_back( factory ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} +// end catch_reporter_registry.cpp +// start catch_result_type.cpp + +namespace Catch { + + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp + +#include +#include +#include + +namespace Catch { + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); + } + + void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); + } + + Totals RunContext::runTest(TestCase const& testCase) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const& testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + IConfigPtr RunContext::config() const { + return m_config; + } + + IStreamingReporter& RunContext::reporter() const { + return *m_reporter; + } + + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (!result.isOk()) { + m_lastAssertionPassed = false; + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed == static_cast(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; + try { + if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStdOut redirectedStdOut; + RedirectedStdErr redirectedStdErr; + + timer.start(); + invokeActiveTestCase(); + redirectedCout += redirectedStdOut.str(); + redirectedCerr += redirectedStdErr.str(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } else { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } catch (TestFailureException&) { + // This just means the test was aborted due to failure + } catch (...) { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } +} +// end catch_run_context.cpp +// start catch_section.cpp + +namespace Catch { + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; + if( uncaught_exceptions() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ) + : name( _name ), + lineInfo( _lineInfo ) + {} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include + +namespace Catch { + + class Session : NonCopyable { + public: + + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + + void useConfigData( ConfigData const& configData ); + + int run( int argc, char* argv[] ); + #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int run( int argc, wchar_t* const argv[] ); + #endif + int run(); + + clara::Parser const& cli() const; + void cli( clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); +} + +// end catch_version.h +#include +#include + +namespace Catch { + + namespace { + const int MaxExitCode = 255; + + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; + } + + IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { + return createReporter(config->getReporterName(), config); + } + + auto multi = std::unique_ptr(new ListeningReporter); + + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi->addListener(listener->create(Catch::ReporterConfig(config))); + } + multi->addReporter(createReporter(config->getReporterName(), config)); + return std::move(multi); + } + + Catch::Totals runTests(std::shared_ptr const& config) { + // FixMe: Add listeners in order first, then add reporters. + + auto reporter = makeReporter(config); + + RunContext context(config, std::move(reporter)); + + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + + auto const& allTestCases = getAllTestCasesSorted(*config); + for (auto const& testCase : allTestCases) { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); + } + + if (config->warnAboutNoTests() && totals.testCases.total() == 0) { + ReusableStringStream testConfig; + + bool first = true; + for (const auto& input : config->getTestsOrTags()) { + if (!first) { testConfig << ' '; } + first = false; + testConfig << input; + } + + context.reporter().noMatchingTestCases(testConfig.str()); + totals.error = -1; + } + + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; + } + + void applyFilenamesAsTags(Catch::IConfig const& config) { + auto& tests = const_cast&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; + + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } + } + + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + try { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + catch(...) { getMutableRegistryHub().registerStartupException(); } + } + + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + m_startupExceptions = true; + Colour colourGuard( Colour::Red ); + Catch::cerr() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + } + } + } + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" << std::endl; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + } + + int Session::applyCommandLine( int argc, char const * const * argv ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { + Catch::cerr() + << Colour( Colour::Red ) + << "\nError(s) in input:\n" + << Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" << std::endl; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + return 0; + } + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run( int argc, char* argv[] ) { + if( m_startupExceptions ) + return 1; + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int Session::run( int argc, wchar_t* const argv[] ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = run( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast(std::getchar()); + } + return exitCode; + } + + clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = std::make_shared( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if( m_configData.showHelp || m_configData.libIdentify ) + return 0; + + try + { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + auto totals = runTests( m_config ); + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast(totals.assertions.failed))); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } + } + +} // end namespace Catch +// end catch_session.cpp +// start catch_startup_exception_registry.cpp + +namespace Catch { + void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + try { + m_exceptions.push_back(exception); + } + catch(...) { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } + +} // end namespace Catch +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp + +#include +#include +#include +#include +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { + + Catch::IStream::~IStream() = default; + + namespace detail { namespace { + template + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( StringRef filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + std::unique_ptr> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + }} // namespace anon::detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( StringRef const &filename ) -> IStream const* { + if( filename.empty() ) + return new detail::CoutStream(); + else if( filename[0] == '%' ) { + if( filename == "%debug" ) + return new detail::DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); + } + else + return new detail::FileStream( filename ); + } + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector> m_streams; + std::vector m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + static StringStreams* s_instance; + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( std::unique_ptr( new std::ostringstream ) ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + + // !TBD: put in TLS + static auto instance() -> StringStreams& { + if( !s_instance ) + s_instance = new StringStreams(); + return *s_instance; + } + static void cleanup() { + delete s_instance; + s_instance = nullptr; + } + }; + + StringStreams* StringStreams::s_instance = nullptr; + + void ReusableStringStream::cleanup() { + StringStreams::cleanup(); + } + + ReusableStringStream::ReusableStringStream() + : m_index( StringStreams::instance().add() ), + m_oss( StringStreams::instance().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast( m_oss )->str(""); + m_oss->clear(); + StringStreams::instance().release( m_index ); + } + + auto ReusableStringStream::str() const -> std::string { + return static_cast( m_oss )->str(); + } + + /////////////////////////////////////////////////////////////////////////// + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif +} + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stream.cpp +// start catch_string_manip.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + } + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include +#include +#include + +namespace { + const uint32_t byte_2_lead = 0xC0; + const uint32_t byte_3_lead = 0xE0; + const uint32_t byte_4_lead = 0xF0; +} + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, static_cast(std::strlen(rawChars) ) ) + {} + + StringRef::operator std::string() const { + return std::string( m_start, m_size ); + } + + void StringRef::swap( StringRef& other ) noexcept { + std::swap( m_start, other.m_start ); + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + + auto StringRef::c_str() const -> char const* { + if( isSubstring() ) + const_cast( this )->takeOwnership(); + return m_start; + } + auto StringRef::currentData() const noexcept -> char const* { + return m_start; + } + + auto StringRef::isOwned() const noexcept -> bool { + return m_data != nullptr; + } + auto StringRef::isSubstring() const noexcept -> bool { + return m_start[m_size] != '\0'; + } + + void StringRef::takeOwnership() { + if( !isOwned() ) { + m_data = new char[m_size+1]; + memcpy( m_data, m_start, m_size ); + m_data[m_size] = '\0'; + m_start = m_data; + } + } + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if( start < m_size ) + return StringRef( m_start+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return + size() == other.size() && + (std::strncmp( m_start, other.m_start, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { + return !operator==( other ); + } + + auto StringRef::operator[](size_type index) const noexcept -> char { + return m_start[index]; + } + + auto StringRef::numberOfCharacters() const noexcept -> size_type { + size_type noChars = m_size; + // Make adjustments for uft encodings + for( size_type i=0; i < m_size; ++i ) { + char c = m_start[i]; + if( ( c & byte_2_lead ) == byte_2_lead ) { + noChars--; + if (( c & byte_3_lead ) == byte_3_lead ) + noChars--; + if( ( c & byte_4_lead ) == byte_4_lead ) + noChars--; + } + } + return noChars; + } + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { + std::string str; + str.reserve( lhs.size() + rhs.size() ); + str += lhs; + str += rhs; + return str; + } + auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + + auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { + return os.write(str.currentData(), str.size()); + } + + auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { + lhs.append(rhs.currentData(), rhs.size()); + return lhs; + } + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch { + TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} +} +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + try { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } catch (...) { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast(tag[0]) ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo ); + } + } + + TestCase makeTestCase( ITestInvoker* _testCase, + std::string const& _className, + NameAndTags const& nameAndTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden = false; + + // Parse out tags + std::vector tags; + std::string desc, tag; + bool inTag = false; + std::string _descOrTags = nameAndTags.tags; + for (char c : _descOrTags) { + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( ( prop & TestCaseInfo::IsHidden ) != 0 ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.push_back( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.push_back( "." ); + } + + TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, std::move(info) ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::vector tags ) { + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); + testCaseInfo.lcaseTags.clear(); + + for( auto const& tag : tags ) { + std::string lcaseTag = toLower( tag ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.push_back( lcaseTag ); + } + testCaseInfo.tags = std::move(tags); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; + } + + TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp + +#include + +namespace Catch { + + std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + seedRng( config ); + std::shuffle( sorted.begin(), sorted.end(), rng() ); + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( auto const& function : functions ) { + auto prev = seenFunctions.insert( function ); + CATCH_ENFORCE( prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( auto const& testCase : testCases ) + if( matchTest( testCase, testSpec, config ) ) + filtered.push_back( testCase ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + void TestRegistry::registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( rss.str() ) ); + } + m_functions.push_back( testCase ); + } + + std::vector const& TestRegistry::getAllTests() const { + return m_functions; + } + std::vector const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + /////////////////////////////////////////////////////////////////////////// + TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} + + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); + } + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + +} // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp + +#include +#include +#include +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + + ITracker::~ITracker() = default; + + TrackerContext& TrackerContext::instance() { + static TrackerContext s_instance; + return s_instance; + } + + ITracker& TrackerContext::startRun() { + m_rootTracker = std::make_shared( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; + } + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} + bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const { + return + tracker->nameAndLocation().location == m_nameAndLocation.location && + tracker->nameAndLocation().name == m_nameAndLocation.name; + } + + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ) + {} + + NameAndLocation const& TrackerBase::nameAndLocation() const { + return m_nameAndLocation; + } + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + bool TrackerBase::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + bool TrackerBase::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + bool TrackerBase::hasChildren() const { + return !m_children.empty(); + } + + void TrackerBase::addChild( ITrackerPtr const& child ) { + m_children.push_back( child ); + } + + ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + return( it != m_children.end() ) + ? *it + : nullptr; + } + ITracker& TrackerBase::parent() { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + void TrackerBase::openChild() { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + bool TrackerBase::isSectionTracker() const { return false; } + bool TrackerBase::isIndexTracker() const { return false; } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + std::shared_ptr section; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = std::static_pointer_cast( childTracker ); + } + else { + section = std::make_shared( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void SectionTracker::tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + + IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ) + {} + + bool IndexTracker::isIndexTracker() const { return true; } + + IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + std::shared_ptr tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast( childTracker ); + } + else { + tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int IndexTracker::index() const { return m_index; } + + void IndexTracker::moveNext() { + m_index++; + m_children.clear(); + } + + void IndexTracker::close() { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch { + + auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); + } + + NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} + + AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { + try { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName( classOrMethod ), + nameAndTags, + lineInfo)); + } catch (...) { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + AutoReg::~AutoReg() = default; +} +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include +#include +#include +#include + +namespace Catch { + + TestSpec::Pattern::~Pattern() = default; + TestSpec::NamePattern::~NamePattern() = default; + TestSpec::TagPattern::~TagPattern() = default; + TestSpec::ExcludedPattern::~ExcludedPattern() = default; + + TestSpec::NamePattern::NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + + TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); + } + + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( auto const& pattern : m_patterns ) { + if( !pattern->matches( testCase ) ) + return false; + } + return true; + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( auto const& filter : m_filters ) + if( filter.matches( testCase ) ) + return true; + return false; + } +} +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return m_testSpec; + } + + void TestSpecParser::visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void TestSpecParser::escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp + +#include + +static const uint64_t nanosecondsInSecond = 1000000000; + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); + } + + namespace { + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + auto startTime = getCurrentNanosecondsSinceEpoch(); + + for( std::size_t i = 0; i < iterations; ++i ) { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } while( ticks == baseTicks ); + + auto delta = ticks - baseTicks; + sum += delta; + + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) { + return sum / i; + } + } + + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; + } + } + auto getEstimatedClockResolution() -> uint64_t { + static auto s_resolution = estimateClockResolution(); + return s_resolution; + } + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include +#include + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + rss << std::setw(2) << static_cast(bytes[i]); + return rss.str(); + } +} + +template +std::string fpToString( T value, int precision ) { + if (std::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker::convert(const std::string& str) { + if (!getCurrentContext().getConfig()->showInvisibles()) { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) { + switch (c) { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} +#endif + +std::string StringMaker::convert(char const* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(char* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +#endif + +std::string StringMaker::convert(int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast(value)); +} +std::string StringMaker::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker::convert(bool b) { + return b ? "true" : "false"; +} + +std::string StringMaker::convert(char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker::convert(signed char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} +std::string StringMaker::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast(c)); +} + +std::string StringMaker::convert(std::nullptr_t) { + return "nullptr"; +} + +std::string StringMaker::convert(float value) { + return fpToString(value, 5) + 'f'; +} +std::string StringMaker::convert(double value) { + return fpToString(value, 10); +} + +std::string ratio_string::symbol() { return "a"; } +std::string ratio_string::symbol() { return "f"; } +std::string ratio_string::symbol() { return "p"; } +std::string ratio_string::symbol() { return "n"; } +std::string ratio_string::symbol() { return "u"; } +std::string ratio_string::symbol() { return "m"; } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_tostring.cpp +// start catch_totals.cpp + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + +} +// end catch_totals.cpp +// start catch_uncaught_exceptions.cpp + +#include + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch +// end catch_uncaught_exceptions.cpp +// start catch_version.cpp + +#include + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 2, 3, 0, "", 0 ); + return version; + } + +} +// end catch_version.cpp +// start catch_wildcard_pattern.cpp + +#include + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } +} +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp + +#include + +using uchar = unsigned char; + +namespace Catch { + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << ""; + m_needsNewline = true; + return *this; + } + + void XmlWriter::writeStylesheetRef( std::string const& url ) { + m_os << "\n"; + } + + XmlWriter& XmlWriter::writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } +} +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp + +#include +#include +#include +#include +#include + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result) { + result.getExpandedExpression(); + } + + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) + :StreamingReporterBase(_config) {} + + void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} + + bool TestEventListenerBase::assertionEnded(AssertionStats const &) { + return false; + } + +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp + +namespace { + +#ifdef CATCH_PLATFORM_MAC + const char* failedString() { return "FAILED"; } + const char* passedString() { return "PASSED"; } +#else + const char* failedString() { return "failed"; } + const char* passedString() { return "passed"; } +#endif + + // Colour::LightGrey + Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } + + std::string bothOrAll( std::size_t count ) { + return count == 1 ? std::string() : + count == 2 ? "both " : "all " ; + } + +} // anon namespace + +namespace Catch { +namespace { +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + Colour colour(Colour::ResultError); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : std::string(); + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion") << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } else if (totals.assertions.failed) { + Colour colour(Colour::ResultError); + out << + "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + } else { + Colour colour(Colour::ResultSuccess); + out << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << + " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} + +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, passedString()); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); + else + printResultType(Colour::Error, failedString()); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, failedString()); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, failedString()); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, failedString()); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, failedString()); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, std::string const& passOrFail) const { + if (!passOrFail.empty()) { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue(std::string const& issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + Colour colour(dimColour()); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + { + Colour colour(dimColour()); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = dimColour()) { + if (itMessage == messages.end()) + return; + + // using messages.end() directly yields (or auto) compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast(std::distance(itMessage, itEnd)); + + { + Colour colourGuard(colour); + stream << " with " << pluralise(N, "message") << ':'; + } + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + Colour colourGuard(dimColour()); + stream << " and"; + } + } + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + ReporterPreferences CompactReporter::getPreferences() const { + return m_reporterPrefs; + } + + void CompactReporter::noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + void CompactReporter::assertionStarting( AssertionInfo const& ) {} + + bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( stream, _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + CompactReporter::~CompactReporter() {} + + CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + if (result.isOk()) + stream << '\n'; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + +private: + void printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; +}; + +std::size_t makeRatio(std::size_t number, std::size_t total) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : ratio; +} + +std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} + +struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; + +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + uint64_t m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); + default: + return static_cast(m_inNanoseconds); + } + } + auto unitsAsString() const -> std::string { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "µs"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << " " << duration.unitsAsString(); + } +}; +} // end anon namespace + +class TablePrinter { + std::ostream& m_os; + std::vector m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector columnInfos ) + : m_os( os ), + m_columnInfos( std::move( columnInfos ) ) {} + + auto columnInfos() const -> std::vector const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + for (auto const& info : m_columnInfos) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } + + template + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 2 < static_cast(colInfo.width)) + ? std::string(colInfo.width - (strSize + 2), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig const& config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + { + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, + { "iters", 8, ColumnInfo::Right }, + { "elapsed ns", 14, ColumnInfo::Right }, + { "average", 14, ColumnInfo::Right } + })) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases(std::string const& spec) { + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; + + lazyPrint(); + + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + Colour colour(Colour::ResultError); + if (m_sectionStack.size() > 1) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column( info.name ).width( static_cast( m_tablePrinter->columnInfos()[0].width - 2 ) ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { + Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + (*m_tablePrinter) + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { + if (currentGroupInfo.used) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals(_testGroupStats.totals); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded(_testGroupStats); +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); + + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour(Colour::SecondaryText); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if (m_config->rngSeed() != 0) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; +} +void ConsoleReporter::lazyPrintGroupInfo() { + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + Colour colourGuard(Colour::Headers); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if (!lineInfo.empty()) { + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard(Colour::Headers); + printHeaderString(_name); + } +} + +// if string has a : in first line will set indent to follow it on +// subsequent lines +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} + +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( std::move( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::size_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + +}; + +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << Colour(Colour::ResultSuccess) << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion") << " in " + << pluralise(totals.testCases.passed, "test case") << ')' + << '\n'; + } else { + + std::vector columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases", columns, 0); + printSummaryRow("assertions", columns, 1); + } +} +void ConsoleReporter::printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row) { + for (auto col : cols) { + std::string value = col.rows[row]; + if (col.label.empty()) { + stream << label << ": "; + if (value != "0") + stream << value; + else + stream << Colour(Colour::Warning) << "- none -"; + } else if (value != "0") { + stream << Colour(Colour::LightGrey) << " | "; + stream << Colour(col.colour) + << value << ' ' << col.label; + } + } + stream << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + stream << Colour(Colour::Error) << std::string(failedRatio, '='); + stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) + stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); + else + stream << Colour(Colour::Success) << std::string(passedRatio, '='); + } else { + stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; +} + +CATCH_REGISTER_REPORTER("console", ConsoleReporter) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include +#include +#include +#include + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + std::string fileNameTag(const std::vector &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (std::string const& tag) {return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); + } + } // anonymous namespace + + JunitReporter::JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + JunitReporter::~JunitReporter() {} + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + void JunitReporter::testRunEndedCumulative() { + xml.endElement(); + } + + void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; + } + + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; + + writeSection( className, "", rootSection ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode ); + else + writeSection( className, name, *childNode ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + ReusableStringStream rss; + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), false ); + } + } + + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_listening.cpp + +#include + +namespace Catch { + + ListeningReporter::ListeningReporter() { + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; + } + + void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { + m_listeners.push_back( std::move( listener ) ); + } + + void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move( reporter ); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; + } + + ReporterPreferences ListeningReporter::getPreferences() const { + return m_preferences; + } + + std::set ListeningReporter::getSupportedVerbosities() { + return std::set{ }; + } + + void ListeningReporter::noMatchingTestCases( std::string const& spec ) { + for ( auto const& listener : m_listeners ) { + listener->noMatchingTestCases( spec ); + } + m_reporter->noMatchingTestCases( spec ); + } + + void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkStarting( benchmarkInfo ); + } + m_reporter->benchmarkStarting( benchmarkInfo ); + } + void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkEnded( benchmarkStats ); + } + m_reporter->benchmarkEnded( benchmarkStats ); + } + + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testRunStarting( testRunInfo ); + } + m_reporter->testRunStarting( testRunInfo ); + } + + void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupStarting( groupInfo ); + } + m_reporter->testGroupStarting( groupInfo ); + } + + void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseStarting( testInfo ); + } + m_reporter->testCaseStarting( testInfo ); + } + + void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->sectionStarting( sectionInfo ); + } + m_reporter->sectionStarting( sectionInfo ); + } + + void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->assertionStarting( assertionInfo ); + } + m_reporter->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { + for( auto const& listener : m_listeners ) { + static_cast( listener->assertionEnded( assertionStats ) ); + } + return m_reporter->assertionEnded( assertionStats ); + } + + void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto const& listener : m_listeners ) { + listener->sectionEnded( sectionStats ); + } + m_reporter->sectionEnded( sectionStats ); + } + + void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseEnded( testCaseStats ); + } + m_reporter->testCaseEnded( testCaseStats ); + } + + void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupEnded( testGroupStats ); + } + m_reporter->testGroupEnded( testGroupStats ); + } + + void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto const& listener : m_listeners ) { + listener->testRunEnded( testRunStats ); + } + m_reporter->testRunEnded( testRunStats ); + } + + void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->skipTest( testInfo ); + } + m_reporter->skipTest( testInfo ); + } + + bool ListeningReporter::isMulti() const { + return true; + } + +} // end namespace Catch +// end catch_reporter_listening.cpp +// start catch_reporter_xml.cpp + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + void XmlReporter::noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionStarting( AssertionInfo const& ) { } + + bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + +namespace Catch { + LeakDetector leakDetector; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_impl.hpp +#endif + +#ifdef CATCH_CONFIG_MAIN +// start catch_default_main.hpp + +#ifndef __OBJC__ + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char**)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +// end catch_default_main.hpp +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +#if !defined(CATCH_CONFIG_DISABLE) +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And when: " << desc ) +#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + +#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And when: " << desc ) +#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +using Catch::Detail::Approx; + +#else // CATCH_CONFIG_DISABLE + +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) (void)(0) +#define CATCH_REQUIRE_FALSE( ... ) (void)(0) + +#define CATCH_REQUIRE_THROWS( ... ) (void)(0) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + +#define CATCH_CHECK( ... ) (void)(0) +#define CATCH_CHECK_FALSE( ... ) (void)(0) +#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL( ... ) (void)(0) + +#define CATCH_CHECK_THROWS( ... ) (void)(0) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + +#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define CATCH_SECTION( ... ) +#define CATCH_DYNAMIC_SECTION( ... ) +#define CATCH_FAIL( ... ) (void)(0) +#define CATCH_FAIL_CHECK( ... ) (void)(0) +#define CATCH_SUCCEED( ... ) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_GIVEN( desc ) +#define CATCH_WHEN( desc ) +#define CATCH_AND_WHEN( desc ) +#define CATCH_THEN( desc ) +#define CATCH_AND_THEN( desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) (void)(0) +#define REQUIRE_FALSE( ... ) (void)(0) + +#define REQUIRE_THROWS( ... ) (void)(0) +#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) (void)(0) + +#define CHECK( ... ) (void)(0) +#define CHECK_FALSE( ... ) (void)(0) +#define CHECKED_IF( ... ) if (__VA_ARGS__) +#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL( ... ) (void)(0) + +#define CHECK_THROWS( ... ) (void)(0) +#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) (void)(0) + +#define REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) (void)(0) +#define WARN( msg ) (void)(0) +#define CAPTURE( msg ) (void)(0) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define METHOD_AS_TEST_CASE( method, ... ) +#define REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define SECTION( ... ) +#define DYNAMIC_SECTION( ... ) +#define FAIL( ... ) (void)(0) +#define FAIL_CHECK( ... ) (void)(0) +#define SUCCEED( ... ) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + +#define GIVEN( desc ) +#define WHEN( desc ) +#define AND_WHEN( desc ) +#define THEN( desc ) +#define AND_THEN( desc ) + +using Catch::Detail::Approx; + +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +// start catch_reenable_warnings.h + + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +// end catch_reenable_warnings.h +// end catch.hpp +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/.cs211/lib/catch/src/catch-main.cxx b/.cs211/lib/catch/src/catch-main.cxx new file mode 100644 index 0000000..95310e6 --- /dev/null +++ b/.cs211/lib/catch/src/catch-main.cxx @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hxx" diff --git a/.cs211/lib/ge211/.gitignore b/.cs211/lib/ge211/.gitignore new file mode 100644 index 0000000..6ac0149 --- /dev/null +++ b/.cs211/lib/ge211/.gitignore @@ -0,0 +1,7 @@ +# CLion +.idea/* + +# Build directories: +build/ +builds/ +cmake-build-*/ diff --git a/.cs211/lib/ge211/.travis.yml b/.cs211/lib/ge211/.travis.yml new file mode 100644 index 0000000..a277ab5 --- /dev/null +++ b/.cs211/lib/ge211/.travis.yml @@ -0,0 +1,44 @@ +language: cpp +dist: xenial + +os: + - linux + +compiler: + - clang + +install: +# Where we install to: + - export PREFIX=${TRAVIS_BUILD_DIR}/../deps + - mkdir -p ${PREFIX} + - | + export PATH="${PREFIX}/bin:${PATH}" + export LD_LIBRARY_PATH="${PREFIX}/lib:${LD_LIBRARY_PATH}" + export DYLD_LIBRARY_PATH="${PREFIX}/lib:${DYLD_LIBRARY_PATH}" + +# Upgrade CMake: + - | + CMAKE_VERSION=3.14.2 + CMAKE_URL=https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-Linux-x86_64.tar.gz + travis_retry wget --quiet -O- ${CMAKE_URL} | \ + tar --strip-components=1 -xz -C ${PREFIX} && + cmake --version + +# Install SDL2 and friends: + - I=scripts/autotools_install.sh + - $I https://libsdl.org/release/SDL2-2.0.10.tar.gz + - $I https://libsdl.org/projects/SDL_mixer/release/SDL2_mixer-2.0.4.tar.gz + - $I https://libsdl.org/projects/SDL_ttf/release/SDL2_ttf-2.0.15.tar.gz + - $I https://libsdl.org/projects/SDL_image/release/SDL2_image-2.0.5.tar.gz + +cache: + directories: + - ../deps + +before_script: + - mkdir build + - cmake -S . -B build + +script: + - cmake --build build -- -j2 + diff --git a/.cs211/lib/ge211/3rdparty/utf8-cpp/CMakeLists.txt b/.cs211/lib/ge211/3rdparty/utf8-cpp/CMakeLists.txt new file mode 100644 index 0000000..e9eb243 --- /dev/null +++ b/.cs211/lib/ge211/3rdparty/utf8-cpp/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.13) +project(utf8-cpp CXX) + +add_library(utf8-cpp INTERFACE) + +target_include_directories(utf8-cpp SYSTEM INTERFACE + $) + +export(TARGETS utf8-cpp FILE Utf8CppConfig.cmake) diff --git a/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8.h b/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8.h new file mode 100644 index 0000000..82b13f5 --- /dev/null +++ b/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8.h @@ -0,0 +1,34 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "utf8/checked.h" +#include "utf8/unchecked.h" + +#endif // header guard diff --git a/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/checked.h b/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/checked.h new file mode 100644 index 0000000..1331155 --- /dev/null +++ b/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/checked.h @@ -0,0 +1,327 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" +#include + +namespace utf8 +{ + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception { + }; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception { + uint32_t cp; + public: + invalid_code_point(uint32_t cp) : cp(cp) {} + virtual const char* what() const throw() { return "Invalid code point"; } + uint32_t code_point() const {return cp;} + }; + + class invalid_utf8 : public exception { + uint8_t u8; + public: + invalid_utf8 (uint8_t u) : u8(u) {} + virtual const char* what() const throw() { return "Invalid UTF-8"; } + uint8_t utf8_octet() const {return u8;} + }; + + class invalid_utf16 : public exception { + uint16_t u16; + public: + invalid_utf16 (uint16_t u) : u16(u) {} + virtual const char* what() const throw() { return "Invalid UTF-16"; } + uint16_t utf16_word() const {return u16;} + }; + + class not_enough_room : public exception { + public: + virtual const char* what() const throw() { return "Not enough space"; } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + throw not_enough_room(); + case internal::INVALID_LEAD: + out = utf8::append (replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append (replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + template + uint32_t next(octet_iterator& it, octet_iterator end) + { + uint32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) { + case internal::UTF8_OK : + break; + case internal::NOT_ENOUGH_ROOM : + throw not_enough_room(); + case internal::INVALID_LEAD : + case internal::INCOMPLETE_SEQUENCE : + case internal::OVERLONG_SEQUENCE : + throw invalid_utf8(*it); + case internal::INVALID_CODE_POINT : + throw invalid_code_point(cp); + } + return cp; + } + + template + uint32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + uint32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + /// Deprecated in versions that include "prior" + template + uint32_t previous(octet_iterator& it, octet_iterator pass_start) + { + octet_iterator end = it; + while (utf8::internal::is_trail(*(--it))) + if (it == pass_start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + octet_iterator temp = it; + return utf8::next(temp, end); + } + + template + void advance (octet_iterator& it, distance_type n, octet_iterator end) + { + for (distance_type i = 0; i < n; ++i) + utf8::next(it, end); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start != end) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start != end) { + uint32_t cp = utf8::next(start, end); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start != end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it, + const octet_iterator& range_start, + const octet_iterator& range_end) : + it(octet_it), range_start(range_start), range_end(range_end) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator == (const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + utf8::next(it, range_end); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator -- () + { + utf8::prior(it, range_start); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#endif //header guard + + diff --git a/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/core.h b/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/core.h new file mode 100644 index 0000000..693d388 --- /dev/null +++ b/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/core.h @@ -0,0 +1,329 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include + +namespace utf8 +{ + // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers + // You may need to change them to match your system. + // These typedefs have the same names as ones from cstdint, or boost/cstdint + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + +// Helper code - not intended to be directly called by the library users. May be changed at any time +namespace internal +{ + // Unicode constants + // Leading (high) surrogates: 0xd800 - 0xdbff + // Trailing (low) surrogates: 0xdc00 - 0xdfff + const uint16_t LEAD_SURROGATE_MIN = 0xd800u; + const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; + const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; + const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; + const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); + const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; + + // Maximum valid value for a Unicode code point + const uint32_t CODE_POINT_MAX = 0x0010ffffu; + + template + inline uint8_t mask8(octet_type oc) + { + return static_cast(0xff & oc); + } + template + inline uint16_t mask16(u16_type oc) + { + return static_cast(0xffff & oc); + } + template + inline bool is_trail(octet_type oc) + { + return ((utf8::internal::mask8(oc) >> 6) == 0x2); + } + + template + inline bool is_lead_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + } + + template + inline bool is_trail_surrogate(u16 cp) + { + return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_code_point_valid(u32 cp) + { + return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); + } + + template + inline typename std::iterator_traits::difference_type + sequence_length(octet_iterator lead_it) + { + uint8_t lead = utf8::internal::mask8(*lead_it); + if (lead < 0x80) + return 1; + else if ((lead >> 5) == 0x6) + return 2; + else if ((lead >> 4) == 0xe) + return 3; + else if ((lead >> 3) == 0x1e) + return 4; + else + return 0; + } + + template + inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) + { + if (cp < 0x80) { + if (length != 1) + return true; + } + else if (cp < 0x800) { + if (length != 2) + return true; + } + else if (cp < 0x10000) { + if (length != 3) + return true; + } + + return false; + } + + enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; + + /// Helper for get_sequence_x + template + utf_error increase_safely(octet_iterator& it, octet_iterator end) + { + if (++it == end) + return NOT_ENOUGH_ROOM; + + if (!utf8::internal::is_trail(*it)) + return INCOMPLETE_SEQUENCE; + + return UTF8_OK; + } + + #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} + + /// get_sequence_x functions decode utf-8 sequences of the length x + template + utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + return UTF8_OK; + } + + template + utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); + + return UTF8_OK; + } + + template + utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + template + utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR + + template + utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + // Save the original value of it so we can go back in case of failure + // Of course, it does not make much sense with i.e. stream iterators + octet_iterator original_it = it; + + uint32_t cp = 0; + // Determine the sequence length based on the lead octet + typedef typename std::iterator_traits::difference_type octet_difference_type; + const octet_difference_type length = utf8::internal::sequence_length(it); + + // Get trail octets and calculate the code point + utf_error err = UTF8_OK; + switch (length) { + case 0: + return INVALID_LEAD; + case 1: + err = utf8::internal::get_sequence_1(it, end, cp); + break; + case 2: + err = utf8::internal::get_sequence_2(it, end, cp); + break; + case 3: + err = utf8::internal::get_sequence_3(it, end, cp); + break; + case 4: + err = utf8::internal::get_sequence_4(it, end, cp); + break; + } + + if (err == UTF8_OK) { + // Decoding succeeded. Now, security checks... + if (utf8::internal::is_code_point_valid(cp)) { + if (!utf8::internal::is_overlong_sequence(cp, length)){ + // Passed! Return here. + code_point = cp; + ++it; + return UTF8_OK; + } + else + err = OVERLONG_SEQUENCE; + } + else + err = INVALID_CODE_POINT; + } + + // Failure branch - restore the original value of the iterator + it = original_it; + return err; + } + + template + inline utf_error validate_next(octet_iterator& it, octet_iterator end) { + uint32_t ignored; + return utf8::internal::validate_next(it, end, ignored); + } + +} // namespace internal + + /// The library API - functions intended to be called by the users + + // Byte order mark + const uint8_t bom[] = {0xef, 0xbb, 0xbf}; + + template + octet_iterator find_invalid(octet_iterator start, octet_iterator end) + { + octet_iterator result = start; + while (result != end) { + utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); + if (err_code != internal::UTF8_OK) + return result; + } + return result; + } + + template + inline bool is_valid(octet_iterator start, octet_iterator end) + { + return (utf8::find_invalid(start, end) == end); + } + + template + inline bool starts_with_bom (octet_iterator it, octet_iterator end) + { + return ( + ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && + ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && + ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) + ); + } + + //Deprecated in release 2.3 + template + inline bool is_bom (octet_iterator it) + { + return ( + (utf8::internal::mask8(*it++)) == bom[0] && + (utf8::internal::mask8(*it++)) == bom[1] && + (utf8::internal::mask8(*it)) == bom[2] + ); + } +} // namespace utf8 + +#endif // header guard + + diff --git a/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/unchecked.h b/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/unchecked.h new file mode 100644 index 0000000..cb24271 --- /dev/null +++ b/.cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/unchecked.h @@ -0,0 +1,228 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" + +namespace utf8 +{ + namespace unchecked + { + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f)| 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + uint32_t next(octet_iterator& it) + { + uint32_t cp = utf8::internal::mask8(*it); + typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); + switch (length) { + case 1: + break; + case 2: + it++; + cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); + break; + case 3: + ++it; + cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + ++it; + cp += (*it) & 0x3f; + break; + case 4: + ++it; + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + ++it; + cp += (utf8::internal::mask8(*it) << 6) & 0xfff; + ++it; + cp += (*it) & 0x3f; + break; + } + ++it; + return cp; + } + + template + uint32_t peek_next(octet_iterator it) + { + return utf8::unchecked::next(it); + } + + template + uint32_t prior(octet_iterator& it) + { + while (utf8::internal::is_trail(*(--it))) ; + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + + // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous) + template + inline uint32_t previous(octet_iterator& it) + { + return utf8::unchecked::prior(it); + } + + template + void advance (octet_iterator& it, distance_type n) + { + for (distance_type i = 0; i < n; ++i) + utf8::unchecked::next(it); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::unchecked::next(first); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + result = utf8::unchecked::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + uint32_t cp = utf8::unchecked::next(start); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::unchecked::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::unchecked::next(start); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it): it(octet_it) {} + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + bool operator == (const iterator& rhs) const + { + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + ::std::advance(it, utf8::internal::sequence_length(it)); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + ::std::advance(it, utf8::internal::sequence_length(it)); + return temp; + } + iterator& operator -- () + { + utf8::unchecked::prior(it); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::unchecked::prior(it); + return temp; + } + }; // class iterator + + } // namespace utf8::unchecked +} // namespace utf8 + + +#endif // header guard + diff --git a/.cs211/lib/ge211/CMakeLists.txt b/.cs211/lib/ge211/CMakeLists.txt new file mode 100644 index 0000000..c3f91bc --- /dev/null +++ b/.cs211/lib/ge211/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.13) +project(ge211 + VERSION 2020.5.3 + DESCRIPTION "A student game engine" + LANGUAGES CXX) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +### +### DEPENDENCIES +### + +# Prefer Homebrew's /usr/local to Valve's /Library/Frameworks: +set(CMAKE_FIND_FRAMEWORK LAST) + +# Don't link in SDL2's main(): +set(SDL2_BUILDING_LIBRARY 1) + +add_subdirectory(3rdparty/utf8-cpp EXCLUDE_FROM_ALL) + +find_package(SDL2 REQUIRED) +find_package(SDL2_image REQUIRED) +find_package(SDL2_mixer REQUIRED) +find_package(SDL2_ttf REQUIRED) + +### +### MAIN LIBRARY SETUP +### + +configure_file(include/ge211_version.hxx.in + include/ge211_version.hxx) +add_subdirectory(src) + +### +### DOCUMENTATION +### + +find_package(Doxygen) +if(DOXYGEN_FOUND) + option(BUILD_DOCUMENTATION + "Create the HTML-based API documentation (requires Doxygen)" + On) + option(DOWNLOAD_STDLIB_TAGS + "Download tags for linking to standard library docs" + On) + set(GIT_PUSH_DOCS_URI + "https://github.com/tov/ge211.git" + CACHE STRING "Repo to push documentation to.") + set(GIT_PUSH_DOCS_BRANCH + "gh-pages" + CACHE STRING "Branch to push documentation to.") + + if(BUILD_DOCUMENTATION) + add_subdirectory(doc) + endif() +endif() + +### +### EXTRAS +### + +# Support library installation +include(Ge211Installer) + +# Example client program +set(GE211_INHERITED 1) +add_subdirectory(example/) diff --git a/.cs211/lib/ge211/Makefile b/.cs211/lib/ge211/Makefile new file mode 100644 index 0000000..a00b157 --- /dev/null +++ b/.cs211/lib/ge211/Makefile @@ -0,0 +1,19 @@ +TAG_FILE = doxygen/cppreference.xml +TAG_URL = http://upload.cppreference.com/mwiki/images/f/f8/cppreference-doxygen-web.tag.xml + +DOXY = doxygen/DoxygenLayout.xml doxygen/FRONTMATTER.md $(TAGFILE) +SRCS = $(wildcard include/*) $(wildcard src/*) + +doc: doxygen/Doxyfile $(DOXY) $(SRCS) + doxygen $< + +doxygen/FRONTMATTER.md: README.md + sed -E '/^\[.*\]: *$$/,/^ *$$/d;s/\[|\]//g' < $< > $@ + +upload-doc: + make doc + ghp-import -n doc/html + git push -f https://github.com/tov/ge211.git gh-pages + +$(TAG_FILE): + curl "$(TAG_URL)" > $@ diff --git a/.cs211/lib/ge211/README.md b/.cs211/lib/ge211/README.md new file mode 100644 index 0000000..2588666 --- /dev/null +++ b/.cs211/lib/ge211/README.md @@ -0,0 +1,71 @@ +# GE211 — a game engine for CS 211 + +GE211 is a relatively simple game engine for beginning C++ programmers. + +## Use + +To use the framework, you need to derive your game class from +[ge211::Abstract_game]; so to get started you may want to go straight +there. Otherwise, all useful definitions are in the [ge211] namespace. + +[ge211::Abstract_game]: + https://tov.github.io/ge211/classge211_1_1_abstract__game.html + +[ge211]: + https://tov.github.io/ge211/namespacege211.html + +## Requirements + +GE211 depends on the SDL library version 2, along with SDL2 plugin +libraries SDL2_image, SDL2_mixer, and SDL2_ttf. You need to install the +development versions of these packages, as appropriate for your +operating system. They are easy to find on Google, but if you are in a +class, your instructor might have an easier way for you to install them. + +## Setup + +If you are using GE211 in a course (such as CS 211 at Northwestern), +your instructor will give you a CMake project that includes files and +configuration for GE211. You shouldn't have to do anything to set it +up. Otherwise, read on. + +GE211 is configured and built using CMake. The easiest way to add the +library to your project is to add the whole repository as a subdirectory, +and then include it in your `CMakeLists.txt` via the `add_subdirectory` +command: + +```CMake +add_subdirectory(3rdparty/ge211 EXCLUDE_FROM_ALL) +``` + +The `EXCLUDE_FROM_ALL` flag prevents extra CMake targets from GE211 +from appearing in your IDE. + +Adding the subdirectory creates a CMake library target that your program +target can be linked against using the `target_link_libraries` command: + +```CMake +target_link_libraries(my_game ge211) +``` + +A minimal, complete `CMakeLists.txt` for using GE211 might look +something like this: + +```CMake +cmake_minimum_required(VERSION 3.13) +project(my_game CXX) + +add_subdirectory(3rdparty/ge211 EXCLUDE_FROM_ALL) + +add_executable(my_game my_game.cxx) +target_link_libraries(my_game ge211) +set_target_properties(my_game PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED On + CXX_EXTENSIONS Off) +``` + +To see this in action, see the [`ge211-vendored-example`] repo. + +[`ge211-vendored-example`]: + https://github.com/tov/ge211-vendored-example diff --git a/.cs211/lib/ge211/Resources/LICENSE.sans.ttf.txt b/.cs211/lib/ge211/Resources/LICENSE.sans.ttf.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/.cs211/lib/ge211/Resources/LICENSE.sans.ttf.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/.cs211/lib/ge211/Resources/pop.ogg b/.cs211/lib/ge211/Resources/pop.ogg new file mode 100644 index 0000000..1ba427d Binary files /dev/null and b/.cs211/lib/ge211/Resources/pop.ogg differ diff --git a/.cs211/lib/ge211/Resources/sans.ttf b/.cs211/lib/ge211/Resources/sans.ttf new file mode 100644 index 0000000..c07abbf Binary files /dev/null and b/.cs211/lib/ge211/Resources/sans.ttf differ diff --git a/.cs211/lib/ge211/TODO.md b/.cs211/lib/ge211/TODO.md new file mode 100644 index 0000000..e85f538 --- /dev/null +++ b/.cs211/lib/ge211/TODO.md @@ -0,0 +1,8 @@ +# To Do + + - Factor `Abstract_game` into `Abstract_view` and `Abstract_controller`. + + - Make int versions of geometry structs friendlier. + + - [Fix your timestep!](https://gafferongames.com/post/fix_your_timestep/) + diff --git a/.cs211/lib/ge211/cmake/FindSDL2.cmake b/.cs211/lib/ge211/cmake/FindSDL2.cmake new file mode 100644 index 0000000..2bc1caa --- /dev/null +++ b/.cs211/lib/ge211/cmake/FindSDL2.cmake @@ -0,0 +1,209 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindSDL2 +# ------- +# +# Locate SDL2 library +# +# This module defines +# +# :: +# +# SDL2_LIBRARY, the name of the library to link against +# SDL2_FOUND, if false, do not try to link to SDL +# SDL2_INCLUDE_DIR, where to find SDL.h +# SDL2_VERSION_STRING, human-readable string containing the version of SDL +# +# +# +# This module responds to the flag: +# +# :: +# +# SDL2_BUILDING_LIBRARY +# If this is defined, then no SDL2_main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the proper link flags +# as part of the returned SDL2_LIBRARY variable. +# +# +# +# Don't forget to include SDLmain.h and SDLmain.m your project for the +# OS X framework based version. (Other versions link to -lSDLmain which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# +# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your +# configuration and no SDL2_LIBRARY, it means CMake did not find your SDL +# library (SDL.dll, libsdl.so, SDL.framework, etc). Set +# SDL2_LIBRARY_TEMP to point to your SDL library, and configure again. +# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this +# value as appropriate. These values are used to generate the final +# SDL2_LIBRARY variable, but when these values are unset, SDL2_LIBRARY +# does not get created. +# +# +# +# $SDLDIR is an environment variable that would correspond to the +# ./configure --prefix=$SDLDIR used in building SDL. l.e.galup 9-20-02 +# +# Modified by Eric Wing. Added code to assist with automated building +# by using environmental variables and providing a more +# controlled/consistent search behavior. Added new modifications to +# recognize OS X frameworks and additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL +# guidelines. Added a search for SDLmain which is needed by some +# platforms. Added a search for threads which is needed by some +# platforms. Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of SDL2_LIBRARY to +# override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL/SDL.h to just SDL.h +# This needed to change because "proper" SDL convention is #include +# "SDL.h", not . This is done for portability reasons +# because not all systems place things in SDL/ (see FreeBSD). + +include(FindPackageHandleStandardArgs) + +if(NOT SDL2_DIR) + set(SDL2_DIR "" CACHE PATH "SDL2 directory") +endif() + +find_path(SDL2_INCLUDE_DIR + NAMES SDL.h + HINTS + ENV SDLDIR + ${SDL2_DIR} + # path suffixes to search inside above. + PATH_SUFFIXES + SDL2 + include/SDL2 + include) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(VC_LIB_PATH_SUFFIX lib/x64) +else() + set(VC_LIB_PATH_SUFFIX lib/x86) +endif() + +# SDL-1.1 is the name used by FreeBSD ports... +# don't confuse it for the version number. +find_library(SDL2_LIBRARY_TEMP + NAMES SDL2 + HINTS + ENV SDLDIR + ${SDL2_DIR} + PATH_SUFFIXES + lib + ${VC_LIB_PATH_SUFFIX}) + +# Hide this cache variable from the user, it's an internal implementation +# detail. The documented library variable for the user is SDL2_LIBRARY +# which is derived from SDL2_LIBRARY_TEMP further below. +set_property(CACHE SDL2_LIBRARY_TEMP PROPERTY TYPE INTERNAL) + +if(NOT SDL2_BUILDING_LIBRARY) + if(NOT SDL2_INCLUDE_DIR MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDLmain for compatibility even though they don't + # necessarily need it. + find_library(SDL2MAIN_LIBRARY + NAMES SDL2main + HINTS + ENV SDLDIR + ${SDL2_DIR} + PATH_SUFFIXES + lib + ${VC_LIB_PATH_SUFFIX} + PATHS + /sw + /opt/local + /opt/csw + /opt) + endif() +endif() + +# SDL may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +if(NOT APPLE) + find_package(Threads) +endif() + +# MinGW needs an additional link flag, -mwindows +# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -mwindows +if(MINGW) + set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW") +endif() + +if(SDL2_LIBRARY_TEMP) + # For SDLmain + if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY) + list(FIND SDL2_LIBRARY_TEMP "${SDL2MAIN_LIBRARY}" _SDL2_MAIN_INDEX) + if(_SDL2_MAIN_INDEX EQUAL -1) + set(SDL2_LIBRARY_TEMP "${SDL2MAIN_LIBRARY}" ${SDL2_LIBRARY_TEMP}) + endif() + unset(_SDL2_MAIN_INDEX) + endif() + + # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + if(APPLE) + set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") + endif() + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + if(NOT APPLE) + set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + endif() + + # For MinGW library + if(MINGW) + set(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) + endif() + + # Set the final string here so the GUI reflects the final state. + set(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") +endif() + +if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL2_version.h") + file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+[0-9]+$") + string(REGEX REPLACE "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}") + set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}) + unset(SDL2_VERSION_MAJOR_LINE) + unset(SDL2_VERSION_MINOR_LINE) + unset(SDL2_VERSION_PATCH_LINE) + unset(SDL2_VERSION_MAJOR) + unset(SDL2_VERSION_MINOR) + unset(SDL2_VERSION_PATCH) +endif() + +set(SDL2_LIBRARIES ${SDL2_LIBRARY}) +set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR}) + +find_package_handle_standard_args(SDL2 + FOUND_VAR SDL2_FOUND + REQUIRED_VARS SDL2_LIBRARIES SDL2_INCLUDE_DIRS + VERSION_VAR SDL2_VERSION_STRING) + +mark_as_advanced(SDL2_LIBRARY SDL2_INCLUDE_DIR) diff --git a/.cs211/lib/ge211/cmake/FindSDL2_image.cmake b/.cs211/lib/ge211/cmake/FindSDL2_image.cmake new file mode 100644 index 0000000..755d026 --- /dev/null +++ b/.cs211/lib/ge211/cmake/FindSDL2_image.cmake @@ -0,0 +1,86 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindSDL2_image +# ------------- +# +# Locate SDL2_image library +# +# This module defines: +# +# :: +# +# SDL2_IMAGE_LIBRARIES, the name of the library to link against +# SDL2_IMAGE_INCLUDE_DIRS, where to find the headers +# SDL2_IMAGE_FOUND, if false, do not try to link against +# SDL2_IMAGE_VERSION_STRING - human-readable string containing the +# version of SDL2_image +# +# +# +# +# +# $SDL2DIR is an environment variable that would correspond to the +# ./configure --prefix=$SDL2DIR used in building SDL. +# +# Created by Eric Wing. This was influenced by the FindSDL.cmake +# module, but with modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). + +include(FindPackageHandleStandardArgs) + +find_path(SDL2_IMAGE_INCLUDE_DIR + NAMES SDL_image.h + HINTS + ENV SDL2IMAGEDIR + ENV SDL2DIR + ${SDL2_DIR} + # path suffixes to search inside ENV{SDL2DIR} + PATH_SUFFIXES + SDL2 + include/SDL2 + include) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(VC_LIB_PATH_SUFFIX lib/x64) +else() + set(VC_LIB_PATH_SUFFIX lib/x86) +endif() + +find_library(SDL2_IMAGE_LIBRARY + NAMES SDL2_image + HINTS + ENV SDL2IMAGEDIR + ENV SDL2DIR + ${SDL2_DIR} + PATH_SUFFIXES + lib + ${VC_LIB_PATH_SUFFIX}) + +if(SDL2_IMAGE_INCLUDE_DIR AND EXISTS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h") + file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_IMAGE_MAJOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_IMAGE_MINOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_IMAGE_PATCHLEVEL[ \t]+[0-9]+$") + string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MAJOR "${SDL_IMAGE_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MINOR "${SDL_IMAGE_VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_PATCH "${SDL_IMAGE_VERSION_PATCH_LINE}") + set(SDL2_IMAGE_VERSION_STRING ${SDL2_IMAGE_VERSION_MAJOR}.${SDL2_IMAGE_VERSION_MINOR}.${SDL2_IMAGE_VERSION_PATCH}) + unset(SDL2_IMAGE_VERSION_MAJOR_LINE) + unset(SDL2_IMAGE_VERSION_MINOR_LINE) + unset(SDL2_IMAGE_VERSION_PATCH_LINE) + unset(SDL2_IMAGE_VERSION_MAJOR) + unset(SDL2_IMAGE_VERSION_MINOR) + unset(SDL2_IMAGE_VERSION_PATCH) +endif() + +set(SDL2_IMAGE_LIBRARIES ${SDL2_IMAGE_LIBRARY}) +set(SDL2_IMAGE_INCLUDE_DIRS ${SDL2_IMAGE_INCLUDE_DIR}) + +find_package_handle_standard_args(SDL2_image + FOUND_VAR SDL2_IMAGE_FOUND + REQUIRED_VARS SDL2_IMAGE_LIBRARIES SDL2_IMAGE_INCLUDE_DIRS + VERSION_VAR SDL2_IMAGE_VERSION_STRING) + +mark_as_advanced(SDL2_IMAGE_LIBRARY SDL2_IMAGE_INCLUDE_DIR) + diff --git a/.cs211/lib/ge211/cmake/FindSDL2_mixer.cmake b/.cs211/lib/ge211/cmake/FindSDL2_mixer.cmake new file mode 100644 index 0000000..02d805e --- /dev/null +++ b/.cs211/lib/ge211/cmake/FindSDL2_mixer.cmake @@ -0,0 +1,87 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindSDL2_mixer +# ------------- +# +# Locate SDL2_mixer library +# +# This module defines: +# +# :: +# +# SDL2_MIXER_LIBRARIES, the name of the library to link against +# SDL2_MIXER_INCLUDE_DIRS, where to find the headers +# SDL2_MIXER_FOUND, if false, do not try to link against +# SDL2_MIXER_VERSION_STRING - human-readable string containing the +# version of SDL2_mixer +# +# +# +# +# $SDL2DIR is an environment variable that would correspond to the +# ./configure --prefix=$SDL2DIR used in building SDL. +# +# Created by Eric Wing. This was influenced by the FindSDL.cmake +# module, but with modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# +# Mindlessly adapted from FindSDL2_ttf.cmake by Jesse Tov. + +include(FindPackageHandleStandardArgs) + +find_path(SDL2_MIXER_INCLUDE_DIR + NAMES SDL_mixer.h + HINTS + ENV SDL2MIXERDIR + ENV SDL2DIR + ${SDL2_DIR} + # path suffixes to search inside ENV{SDL2DIR} + PATH_SUFFIXES + SDL2 + include/SDL2 + include) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(VC_LIB_PATH_SUFFIX lib/x64) +else() + set(VC_LIB_PATH_SUFFIX lib/x86) +endif() + +find_library(SDL2_MIXER_LIBRARY + NAMES SDL2_mixer + HINTS + ENV SDL2MIXERDIR + ENV SDL2DIR + ${SDL2_DIR} + PATH_SUFFIXES + lib + ${VC_LIB_PATH_SUFFIX}) + +if(SDL2_MIXER_INCLUDE_DIR AND EXISTS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h") + file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+[0-9]+$") + string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MAJOR "${SDL_MIXER_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MINOR "${SDL_MIXER_VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_PATCH "${SDL_MIXER_VERSION_PATCH_LINE}") + set(SDL2_MIXER_VERSION_STRING ${SDL2_MIXER_VERSION_MAJOR}.${SDL2_MIXER_VERSION_MINOR}.${SDL2_MIXER_VERSION_PATCH}) + unset(SDL2_MIXER_VERSION_MAJOR_LINE) + unset(SDL2_MIXER_VERSION_MINOR_LINE) + unset(SDL2_MIXER_VERSION_PATCH_LINE) + unset(SDL2_MIXER_VERSION_MAJOR) + unset(SDL2_MIXER_VERSION_MINOR) + unset(SDL2_MIXER_VERSION_PATCH) +endif() + +set(SDL2_MIXER_LIBRARIES ${SDL2_MIXER_LIBRARY}) +set(SDL2_MIXER_INCLUDE_DIRS ${SDL2_MIXER_INCLUDE_DIR}) + +find_package_handle_standard_args(SDL2_mixer + FOUND_VAR SDL2_MIXER_FOUND + REQUIRED_VARS SDL2_MIXER_LIBRARIES SDL2_MIXER_INCLUDE_DIRS + VERSION_VAR SDL2_MIXER_VERSION_STRING) + +mark_as_advanced(SDL2_MIXER_LIBRARY SDL2_MIXER_INCLUDE_DIR) + diff --git a/.cs211/lib/ge211/cmake/FindSDL2_ttf.cmake b/.cs211/lib/ge211/cmake/FindSDL2_ttf.cmake new file mode 100644 index 0000000..104f0c7 --- /dev/null +++ b/.cs211/lib/ge211/cmake/FindSDL2_ttf.cmake @@ -0,0 +1,85 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindSDL2_ttf +# ------------- +# +# Locate SDL2_ttf library +# +# This module defines: +# +# :: +# +# SDL2_TTF_LIBRARIES, the name of the library to link against +# SDL2_TTF_INCLUDE_DIRS, where to find the headers +# SDL2_TTF_FOUND, if false, do not try to link against +# SDL2_TTF_VERSION_STRING - human-readable string containing the +# version of SDL2_ttf +# +# +# +# +# $SDL2DIR is an environment variable that would correspond to the +# ./configure --prefix=$SDL2DIR used in building SDL. +# +# Created by Eric Wing. This was influenced by the FindSDL.cmake +# module, but with modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). + +include(FindPackageHandleStandardArgs) + +find_path(SDL2_TTF_INCLUDE_DIR + NAMES SDL_ttf.h + HINTS + ENV SDL2TTFDIR + ENV SDL2DIR + ${SDL2_DIR} + # path suffixes to search inside the above + PATH_SUFFIXES + SDL2 + include/SDL2 + include) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(VC_LIB_PATH_SUFFIX lib/x64) +else() + set(VC_LIB_PATH_SUFFIX lib/x86) +endif() + +find_library(SDL2_TTF_LIBRARY + NAMES SDL2_ttf + HINTS + ENV SDL2TTFDIR + ENV SDL2DIR + ${SDL2_DIR} + PATH_SUFFIXES + lib + ${VC_LIB_PATH_SUFFIX}) + +if(SDL2_TTF_INCLUDE_DIR AND EXISTS "${SDL2_TTF_INCLUDE_DIR}/SDL_ttf.h") + file(STRINGS "${SDL2_TTF_INCLUDE_DIR}/SDL_ttf.h" SDL2_TTF_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_TTF_MAJOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_TTF_INCLUDE_DIR}/SDL_ttf.h" SDL2_TTF_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_TTF_MINOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_TTF_INCLUDE_DIR}/SDL_ttf.h" SDL2_TTF_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_TTF_PATCHLEVEL[ \t]+[0-9]+$") + string(REGEX REPLACE "^#define[ \t]+SDL_TTF_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_TTF_VERSION_MAJOR "${SDL_TTF_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_TTF_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_TTF_VERSION_MINOR "${SDL_TTF_VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_TTF_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_TTF_VERSION_PATCH "${SDL_TTF_VERSION_PATCH_LINE}") + set(SDL2_TTF_VERSION_STRING ${SDL2_TTF_VERSION_MAJOR}.${SDL2_TTF_VERSION_MINOR}.${SDL2_TTF_VERSION_PATCH}) + unset(SDL2_TTF_VERSION_MAJOR_LINE) + unset(SDL2_TTF_VERSION_MINOR_LINE) + unset(SDL2_TTF_VERSION_PATCH_LINE) + unset(SDL2_TTF_VERSION_MAJOR) + unset(SDL2_TTF_VERSION_MINOR) + unset(SDL2_TTF_VERSION_PATCH) +endif() + +set(SDL2_TTF_LIBRARIES ${SDL2_TTF_LIBRARY}) +set(SDL2_TTF_INCLUDE_DIRS ${SDL2_TTF_INCLUDE_DIR}) + +find_package_handle_standard_args(SDL2_ttf + FOUND_VAR SDL2_TTF_FOUND + REQUIRED_VARS SDL2_TTF_LIBRARIES SDL2_TTF_INCLUDE_DIRS + VERSION_VAR SDL2_TTF_VERSION_STRING) + +mark_as_advanced(SDL2_TTF_LIBRARY SDL2_TTF_INCLUDE_DIR) + diff --git a/.cs211/lib/ge211/cmake/Ge211ClientInstaller.cmake b/.cs211/lib/ge211/cmake/Ge211ClientInstaller.cmake new file mode 100644 index 0000000..06e1c61 --- /dev/null +++ b/.cs211/lib/ge211/cmake/Ge211ClientInstaller.cmake @@ -0,0 +1,122 @@ +if(WIN32) + set(MINGW_DIR "${SDL2_IMAGE_INCLUDE_DIR}/../.." + CACHE PATH "Where MinGW-w64 is installed") +endif(WIN32) + +define_property(GLOBAL PROPERTY GE211_CLIENT_NAME + BRIEF_DOCS "Name of the GE211 client installer" + FULL_DOCS "Name of the GE211 client installer") + + +# Given a base path to search in, a file extension, and a list of library +# names, searches for the libraries (using a glob) and saves the resulting +# list of paths in ${output_var}. +function(glob_libs output_var base_path ext) + set(acc "") + + foreach(lib ${ARGN}) + file(GLOB next "${base_path}/lib${lib}*${ext}") + list(APPEND acc "${next}") + endforeach() + + set(${output_var} "${acc}" PARENT_SCOPE) +endfunction(glob_libs) + + +# Precondition: GLOBAL PROPERTY CPACK_PACKAGE_NAME is set. +macro(_set_ge211_installer_vars) + get_property(CPACK_PACKAGE_NAME GLOBAL PROPERTY GE211_CLIENT_NAME) + if(NOT CPACK_PACKAGE_NAME) + message(FATAL_ERROR "ge211_installer_add: oops.") + endif() + + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-dist") + set(CPACK_STRIP_FILES FALSE) + + if(APPLE) + set(CPACK_GENERATOR DragNDrop) + set(CLIENT_INSTALLER "${CPACK_PACKAGE_FILE_NAME}.dmg") + elseif(WIN32) + set(CPACK_GENERATOR ZIP) + set(CLIENT_INSTALLER "${CPACK_PACKAGE_FILE_NAME}.zip") + else() + message(FATAL_ERROR + "Creating an installer is not supported on your platform") + endif() +endmacro(_set_ge211_installer_vars) + + +function(_initialize_ge211_installer) + cmake_parse_arguments(pa "" "NAME" "" ${ARGN}) + + get_property(already_initialized GLOBAL PROPERTY GE211_CLIENT_NAME SET) + if(already_initialized) + get_property(old_name GLOBAL PROPERTY GE211_CLIENT_NAME) + if(pa_NAME AND NOT "${pa_NAME}" STREQUAL "${old_name}") + message(WARNING "Cannot name installer ‘${pa_NAME}’" + " because there is already an installer named" + " ‘${old_name}’.") + endif() + return() + endif() + + if(NOT pa_NAME) + set(pa_NAME "${CMAKE_PROJECT_NAME}") + endif() + set_property(GLOBAL PROPERTY GE211_CLIENT_NAME "${pa_NAME}") + + _set_ge211_installer_vars() + + add_custom_target("${CLIENT_INSTALLER}") + add_custom_command(TARGET "${CLIENT_INSTALLER}" + COMMAND "${CMAKE_CPACK_COMMAND}" + COMMENT "Running CPack, please wait...") + + install(CODE "include(BundleUtilities)") + + if(WIN32) + glob_libs(Plugins "${MINGW_DIR}/bin" .dll jpeg png tiff webp) + install(FILES ${Plugins} DESTINATION bin) + endif() +endfunction(_initialize_ge211_installer) + + +# Creates a target for a platform-dependent installer for an executable +# (given by the name of its target) and some resource files. +function(ge211_installer_name name) + _initialize_ge211_installer(NAME "${name}") +endfunction(ge211_installer_name) + + +# Sets up the given target for installation along with the given +# resource files. +function(ge211_installer_add target) + _initialize_ge211_installer() + _set_ge211_installer_vars() + add_dependencies(${CLIENT_INSTALLER} ${target}) + set(CPACK_PACKAGE_EXECUTABLES "${target}" "${target}") + include(CPack) + + target_sources(${target} PUBLIC ${ARGN}) + + if(APPLE) + set_target_properties(${target} PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_FRAMEWORK_IDENTIFIER edu.northwestern.cs.jesse.cs211 + RESOURCE "${ARGN}") + set(app "${CMAKE_BINARY_DIR}/${target}.app") + install(CODE "set(BU_CHMOD_BUNDLE_ITEMS TRUE) + fixup_bundle(\"${app}\" \"\" \"\")") + install(TARGETS ${target} + BUNDLE DESTINATION .) + elseif(WIN32) + set(exe "\${CMAKE_INSTALL_PREFIX}/bin/${target}.exe") + set(bindir "${MINGW_DIR}/bin") + install(TARGETS ${target} + RUNTIME DESTINATION bin + RESOURCES DESTINATION Resources) + install(CODE "list(APPEND gp_cmd_paths \"${bindir}\") + fixup_bundle(\"${exe}\" \"\" \"\")") + #? fixup_bundle(\"${exe}\" \"\" \"${bindir}\")") + endif() +endfunction(ge211_installer_add) diff --git a/.cs211/lib/ge211/cmake/Ge211Installer.cmake b/.cs211/lib/ge211/cmake/Ge211Installer.cmake new file mode 100644 index 0000000..8cb00c3 --- /dev/null +++ b/.cs211/lib/ge211/cmake/Ge211Installer.cmake @@ -0,0 +1,36 @@ +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +set(config_version_cmake + ${CMAKE_CURRENT_BINARY_DIR}/Ge211/Ge211ConfigVersion.cmake) +set(config_install_dir + ${CMAKE_INSTALL_DATADIR}/cmake/Ge211) + +install(TARGETS ge211 + utf8-cpp + EXPORT Ge211Config + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(DIRECTORY include/ + ${CMAKE_CURRENT_BINARY_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.hxx") + +install(DIRECTORY Resources/ + DESTINATION ${CMAKE_INSTALL_DATADIR}/ge211) + +install(EXPORT Ge211Config + DESTINATION ${config_install_dir}) + +install(FILES ${config_version_cmake} + DESTINATION ${config_install_dir}) + +export( TARGETS ge211 + FILE Ge211Config.cmake) + +write_basic_package_version_file(${config_version_cmake} + VERSION ${CMAKE_PROJECT_VERSION} + COMPATIBILITY SameMinorVersion) + diff --git a/.cs211/lib/ge211/doc/CMakeLists.txt b/.cs211/lib/ge211/doc/CMakeLists.txt new file mode 100644 index 0000000..fff7414 --- /dev/null +++ b/.cs211/lib/ge211/doc/CMakeLists.txt @@ -0,0 +1,61 @@ +set(STDLIB_DOC_ROOT "http://en.cppreference.com/w/") +set(STDLIB_TAG_FILE "cppreference.xml") +set(STDLIB_TAG_URL "http://upload.cppreference.com/mwiki/images/f/f8/cppreference-doxygen-web.tag.xml") + +set(readme_md ${CMAKE_CURRENT_SOURCE_DIR}/../README.md) +set(frontmatter_md ${CMAKE_CURRENT_BINARY_DIR}/FRONTMATTER.md) + +set(DOXYGEN_OUTPUT_DIRECTORY doc) +set(DOXYGEN_JAVADOC_AUTOBRIEF YES) +set(DOXYGEN_ALIASES [=[preconditions=#### Preconditions: ]=]) +set(DOXYGEN_HIDE_FRIEND_COMPOUNDS YES) +set(DOXYGEN_HIDE_SCOPE_NAMES YES) +set(DOXYGEN_SHOW_INCLUDE_FILES NO) +set(DOXYGEN_LAYOUT_FILE DoxygenLayout.xml) +set(DOXYGEN_EXCLUDE_SYMBOLS detail) +set(DOXYGEN_USE_MDFILE_AS_MAINPAGE ${frontmatter_md}) +set(DOXYGEN_SOURCE_BROWSER YES) +set(DOXYGEN_HTML_DYNAMIC_SECTIONS YES) +set(DOXYGEN_DISABLE_INDEX YES) +set(DOXYGEN_GENERATE_TREEVIEW YES) +set(DOXYGEN_ENABLE_PREPROCESSING YES) +set(DOXYGEN_PREDEFINED NO_NOEXCEPT) +set(DOXYGEN_MACRO_EXPANSION YES) +set(DOXYGEN_EXPAND_ONLY_PREDEF YES) +set(DOXYGEN_EXPAND_AS_DEFINED NOEXCEPT NOEXCEPT_) + +SET(sed_command sed -E "/^\\[.*\\]: *$$/,/^ *$$/d\;s/[][]//g") +execute_process(COMMAND ${sed_command} + INPUT_FILE ${readme_md} + OUTPUT_FILE ${frontmatter_md}) +file(GLOB_RECURSE ge211_srcs + LIST_DIRECTORIES false + ../include/*.hxx + ../src/*.cxx) +doxygen_add_docs(Build-GE211-Docs + ${ge211_srcs} + ${frontmatter_md} + USE_STAMP_FILE) + +if (DOWNLOAD_STDLIB_TAGS) + include(ExternalProject) + ExternalProject_Add(Fetch-StdLib-Tags + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + URL ${STDLIB_TAG_URL} + DOWNLOAD_NAME ${STDLIB_TAG_FILE} + DOWNLOAD_NO_EXTRACT True + TIMEOUT 10 + DOWNLOAD_NO_PROGRESS True + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "") + set(DOXYGEN_TAGFILES + "${STDLIB_TAG_FILE}=${CMAKE_CURRENT_BINARY_DIR}/${STDLIB_TAGFILE}") + add_dependencies(Build-GE211-Docs Fetch-StdLib-Tags) +endif() + +add_custom_target(Upload-GE211-Docs + COMMAND ghp-import -n doc/html + COMMAND git push --force ${GIT_PUSH_DOCS_URI} ${GIT_PUSH_DOCS_BRANCH} + COMMENT "Publishing to GitHub Pages") +add_dependencies(Upload-GE211-Docs Build-GE211-Docs) diff --git a/.cs211/lib/ge211/doc/DoxygenLayout.xml b/.cs211/lib/ge211/doc/DoxygenLayout.xml new file mode 100644 index 0000000..0f1881c --- /dev/null +++ b/.cs211/lib/ge211/doc/DoxygenLayout.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.cs211/lib/ge211/example/CMakeLists.txt b/.cs211/lib/ge211/example/CMakeLists.txt new file mode 100644 index 0000000..dd9fe81 --- /dev/null +++ b/.cs211/lib/ge211/example/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.13) +project(ge211_examples CXX) + +if(NOT GE211_INHERITED) + find_package(Ge211 2020.5 REQUIRED) +endif() + +add_executable(fireworks fireworks.cxx) +set_target_properties(fireworks PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED On + CXX_EXTENSIONS Off) +target_link_libraries(fireworks ge211) + +if(MSVC) + target_compile_options(fireworks PRIVATE /W4) +else(MSVC) + target_compile_options(fireworks PRIVATE -Wall -pedantic-errors) +endif(MSVC) + +include(GNUInstallDirs) +install(TARGETS fireworks + RUNTIME DESTINATION bin) diff --git a/.cs211/lib/ge211/example/fireworks.cxx b/.cs211/lib/ge211/example/fireworks.cxx new file mode 100644 index 0000000..2f78f28 --- /dev/null +++ b/.cs211/lib/ge211/example/fireworks.cxx @@ -0,0 +1,326 @@ +#include + +#include +#include +#include +#include + +using namespace ge211; +using namespace std; + +// MODEL CONSTANTS + +Dimensions const scene_dimensions{1024, 768}; +Basic_dimensions const gravity_acceleration{0, 120}; // px/s^2 +int const min_launch_speed{350}; // px/s +int const max_launch_speed{500}; // px/s +int const max_launch_angle{30}; // degrees from vertical +double const fuse_seconds{2}; +int const min_stars{40}; +int const max_stars{400}; +int const min_star_speed{10}; // px/s +int const max_star_speed{100}; // px/s +double const burn_seconds{2}; +int const number_of_colors{12}; + +// VIEW CONSTANTS + +int const mortar_radius = 5; +Color const mortar_color{255, 255, 127, 80}; +int const star_radius = 2; + +// MODEL DATA DEFINITIONS + +struct Projectile +{ + using Position = Basic_position; + using Velocity = Basic_dimensions; + + Position position; + Velocity velocity; + + void update(double dt); + + /// Creates a Projectile with the given Position and a random velocity + /// within the given speed range and angle range. + static Projectile random( + Random&, + Position, + double min_speed, double max_speed, + double min_degrees, double max_degrees); +}; + +struct Firework +{ + enum class Stage { mortar, stars, done }; + + Stage stage; + Projectile mortar; + vector stars; + int star_color; + double stage_time; + + unsigned update(double dt); + static Firework random(Random&, Projectile::Position); +}; + +struct Model +{ + vector fireworks; + + unsigned update(double dt); + void add_random(Random&, Projectile::Position); +}; + +// VIEW DATA DEFINITIONS + +struct View +{ + View(Mixer&); + + Font sans{"sans.ttf", 30}; + Text_sprite fps; + Text_sprite load; + Circle_sprite mortar{mortar_radius, mortar_color}; + vector stars; + Sound_effect pop; +}; + +// MAIN STRUCT AND FUNCTION + +struct Fireworks : Abstract_game +{ + // Constructor + Fireworks(); + + // Model + Model model; + + // View + View view; + Dimensions initial_window_dimensions() const override; + void draw(Sprite_set& sprites) override; + void draw_fireworks(Sprite_set& sprites) const; + void draw_stats(Sprite_set& sprites); + + // Controller + bool is_paused = false; + void on_key(Key key) override; + void on_mouse_up(Mouse_button button, Position position) override; + void on_frame(double dt) override; +}; + +int main() +{ + Fireworks{}.run(); +} + +// FUNCTION DEFINITIONS FOR MODEL + +void Projectile::update(double dt) +{ + position += velocity * dt; + velocity += gravity_acceleration * dt; +} + +Projectile +Projectile::random(Random& rng, Position position, + double min_speed, double max_speed, + double min_degrees, double max_degrees) +{ + double speed = rng.between(min_speed, max_speed); + double radians = M_PI / 180 * rng.between(min_degrees, max_degrees); + return {position, {speed * cos(radians), speed * sin(radians)}}; +} + +unsigned Firework::update(double dt) +{ + unsigned explosions = 0; + + switch (stage) { + case Stage::mortar: + if ((stage_time -= dt) <= 0) { + for (Projectile& star : stars) { + star.position = mortar.position; + star.velocity += mortar.velocity; + } + stage_time = burn_seconds; + stage = Stage::stars; + ++explosions; + } else { + mortar.update(dt); + } + break; + + case Stage::stars: + if ((stage_time -= dt) <= 0) { + stage = Stage::done; + } else { + for (Projectile& star : stars) { + star.update(dt); + } + } + break; + + case Stage::done: + break; + } + + return explosions; +} + +Firework Firework::random(Random& rng, Projectile::Position p0) +{ + Projectile mortar = Projectile::random(rng, p0, + min_launch_speed, max_launch_speed, + -90 - max_launch_angle, + -90 + max_launch_angle); + + vector stars; + + int star_count = rng.between(min_stars, max_stars); + for (int i = 0; i < star_count; ++i) { + Projectile star = Projectile::random(rng, {0, 0}, + min_star_speed, max_star_speed, + 0, 360); + stars.push_back(star); + } + + int star_color = rng.up_to(number_of_colors); + + return Firework{Stage::mortar, mortar, stars, star_color, fuse_seconds}; +} + +unsigned Model::update(double dt) +{ + unsigned explosions = 0; + + for (Firework& firework : fireworks) + explosions += firework.update(dt); + + size_t i = 0; + while (i < fireworks.size()) { + if (fireworks[i].stage == Firework::Stage::done) { + fireworks[i] = move(fireworks.back()); + fireworks.pop_back(); + } else { + ++i; + } + } + + return explosions; +} + +void Model::add_random(Random& rng, Projectile::Position position0) +{ + fireworks.push_back(Firework::random(rng, position0)); +} + +// FUNCTION DEFINITIONS FOR VIEW + +View::View(Mixer& mixer) +{ + double hue = 1.0; + double dhue = 360.0 / number_of_colors; + + for (int i = 0; i < number_of_colors; ++i) { + stars.emplace_back(star_radius, Color::from_hsla(hue, .75, .75, .75)); + hue += dhue; + } + + pop.try_load("pop.ogg", mixer); +} + +Dimensions Fireworks::initial_window_dimensions() const +{ + return scene_dimensions; +} + +void Fireworks::draw(Sprite_set& sprites) +{ + draw_fireworks(sprites); + draw_stats(sprites); +} + +void Fireworks::draw_fireworks(Sprite_set& sprites) const +{ + for (Firework const& firework : model.fireworks) { + switch (firework.stage) { + case Firework::Stage::mortar: + sprites.add_sprite(view.mortar, + firework.mortar.position.into()); + break; + + case Firework::Stage::stars: + for (Projectile const& star : firework.stars) { + sprites.add_sprite(view.stars[firework.star_color], + star.position.into()); + } + break; + + // Shouldn't ever happen: + case Firework::Stage::done: + break; + } + } +} + +void Fireworks::draw_stats(Sprite_set& sprites) +{ + Dimensions const margin {20, 10}; + + view.fps.reconfigure(Text_sprite::Builder(view.sans) + << setprecision(3) + << get_frame_rate()); + view.load.reconfigure(Text_sprite::Builder(view.sans) + << setprecision(0) << fixed + << get_load_percent() << '%'); + + auto fps_posn = Position{margin}; + sprites.add_sprite(view.fps, fps_posn); + + auto load_posn = Position{scene_dimensions.width, 0} + .down_left_by(margin) + .left_by(view.load.dimensions().width); + sprites.add_sprite(view.load, load_posn); +} + +// CONSTRUCTING THE GAME OBJECT + +Fireworks::Fireworks() + : view(mixer()) +{ } + +// FUNCTION DEFINITIONS FOR CONTROLLER + +void Fireworks::on_key(Key key) +{ + if (key == Key::code('q')) { + quit(); + } else if (key == Key::code('f')) { + get_window().set_fullscreen(!get_window().get_fullscreen()); + } else if (key == Key::code('p')) { + is_paused = !is_paused; + } else if (key == Key::code(' ') && !is_paused) { + auto dims = get_window().get_dimensions(); + auto initial_position = Position{dims.width / 2, dims.height}; + model.add_random(get_random(), initial_position.into()); + } +} + +void Fireworks::on_frame(double dt) +{ + if (is_paused) return; + + unsigned explosion_count = model.update(dt); + + if (view.pop) + while (explosion_count--) + mixer().play_effect(view.pop); +} + +void Fireworks::on_mouse_up(Mouse_button, Position position) +{ + if (!is_paused) + model.add_random(get_random(), position.into()); +} diff --git a/.cs211/lib/ge211/include/ge211.hxx b/.cs211/lib/ge211/include/ge211.hxx new file mode 100644 index 0000000..b426d73 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211.hxx @@ -0,0 +1,18 @@ +#pragma once + +/// The game engine namespace. +namespace ge211 {} + +#include "ge211_base.hxx" +#include "ge211_color.hxx" +#include "ge211_error.hxx" +#include "ge211_event.hxx" +#include "ge211_geometry.hxx" +#include "ge211_audio.hxx" +#include "ge211_resource.hxx" +#include "ge211_random.hxx" +#include "ge211_sprites.hxx" +#include "ge211_time.hxx" +#include "ge211_util.hxx" +#include "ge211_version.hxx" +#include "ge211_window.hxx" diff --git a/.cs211/lib/ge211/include/ge211_audio.hxx b/.cs211/lib/ge211/include/ge211_audio.hxx new file mode 100644 index 0000000..01007bb --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_audio.hxx @@ -0,0 +1,474 @@ +#pragma once + +#include "ge211_forward.hxx" +#include "ge211_error.hxx" +#include "ge211_time.hxx" +#include "ge211_util.hxx" + +#include +#include + +namespace ge211 { + +/// Audio facilities, for playing music and sound effects. +/// +/// All audio facilities are accessed via the Mixer, which is in turn +/// accessed via the Abstract_game::mixer() const member function of +/// Abstract_game. If the Mixer is enabled (and it may not be), then it +/// can be used to load audio files as Music_track%s and Sound_effect%s. +/// The former is for playing continuous background music, whereas the +/// latter is for adding sound effects. See the Mixer class documentation +/// for more. +namespace audio { + +/// Abstract base class for classes that load audio data, Music_track and +/// Sound_effect. +class Audio_clip +{ +public: + /// Returns true if this audio clip is empty. + bool empty() const { return real_empty(); } + + /// Recognizes a non-empty audio clip. + /// Equivalent to `!empty()`. + operator bool() const { return !real_empty(); } + + /// Loads audio from a resource file into this audio clip instance. + /// + /// Throws exceptions::File_error if the file cannot be opened, and + /// exceptions::Mixer_error if the file format cannot be understood. + void load(const std::string&, const Mixer&); + + /// Attempts to load audio from a resource file into this Audio_clip + /// instance. Returns `true` if loading succeeds, or `false` if + /// the file format cannot be understood. + /// + /// Throws exceptions::File_error if the file cannot be opened. + bool try_load(const std::string&, const Mixer&); + + /// Unloads any audio data, leaving this Audio_clip empty. + void clear(); + + virtual ~Audio_clip() = default; + +protected: + Audio_clip(); + + /// Derived classes must override this to provide an implementation + /// for Audio_clip::try_load(const std::string&, const Mixer&). + virtual bool real_try_load(const std::string& filename, const Mixer&) = 0; + + /// Derived classes must override this to provide an implementation + /// for Audio_clip::clear(). + virtual void real_clear() = 0; + + /// Derived classes must override this to provide an implementation + /// for Audio_clip::empty(). + virtual bool real_empty() const = 0; +}; + +/// A music track, which can be attached to the Mixer and played. +/// A music track may be *empty* or *non-empty*; only non-empty tracks can +/// actually be played. +/// +/// Note that Music_track has few public member functions. However, a +/// music track can be passed to these Mixer member functions to play it: +/// +/// - Mixer::play_music(Music_track, Duration) +/// - Mixer::attach_music(Music_track) +/// +/// Note also that the mixer can only play one music track at a time. +class Music_track : public Audio_clip +{ +public: + /// Loads a new music track from a resource file. + /// + /// Supported file formats may include WAV, MP3, OGG, FLAC, MID, + /// and ABC. Which you actually get depends on which options were + /// enabled when your copy of the SDL2_mixer library that %ge211 links + /// against was compiled. + /// + /// Pausing and resuming does not work correctly with all audio + /// formats. + /// + /// Throws exceptions::File_error if the file cannot be opened, and + /// exceptions::Mixer_error if the file format cannot be understood. + Music_track(const std::string& filename, const Mixer&); + + /// Default-constructs the empty music track. + Music_track() { } + +protected: + bool real_try_load(const std::string&, const Mixer&) override; + void real_clear() override; + bool real_empty() const override; + +private: + // Friends + friend Mixer; + + std::shared_ptr ptr_; +}; + +/// A sound effect track, which can be attached to a Mixer channel and played. +/// +/// A sound effect track may be *empty* or *non-empty*; only non-empty sound +/// effects can actually be played. +/// +/// Note that Sound_effect has few public member functions. However, an +/// effect track can be passed to the Mixer member function +/// Mixer::play_effect(Sound_effect, double) +/// to play it. +class Sound_effect : public Audio_clip +{ +public: + /// Loads a new sound effect track from a resource file. + /// + /// Supported file formats may include WAV, MP3, OGG, FLAC, MID, + /// and ABC. Which you actually get depends on which options were + /// enabled when your copy of the SDL2_mixer library that %ge211 links + /// against was compiled. + /// + /// Throws exceptions::File_error if the file cannot be opened, and + /// exceptions::Mixer_error if the file format cannot be understood. + Sound_effect(const std::string& filename, const Mixer&); + + /// Default-constructs the empty sound effect track. + Sound_effect() { } + +protected: + bool real_try_load(const std::string&, const Mixer&) override; + void real_clear() override; + bool real_empty() const override; + +private: + // Friends + friend Mixer; + + std::shared_ptr ptr_; +}; + +/// The entity that coordinates playing all audio tracks. +/// +/// The mixer is the center of %ge211's audio facilities. It is used to load +/// audio files as Music_track%s and Sound_effect%s, and to play and control +/// them. However, Mixer itself has no public constructor, and you will not +/// construct your own. Rather, a Mixer is constructed, if possible, when +/// Abstract_game is initialized, and this mixer can be accessed by your game +/// via the Abstract_game::mixer() const member function. The member +/// function returns an uncopyable reference. +/// +/// This mixer has one music channel, and some fixed number (usually 8) of +/// sound effects channels. This means that it can play one Music_track and +/// up to (usually) 8 Sound_effect%s simultaneously. +/// +/// For playing background music, one Music_track can be *attached* to the +/// mixer at any given time, using Mixer::attach_music(Music_track). Once +/// a Music_track is attached, it can be played with +/// Mixer::resume(Duration) and paused with +/// Mixer::pause_music(Duration). A Music_track can be both +/// attached and played with Mixer::play_music(Music_track). The music channel +/// is always in one of four states, of type audio::Mixer::State, which can be +/// retrieved by Mixer::get_music_state() const. Note that the validity of +/// music operations depends on what state the mixer's music channel is in. For +/// example, it is an error to attach a Music_track to the mixer when music is +/// already playing or fading out. +/// +/// For playing sound effects, multiple Sound_effect%s can be attached to the +/// mixer simultaneously. A Sound_effect is attached and played using the +/// Mixer::play_effect(Sound_effect, double) member function, which also allows +/// specifying the volume of the sound effect. If nothing further is done, the +/// sound effect plays to completion and then detaches, making room to attach +/// more sound effects; however, Mixer::play_effect(Sound_effect, double) +/// returns a Sound_effect_handle, which can be used to control the sound +/// effect while it is playing as well. +class Mixer +{ +public: + /// The state of an audio channel. + enum class State + { + /// No track is attached to the channel, or no channel is attached to + /// the handle. + detached, + /// Actively playing. + playing, + /// In the process of fading out from playing to paused (for music) or + /// to halted and detached (for sound effects). + fading_out, + /// Attached but not playing. + paused, + }; + + /// Returns whether the mixer is enabled. If it is then we can + /// play audio, but if it isn't, audio operations will fail. + bool is_enabled() const + { return enabled_; } + + /// \name Playing music + ///@{ + + /// Attaches the given music track to this mixer and starts it playing. + /// Equivalent to Mixer::attach_music(Music_track) followed by + /// Mixer::unpause_music(Duration). + /// + /// \preconditions + /// - `get_music_state()` is `paused` or `detached`; throws + /// exceptions::Client_logic_error if violated. + void play_music(Music_track); + + /// Attaches the given music track to this mixer. Give the empty + /// Music_track to detach the current track, if any, without attaching a + /// replacement. + /// + /// \preconditions + /// - `get_music_state()` is `paused` or `detached`; throws + /// exceptions::Client_logic_error if violated. + void attach_music(Music_track); + + /// Plays the currently attached music from the current saved position, + /// fading in if requested. + /// + /// \preconditions + /// - `get_music_state()` is `paused` or `playing`; throws + /// exceptions::Client_logic_error if violated. + void resume_music(Duration fade_in = Duration(0)); + + /// Pauses the currently attached music, fading out if requested. + /// + /// \preconditions + /// - `get_music_state()` is `paused` or `playing`; throws + /// exceptions::Client_logic_error if violated. + void pause_music(Duration fade_out = Duration(0)); + + /// Rewinds the music to the beginning. + /// + /// \preconditions + /// - `get_music_state()` is `paused`; throws exceptions::Client_logic_error if + /// violated. + void rewind_music(); + + /// Gets the Music_track currently attached to this Mixer, if any. + const Music_track& get_music() const + { + return current_music_; + } + + /// Returns the current state of the attached music, if any. + /// + /// The state changes in only three ways: + /// + /// 1. In response to client actions, for example, + /// Mixer::unpause(Duration) changes the state from `paused` to + /// `playing` + /// + /// 2. When a playing track ends, it changes from `playing` to `paused`. + /// + /// 3. When a pause-with-fade-out ends, it changes from `fading_out` to + /// `paused`. + /// + /// Cases 2 and 3 happen only between frames, and not asynchronously + /// while computing the next frame. This means that after checking + /// the result of `get_music_state()` const, that state continues to + /// hold, and can be relied on, at least until the end of the frame, + /// unless the client requests that it be changed (case 1). + State get_music_state() const + { + return music_state_; + } + + /// Returns the music volume as a number from 0.0 to 1.0. + /// Initially this will be 1.0, but you can lower it with + /// Mixer::set_music_volume(double). + double get_music_volume() const; + + /// Sets the music volume, on a scale from 0.0 to 1.0. + void set_music_volume(double unit_value); + + ///@} + + /// \name Playing sound effects + ///@{ + + /// How many effect channels are currently unused? If this is positive, + /// then we can play an additional sound effect with + /// Mixer::play_effect(Sound_effect, double). + int available_effect_channels() const; + + /// Plays the given effect track on this mixer, at the specified volume. + /// The volume must be in the unit interval. Returns a Sound_effect_handle, + /// which can be used to control the sound effect while it's playing. + /// + /// \preconditions + /// - `available_effect_channels() > 0`, throws exceptions::Mixer_error if + /// violated. + /// - `!effect.empty()`, undefined behavior if violated. + Sound_effect_handle + play_effect(Sound_effect effect, double volume = 1.0); + + /// Attempts to play the given effect track on this mixer, returning an + /// empty Sound_effect_handle if no effect channel is available. + /// + /// \preconditions + /// - `!effect.empty()`, undefined behavior if violated. + Sound_effect_handle + try_play_effect(Sound_effect effect, double volume = 1.0); + + /// Pauses all currently-playing effects. + void pause_all_effects(); + + /// Unpauses all currently-paused effects. + void resume_all_effects(); + + ///@} + + ///\name Constructors, assignment operators, and destructor + ///@{ + + /// The mixer cannot be copied. + Mixer(const Mixer&) = delete; + /// The mixer cannot be moved. + Mixer(const Mixer&&) = delete; + /// The mixer cannot be copied. + Mixer& operator=(const Mixer&) = delete; + /// The mixer cannot be moved. + Mixer& operator=(const Mixer&&) = delete; + + /// Destructor, to clean up the mixer's resources. + ~Mixer(); + + ///@} + +private: + using Lazy_ptr = detail::lazy_ptr; + + // This calls default constructor... + friend Abstract_game; + // ...via this: + friend Lazy_ptr; + + /// Private constructor -- should only be called by + /// Abstract_game::mixer() via lazy_ptr. + Mixer(); + + /// Updates the state of the channels. + void poll_channels_(); + friend class detail::Engine; // calls poll_channels_(). + + /// Returns the index of an empty channel. Returns -1 if all + /// are full. + int find_empty_channel_() const; + + /// Registers an effect with a channel. + Sound_effect_handle + register_effect_(int channel, Sound_effect effect); + + /// Unregisters the effect associated with a channel. + void unregister_effect_(int channel); + friend Sound_effect_handle; // calls unregister_effect_(int). + +private: + Music_track current_music_; + State music_state_{State::detached}; + Pausable_timer music_position_{true}; + + bool enabled_; + std::vector channels_; + int available_effect_channels_; +}; + +/// Used to control a Sound_effect after it is started playing on a Mixer. +/// +/// This is returned by Mixer::play_effect(Sound_effect, double). +class Sound_effect_handle +{ +public: + /// Default-constructs the empty sound effect handle. The empty handle + /// is not associated with a channel, and it is an error to attempt to + /// perform operations on it. + /// + /// To get a non-empty Sound_effect_handle, play a Sound_effect with + /// Mixer::play_effect(Sound_effect, double). + Sound_effect_handle() {} + + /// Recognizes the empty sound effect handle. + bool empty() const; + + /// Recognizes a non-empty sound effect handle. + /// Equivalent to `!empty()`. + operator bool() const; + + /// Pauses the effect. + /// + /// \preconditions + /// - `!empty()`, undefined behavior if violated. + /// - `get_state()` is either `playing` or `paused`, throws + /// exceptions::Client_logic_error if violated. + void pause(); + + /// Unpauses the effect. + /// + /// \preconditions + /// - `!empty()`, undefined behavior if violated. + /// - `get_state()` is either `playing` or `paused`, throws + /// exceptions::Client_logic_error if violated. + void resume(); + + /// Stops the effect from playing and detaches it. + /// + /// \preconditions + /// - `!empty()`, undefined behavior if violated. + /// - `get_state()` is either `playing` or `paused`, throws + /// exceptions::Client_logic_error if violated. + void stop(); + + /// Gets the Sound_effect being played by this handle. + /// + /// \preconditions + /// - `!empty()`, undefined behavior if violated. + const Sound_effect& get_effect() const; + + /// Gets the state of this effect. + /// + /// As with the mixer's music state, the state of a side effect channel + /// only changes synchronously, either with client requests, or between + /// frames, in the case where the sound effect finishes and is detached. + /// + /// \preconditions + /// - `!empty()`, undefined behavior if violated. + Mixer::State get_state() const; + + /// Returns the playing sound effect's volume as a number from 0.0 + /// to 1.0. + double get_volume() const; + + /// Sets the playing sound effect's volume as a number from 0.0 to + /// 1.0. + void set_volume(double unit_value); + +private: + friend Mixer; + + struct Impl_ + { + Impl_(Mixer& m, Sound_effect e, int c) + : mixer(m) + , effect(std::move(e)) + , channel(c) + , state(Mixer::State::playing) + { } + + Mixer& mixer; + Sound_effect effect; + int channel; + Mixer::State state; + }; + + Sound_effect_handle(Mixer&, Sound_effect, int channel); + + std::shared_ptr ptr_; +}; + +} // end namespace audio + +} // end namespace ge211 diff --git a/.cs211/lib/ge211/include/ge211_base.hxx b/.cs211/lib/ge211/include/ge211_base.hxx new file mode 100644 index 0000000..ccf5a2f --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_base.hxx @@ -0,0 +1,314 @@ +#pragma once + +#include "ge211_audio.hxx" +#include "ge211_color.hxx" +#include "ge211_error.hxx" +#include "ge211_event.hxx" +#include "ge211_forward.hxx" +#include "ge211_geometry.hxx" +#include "ge211_noexcept.hxx" +#include "ge211_random.hxx" +#include "ge211_resource.hxx" +#include "ge211_session.hxx" +#include "ge211_time.hxx" + +#include +#include + +namespace ge211 { + +/** This is the abstract base class for deriving games. + * + * To create a new game, you must define a new struct or class that derives + * from Abstract_game and includes the state for your game, including + * any model, view (sprites), and controller state. Then you must + * override various handler functions to specify the behavior of your game. + * If nothing else, the Abstract_game::draw(Sprite_set&) function must be + * overridden to specify how to draw your game. + * + * For example, here is a game that creates one rectangular sprites::Sprite and + * renders it on the screen: + * + * ```cpp + * #include + * + * using namespace ge211; + * + * struct My_game : Abstract_game + * { + * Rectangle_sprite rect{Dimensions{300, 200}, Color::medium_red()}; + * + * void draw(Sprite_set& sprites) override + * { + * sprites.add_sprite(rect, Position{100, 100}); + * } + * }; + * + * int main() + * { + * My_game{}.run(); + * } + * ``` + * + * Note that sprites must be created outside draw(Sprite_set&), because they + * need to continue to exist after that function returns. Thus, the usual place + * to define sprites is as member variables of your game struct or class. In + * more advanced cases, you may store sprites in a view class. + * + * Here is a game that creates one circular sprite and renders it wherever + * the mouse goes: + * + * ```cpp + * #include + * + * using namespace ge211; + * + * struct My_game2 : Abstract_game + * { + * // Holds the most recent position of the mouse: + * Position last_mouse{0, 0}; + * + * // Saves the most recent most position each time the + * // mouse moves: + * void on_mouse_move(Position mouse) override + * { + * last_mouse = mouse; + * } + * + * // The circle sprite to render where the mouse is: + * Circle_sprite cursor{10, Color::medium_blue()}; + * + * // Whenever we need to redraw the screen, add the Circle_sprite + * // at the mouse position. + * void draw(Sprite_set& sprites) override + * { + * sprites.add_sprite(cursor, last_mouse); + * } + * }; + * + * int main() + * { + * My_game2{}.run(); + * } + * ``` + */ +class Abstract_game +{ +public: + + /// Runs the game. Usually the way to use this is to create an instance of + /// your game class in `main` and then call run() on it. + void run(); + + /// The default background color of the window, if not changed by the + /// derived class. To change the background color, assign the protected + /// member variable Abstract_game::background_color from the + /// draw(Sprite_set&) or on_start() functions. + static const Color default_background_color; + + /// The default initial window title. You can change this in a derived class + /// by overriding the initial_window_title() const member function. + static const char* const default_window_title; + + /// The default window dimensions, in pixels. You can change this in a + /// derived class by overriding the initial_window_dimensions() const member + /// function. + static const Dimensions default_window_dimensions; + + /// Polymorphic classes should have virtual destructors. + virtual ~Abstract_game() {} + +protected: + /// \name Functions to be overridden by clients + ///@{ + + /// You must override this function in the derived class to specify how + /// to draw your scene. This function is called by the game engine once + /// per frame, after handling events. It is passed a Sprite_set; add + /// sprites to the Sprite_set to have them rendered to the screen. + /// + /// Note that the sprites added to the Sprite_set cannot be local + /// variables owned by the draw(Sprite_set&) function itself, as + /// they must continue to live after the function returns. For this + /// reason, they are usually stored as members in the game class, or + /// in a data structure that is a member of the game class. + virtual void draw(Sprite_set&) = 0; + + /// Called by the game engine once per frame. The parameter is the duration + /// of the previous frame in seconds. Override this function to react to time + /// passing in order to implement animation. + virtual void on_frame(double last_frame_seconds) { + (void) last_frame_seconds; + } + + /// Called by the game engine for each keypress. This uses the system's + /// repeating behavior, so the user holding down a key can result in multiple + /// events being delivered. To find out exactly when keys go down and up, + /// override on_key_down(Key) and on_key_up(Key) instead. + virtual void on_key(Key) { } + + /// Called by the game engine each time a key is depressed. + /// Note that this function is delivered the actual key pressed, not the + /// character that would be generated. For example, if shift is down + /// and the *5 / %* key is pressed, the delivered key code is `'5'`, not + /// `'%'`. Similarly, letter keys deliver only lowercase key codes. If + /// you want key presses interpreted as characters, override on_key(Key) + /// instead. + /// + /// The default behavior of this function, if not overridden, is to quit + /// if the escape key (code 27) is pressed. + virtual void on_key_down(Key); + + /// Called by the game engine each time a key is released. This delivers + /// the same raw key code as on_key_down(Key). + virtual void on_key_up(Key) { } + + /// Called by the game engine each time a mouse button is depressed. + virtual void on_mouse_down(Mouse_button, Position) { } + + /// Called by the game engine each time a mouse button is released. + virtual void on_mouse_up(Mouse_button, Position) { } + + /// Called by the game engine each time the mouse moves. + virtual void on_mouse_move(Position) { } + + /// Called by the game engine after initializing the game but before + /// commencing the event loop. You can do this to perform initialization + /// tasks such as preparing sprites::Sprite%s with + /// prepare(const Sprite&) const. + virtual void on_start() { } + + /// Called by the game engine after exiting the event loop but before + /// the game instance is destroyed. Overriding the function cannot be + /// used to show anything to the user, since no more rendering will be + /// performed. It could, however, be used to save a file or shutdown + /// a network connection. + /// + /// Note that this function is called only if the game exits + /// normally, by calling quit(), or by the user telling + /// the OS to quit the program. It is not called on exceptions or + /// errors. + virtual void on_quit() { } + + /// Override this function to specify the initial dimensions of the + /// game's window. + /// This is only called by the engine once at startup. + virtual Dimensions initial_window_dimensions() const; + + /// Override this function to specify the initial title of the game. + /// This is only called by the engine once at startup. To change the + /// title during the game, use get_window() const and + /// Window::set_title(const std::string&). + virtual std::string initial_window_title() const; + + ///@} + + /// \name Functions to be called by clients + ///@{ + + /// Causes the event loop to quit after the current frame finishes. + void quit() NOEXCEPT; + + /// Gets the Window that the game is running in. This can be used to query + /// its size, change its title, etc. + /// + /// exceptions::Client_logic_error will be thrown if this function is + /// called before the window is created by `run()`. + Window& get_window() const; + + /// Gets a reference to the pseudo-random number generator associated + /// with this game. + /// + /// ``` + /// void My_game::on_frame(double dt) + /// { + /// Random& rnd = get_random(); + /// + /// if (rnd.random_bool(SPAWN_RATE * dt)) { + /// int row = rnd.between(ROW_MIN, ROW_MAX); + /// int count = rnd.up_to(PER_ROW_LIMIT); + /// while (count--) { + /// int col = rnd.between(COL_MIN, COL_MAX); + /// add_enemy(row, col); + /// } + /// } + /// } + /// ``` + /// + /// See the documentation for Random to read more about how this can + /// be used to produce random numbers. + Random& get_random() const NOEXCEPT; + + /// Gets access to the audio mixer, which can be used to play + /// music and sound effects. + Mixer& mixer() const + { + return *mixer_; + } + + /// Gets the time point at which the current frame started. This can be + /// used to measure intervals between events, though it might be better + /// to use a time::Timer or time::Pausable_timer. + Time_point get_frame_start_time() const NOEXCEPT + { return frame_start_.start_time(); } + + /// Returns the duration of the frame right before the frame currently + /// running. See time::Duration for information on how to use the result. + Duration get_prev_frame_length() const NOEXCEPT + { return prev_frame_length_; } + + /// Returns an approximation of the current frame rate in Hz. + /// Typically we synchronize the frame rate with the video controller, but + /// accessing it might be useful for diagnosing performance problems. + double get_frame_rate() const NOEXCEPT + { return fps_; } + + /// Returns an approximation of the current machine load due to GE211. + double get_load_percent() const NOEXCEPT + { return load_; } + + /// Prepares a sprites::Sprite for rendering, without actually including it + /// in the scene. The first time a sprites::Sprite is rendered, it ordinarily + /// has to be converted and transferred to video memory. This function + /// performs that conversion and transfer eagerly instead of waiting + /// for it to happen the first time the sprites::Sprite is used. Careful use of + /// preparation can be used to control when pauses happen and make other + /// parts of the game smoother. The easiest thing is often to prepare + /// all sprites you intend to use from an overridden `on_start()` + /// function. + void prepare(const sprites::Sprite&) const; + + ///@} + + /// Assign this member variable to change the window's background color + /// in subsequent frames. The usual place to assign this variable is from + /// your overridden on_start() and/or draw(Sprite_set&) functions. + Color background_color = default_background_color; + +private: + friend class detail::Engine; + + void mark_present_() NOEXCEPT; + void mark_frame_() NOEXCEPT; + + void poll_channels_(); + + mutable Random rng_; + detail::Session session_; + Mixer::Lazy_ptr mixer_; + detail::Engine* engine_ = nullptr; + + bool quit_ = false; + + Timer frame_start_; + Duration prev_frame_length_; + + double fps_ {0}; + double load_ {100}; + + int sample_counter_ {0}; + Timer real_time_; + Pausable_timer busy_time_; +}; + +} diff --git a/.cs211/lib/ge211/include/ge211_color.hxx b/.cs211/lib/ge211/include/ge211_color.hxx new file mode 100644 index 0000000..b062b56 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_color.hxx @@ -0,0 +1,318 @@ +#pragma once + +#include "ge211_forward.hxx" +#include "ge211_noexcept.hxx" + +#include +#include + +namespace ge211 { + +/// For representing colors. +/// A color has red, green, blue, and alpha (opacity) components, +/// each of which is an integer from 0 to 255, inclusive. +/// +/// The most common way to construct a color is to pass these +/// components to the constructor +/// Color(uint8_t, uint8_t, uint8_t, uint8_t). The components can +/// also be passed as unit-interval `double`s to +/// Color::from_rgba(double, double, double, double). +/// +/// It's also possible to construct a color via alternative color +/// models HSLA and HSVA, and then convert those to RGBA. +class Color +{ +public: + /// \name Constructors and factories + /// @{ + + /// Constructs the transparent color. + constexpr Color() NOEXCEPT + : Color{0, 0, 0, 0} + { } + + /// Constructs the color with the given components. + /// + /// Components are integers from 0 to 255, inclusive. If `alpha` is not + /// provided, it defaults to fully opaque. + constexpr Color(uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t alpha = 255) NOEXCEPT + : red_{red}, green_{green}, blue_{blue}, alpha_{alpha} + { } + + /// Constructs a color with the given components. + /// + /// Components are doubles from 0.0. to 1.0, inclusive. + /// If `alpha` is not given, it defaults to 1.0, meaning fully opaque. + static Color from_rgba(double red, double green, double blue, + double alpha = 1.0) NOEXCEPT; + + /// @} + + /// \name Named colors + /// @{ + + /// Solid white. + static constexpr Color white() NOEXCEPT + { + return {255, 255, 255}; + } + + /// Solid black. + static constexpr Color black() NOEXCEPT + { + return {0, 0, 0}; + } + + /// Solid red. + static Color medium_red() NOEXCEPT + { return from_hsla(0, .5, .5); } + /// Solid green. + static Color medium_green() NOEXCEPT + { return from_hsla(120, .5, .5); } + /// Solid blue. + static Color medium_blue() NOEXCEPT + { return from_hsla(240, .5, .5); } + /// Solid cyan. + static Color medium_cyan() NOEXCEPT + { return from_hsla(180, .5, .5); } + /// Solid magenta. + static Color medium_magenta() NOEXCEPT + { return from_hsla(300, .5, .5); } + /// Solid yellow. + static Color medium_yellow() NOEXCEPT + { return from_hsla(60, .5, .5); } + + /// @} + + /// \name RGBA-model getters + /// @{ + + /// Gets the red component of a color. + uint8_t red() const NOEXCEPT { return red_; }; + /// Gets the green component of a color. + uint8_t green() const NOEXCEPT { return green_; }; + /// Gets the blue component of a color. + uint8_t blue() const NOEXCEPT { return blue_; }; + /// Gets the alpha (opacity) component of a color. + uint8_t alpha() const NOEXCEPT { return alpha_; }; + + + /// \name Transformations + /// @{ + + /// Produces a blend of this color and `that`, with higher + /// `weight` (between 0 and 1) increasing the level of `that`. + Color blend(double weight, Color that) const NOEXCEPT; + + /// Returns the inverse of a color. + Color invert() const NOEXCEPT; + + /// Returns a color by rotating the hue, leaving the other properties + /// constant. + Color rotate_hue(double degrees) const NOEXCEPT; + + /// Produces a tint by lightening the color. The `amount` must be + /// between 0 and 1, where 0 leaves the color the same, and 1 produces + /// white. + Color lighten(double unit_amount) const NOEXCEPT; + + /// Produces a shade by darkening the color. The `unit_amount` must be + /// between 0 and 1, where 0 leaves the color the same, and 1 produces + /// black. + Color darken(double unit_amount) const NOEXCEPT; + + /// Produces a fuller tone by saturating the color. The `unit_amount` must + /// be between 0 and 1, where 0 leaves the color the same, and 1 + /// produces a fully saturated color. + Color saturate(double unit_amount) const NOEXCEPT; + + /// Produces a weaker tone by desaturating the color. The `unit_amount` + /// must be between 0 and 1, where 0 leaves the color the same, and + /// 1 produces gray at the same lightness as the original color. + Color desaturate(double unit_amount) const NOEXCEPT; + + /// Increases opacity of the color. The `unit_amount` must + /// be between 0 and 1, where 0 leaves the color the same, and 1 + /// produces a fully opaque color. + Color fade_in(double unit_amount) const NOEXCEPT; + + /// Decreases opacity of the color. The // `unit_amount` + /// must be between 0 and 1, where 0 leaves the color the same, and + /// 1 produces full transparency. + Color fade_out(double unit_amount) const NOEXCEPT; + + /// @} + + /// \name Alternative color models + /// @{ + + /// Representation for the hue-saturation-lightness-alpha color + /// model. See [Wikipedia](https://en.wikipedia.org/wiki/HSL_and_HSV) + /// for details on color models. + struct HSLA + { + /// The hue in degrees from 0 to 360. 0° (and 360°) is red, + /// 120° is green, and 240° is blue. + double hue; + + /// The fullness of the color, from 0.0 (grey) to 1.0 (fully + /// saturated). + double saturation; + + /// The lightness of the color, from 0.0 (black) to 1.0 (white). + double lightness; + + /// The opacity of the color, from 0.0 (fully transparent) to + /// 1.0 (fully opaque). + double alpha; + + /// Constructs a hue-saturation-lightness-alpha color from its + /// unit interval components. + HSLA(double hue, double saturation, + double lightness, double alpha = 1.0) NOEXCEPT; + + /// Converts color to the RGBA color model. + Color to_rgba() const NOEXCEPT; + + /// \name Transformations + /// @{ + + /// Returns a color by rotating the hue, leaving the other properties + + HSLA rotate_hue(double degrees) const NOEXCEPT; + /// Produces a fuller tone by saturating the color. The `unit_amount` must + /// be between 0 and 1, where 0 leaves the color the same, and 1 + /// produces a fully saturated color. + HSLA saturate(double unit_amount) const NOEXCEPT; + /// Produces a weaker tone by desaturating the color. The `unit_amount` + /// must be between 0 and 1, where 0 leaves the color the same, and + /// 1 produces gray at the same lightness as the original color. + HSLA desaturate(double unit_amount) const NOEXCEPT; + /// Produces a tint by lightening the color. The `amount` must be + /// between 0 and 1, where 0 leaves the color the same, and 1 produces + /// white. + HSLA lighten(double unit_amount) const NOEXCEPT; + /// Produces a shade by darkening the color. The `unit_amount` must be + /// between 0 and 1, where 0 leaves the color the same, and 1 produces + /// black. + HSLA darken(double unit_amount) const NOEXCEPT; + /// Increases opacity of the color. The `unit_amount` must + /// be between 0 and 1, where 0 leaves the color the same, and 1 + /// produces a fully opaque color. + HSLA fade_in(double unit_amount) const NOEXCEPT; + /// Decreases opacity of the color. The // `unit_amount` + /// must be between 0 and 1, where 0 leaves the color the same, and + /// 1 produces full transparency. + HSLA fade_out(double unit_amount) const NOEXCEPT; + + /// @} + }; + + /// Constructs a color given the hue, saturation, lightness, and alpha. + /// + /// \param hue in degrees, from 0.0 to 360.0 + /// \param saturation from 0.0 to 1.0 + /// \param lightness from 0.0 to 1.0 + /// \param alpha opacity, from 0.0 to 1.0 + /// \return the color + static Color from_hsla(double hue, double saturation, double lightness, + double alpha = 1) NOEXCEPT; + + /// Converts a color to the hue-saturation-lightness (HSL) color model. + HSLA to_hsla() const NOEXCEPT; + /// Representation for the hue-saturation-value-alpha color + /// model. See [Wikipedia](https://en.wikipedia.org/wiki/HSL_and_HSV) + /// for details on color models. + struct HSVA + { + /// The hue in degrees from 0 to 360. 0° (and 360°) is red, + /// 120° is green, and 240° is blue. + double hue; + + /// The fullness of the color, from 0,0 (grey) to 1.0 (fully + /// saturated). + double saturation; + + /// The brightness of the color, from 0.0 (black) to 1.0 (fully + /// colored). + double value; + + /// The opacity of the color, from 0.0 (fully transparent) to + /// 1.0 (fully opaque). + double alpha; + + /// Constructs a hue-saturation-value-alpha color from its + /// unit interval components. + HSVA(double hue, double saturation, + double value, double alpha = 1.0) NOEXCEPT; + + /// Converts color to the RGBA color model. + Color to_rgba() const NOEXCEPT; + + /// \name Transformations + /// @{ + + /// Returns a color by rotating the hue, leaving the other properties + /// constant. + HSVA rotate_hue(double degrees) const NOEXCEPT; + /// Produces a fuller tone by saturating the color. The `unit_amount` must + /// be between 0 and 1, where 0 leaves the color the same, and 1 + /// produces a fully saturated color. + HSVA saturate(double unit_amount) const NOEXCEPT; + /// Produces a weaker tone by desaturating the color. The `unit_amount` + /// must be between 0 and 1, where 0 leaves the color the same, and + /// 1 produces gray at the same lightness as the original color. + HSVA desaturate(double unit_amount) const NOEXCEPT; + /// Produces a brighter color by increasing the value. The `unit_amount` + /// must be between 0 and 1, where 0 leaves the color the same, and + /// 1 produces a fully bright color. + HSVA revalue(double unit_amount) const NOEXCEPT; + /// Produces a darker color by decreasing the value. The `unit_amount` + /// must be between 0 and 1, where 0 leaves the color the same, and + /// 1 produces black. + HSVA devalue(double unit_amount) const NOEXCEPT; + /// Increases opacity of the color. The `unit_amount` must + /// be between 0 and 1, where 0 leaves the color the same, and 1 + /// produces a fully opaque color. + HSVA fade_in(double unit_amount) const NOEXCEPT; + /// Decreases opacity of the color. The // `unit_amount` + /// must be between 0 and 1, where 0 leaves the color the same, and + /// 1 produces full transparency. + HSVA fade_out(double unit_amount) const NOEXCEPT; + + /// @} + }; + + /// Constructs a color given the hue, saturation, value, and alpha. + /// + /// \param hue in degrees, from 0.0 to 360.0 + /// \param saturation from 0.0 to 1.0 + /// \param value from 0.0 to 1.0 + /// \param alpha opacity, from 0.0 to 1.0 + /// \return the color + static Color from_hsva(double hue, double saturation, double value, + double alpha = 1) NOEXCEPT; + + /// Converts a color to the hue-saturation-value (HSV) color model. + HSVA to_hsva() const NOEXCEPT; + + /// @} + +private: + uint8_t red_; + uint8_t green_; + uint8_t blue_; + uint8_t alpha_; + + friend Text_sprite; + friend class detail::Render_sprite; + + SDL_Color to_sdl_() const NOEXCEPT; + uint32_t to_sdl_(const SDL_PixelFormat*) const NOEXCEPT; +}; + +} + diff --git a/.cs211/lib/ge211/include/ge211_engine.hxx b/.cs211/lib/ge211/include/ge211_engine.hxx new file mode 100644 index 0000000..5579c03 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_engine.hxx @@ -0,0 +1,35 @@ +#pragma once + +#include "ge211_forward.hxx" +#include "ge211_noexcept.hxx" +#include "ge211_render.hxx" +#include "ge211_window.hxx" + +namespace ge211 { + +namespace detail { + +class Engine +{ +public: + explicit Engine(Abstract_game&); + + void run(); + void prepare(const sprites::Sprite&) const; + Window& get_window() NOEXCEPT; + + ~Engine(); + +private: + void handle_events_(SDL_Event&); + void paint_sprites_(Sprite_set&); + + Abstract_game& game_; + Window window_; + detail::Renderer renderer_; + bool is_focused_ = false; +}; + +} // end namespace detail + +} diff --git a/.cs211/lib/ge211/include/ge211_error.hxx b/.cs211/lib/ge211/include/ge211_error.hxx new file mode 100644 index 0000000..166eed0 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_error.hxx @@ -0,0 +1,252 @@ +#pragma once + +#include "ge211_forward.hxx" +#include "ge211_noexcept.hxx" + +#include +#include +#include +#include + +namespace ge211 { + +/// An exception hierarchy for %ge211 to report errors. +namespace exceptions { + +/// The root of the %ge211 exception hierarchy. Exceptions thrown by +/// %ge211 are derived from Exception_base. +/// +/// The constructor of Exception_base is private, which means that +/// you cannot construct it directly, nor can you derive from it. +/// However, its derived class Client_logic_error has a public +/// constructor, so you can use it as you wish. +class Exception_base : public std::exception +{ +public: + /// The error message associated with the exception. This + /// pointer is guaranteed to be good as long as the exception + /// exists and hasn't been mutated. If you need it for longer, + /// copy it to a std::string. + const char* what() const NOEXCEPT override; + +private: + explicit Exception_base(const std::string& message); + + /// Derived classes + friend Client_logic_error; + friend Environment_error; + + std::shared_ptr message_; +}; + +/// An exception that indicates that a logic error was performed +/// by the client. For example, a Client_logic_error is thrown by +/// Abstract_game::get_window() const if that function is called +/// before the Window has been created. Client code may throw or +/// derive from Client_logic_error as well. +class Client_logic_error : public Exception_base +{ +public: + /// Constructs the error, given the provided error message. + explicit Client_logic_error(const std::string& message); +}; + +/// An exception thrown when the client attempts to perform an +/// action that requires a GE211 session before GE211 starts. +/// For example, GE211 needs to initialize the font subsystem +/// before fonts can be loaded, so the `Font` constructor throws +/// this exception if it’s called too early. +class Session_needed_error : public Client_logic_error +{ +public: + /// The action that the client attempted that couldn't be + /// completed without a GE211 session. + const std::string& attempted_action() const { return action_; } + +private: + friend class detail::Session; + + explicit Session_needed_error(const std::string& action); + + std::string action_; +}; + +/// Indicates that an error was encountered by the game engine or +/// in the client's environment. +/// This could indicate a problem with your video driver, +/// a missing file, or even a bug in %ge211 itself. The derived +/// classes indicate more precisely the nature of the condition. +class Environment_error : public Exception_base +{ + explicit Environment_error(const std::string& message); + + /// Throwers + friend Window; + + /// Derived classes + friend Ge211_logic_error; + friend Host_error; +}; + +/// Indicates a condition unexpected by %ge211, that appears +/// to break its invariants. This may very well indicate a bug +/// in %ge211. Please report it if you see one of these. +class Ge211_logic_error : public Environment_error +{ + explicit Ge211_logic_error(const std::string& message); + + /// Throwers + friend class detail::Render_sprite; + friend Mixer; + friend Text_sprite; +}; + +/// Indicates an exception from the host environment being +/// passed along by %ge211. The host environment is usually +/// SDL2, so these exceptions may include a reason provided +/// by SDL2. +class Host_error : public Environment_error +{ + explicit Host_error(const std::string& extra_message = ""); + + /// Derived classes + friend File_error; + friend Font_error; + friend Image_error; + friend Mixer_error; + + /// Throwers + friend Text_sprite; + friend Window; + friend class detail::Renderer; + friend class detail::Render_sprite; + friend class detail::Texture; +}; + +/// Indicates an error opening a file. +class File_error final : public Host_error +{ + explicit File_error(const std::string& message); + static File_error could_not_open(const std::string& filename); + + /// Thrower + friend class detail::File_resource; +}; + +/// Indicates an error loading a font front an already-open file. +class Font_error final : public Host_error +{ + explicit Font_error(const std::string& message); + static Font_error could_not_load(const std::string& filename); + + /// Thrower + friend Font; +}; + +/// Indicates an error loading an image from an already-open file. +class Image_error final : public Host_error +{ + explicit Image_error(const std::string& message); + static Image_error could_not_load(const std::string& filename); + + /// Thrower + friend Image_sprite; +}; + +/// Indicates an error in the mixer, which could include the inability to +/// understand an audio file format. +class Mixer_error : public Host_error +{ + Mixer_error(const std::string& message); + static Mixer_error could_not_load(const std::string& filename); + static Mixer_error out_of_channels(); + static Mixer_error not_enabled(); + + /// Thrower + friend Mixer; + friend Audio_clip; + friend Music_track; + friend Sound_effect; +}; + +} // end namespace exception + +namespace detail { + +enum class Log_level +{ + /// extra debugging information + debug, + /// helpful information + info, + /// non-fatal but concerning conditions + warn, + /// serious errors + fatal, +}; + +// Right now a Logger just keeps track of the current log +// level, and there's only one Logger (Singleton Pattern). +class Logger +{ +public: + using Level = Log_level; + + Level level() const NOEXCEPT { return level_; } + void level(Level level) NOEXCEPT { level_ = level; } + + static Logger& instance() NOEXCEPT; + +private: + Logger() NOEXCEPT = default; + + Level level_ = Level::warn; +}; + +// A Log_message accumulate information and then prints it all at +// once when it's going to be destroyed. +class Log_message +{ +public: + using Level = Log_level; + + explicit Log_message(Level level = Level::debug); + explicit Log_message(std::string reason, + Level level = Level::debug) NOEXCEPT; + + template + Log_message& operator<<(const T& value) + { + if (active_) message_ << value; + return *this; + } + + // A Log_message has important work to do when it's destroyed. + virtual ~Log_message(); + + // A Log_message should not be copied. + Log_message(const Log_message&) = delete; + Log_message& operator=(const Log_message&) = delete; + + // But it can be moved. + Log_message(Log_message&&) = default; + Log_message& operator=(Log_message&&) = default; + +private: + std::string reason_; + std::ostringstream message_; + bool active_; +}; + +Log_message debug(std::string reason = ""); +Log_message info(std::string reason = ""); +Log_message warn(std::string reason = ""); +Log_message fatal(std::string reason = ""); + +Log_message info_sdl(); +Log_message warn_sdl(); +Log_message fatal_sdl(); + +} // end namespace detail + +} diff --git a/.cs211/lib/ge211/include/ge211_event.hxx b/.cs211/lib/ge211/include/ge211_event.hxx new file mode 100644 index 0000000..8081478 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_event.hxx @@ -0,0 +1,263 @@ +#pragma once + +#include "ge211_forward.hxx" +#include "ge211_error.hxx" +#include "ge211_noexcept.hxx" + +#include +#include + +namespace ge211 { + +/// Types for representing mouse and keyboard events. +namespace events { + +/// A representation of a mouse button. This is used by +/// Abstract_game::on_mouse_down(Mouse_button, Position) and +/// Abstract_game::on_mouse_up(Mouse_button, Position) to specify +/// which mouse button was depressed or released. +enum class Mouse_button +{ + /// the primary mouse button + left, + /// the tertiary mouse button + middle, + /// the secondary mouse button + right, +}; + +/// Prints a #Mouse_button on a std::ostream. This function prints a +/// representation suitable for debugging, but probably not suitable for +/// end users. +std::ostream& operator<<(std::ostream&, Mouse_button); + +} // end namespace events + +namespace detail { + +// Attempts to convert an SDL mouse button code to a ge211 Mouse_button. +// Returns true on success, or false if the SDL mouse button code does +// not correspond to left, middle, or right. +bool map_button(uint8_t, Mouse_button&) NOEXCEPT; + +// Unicode constants. +static char32_t const lowest_unicode_surrogate = 0xD800; +static char32_t const highest_unicode_surrogate = 0xDFFF; +static char32_t const highest_unicode_code_point = 0x10FFFF; + +// Checks for valid Unicode code points. +inline bool is_valid_unicode(char32_t code) +{ + return code < lowest_unicode_surrogate || + (code > highest_unicode_surrogate && + code <= highest_unicode_code_point); +} + +} // end namespace detail + +namespace events { + +/// Represents a key on the keyboard. +/// +/// The easiest way to detect a key is to create the Key value you want +/// to detect and then compare for equality. For example, you can create +/// the up arrow Key with `Key::up()`, or the uppercase `M` key with +/// `Key::code('M')`. +/// +/// Here's an example of key handling: +/// +/// ```cpp +/// void on_key(Key key) override +/// { +/// if (key == Key::code('q')) +/// quit(); +/// else if (key == Key::up()) +/// move_up(); +/// else if (key == Key::down()) +/// move_down(); +/// } +/// ``` +/// +/// Another way to recognize a key is to look at its two properties: +/// +/// - type() const — will be the value Key::Type::code if the key +/// represents a Unicode character (code point), or some other value +/// of the Key::Type enumeration for non-Unicode keys. +/// +/// - code() const — will be the Unicode code point value of the key if +/// type() const is Key::Type::code, and will be `0` otherwise. +/// +/// Here is an example distinguishing several keys by switching on their +/// properties: +/// +/// ```cpp +/// void on_key(Key key) override +/// { +/// switch (key.type()) { +/// case Key::Type::up: +/// move_up(); +/// break; +/// case Key::Type::down: +/// move_down(); +/// break; +/// case Key::Type::code: { +/// char c = key.code(); +/// switch (c) { +/// case '\b': +/// backspace(); +/// break; +/// case '\r': +/// enter(); +/// break; +/// default: +/// add_to_buffer(c); +/// } +/// break; +/// } +/// } +/// ``` +/// +/// Currently this type supports keys that deliver Unicode values using +/// whatever input method is supported by your operating system, as well +/// as the arrow keys and modifier keys shift, control, option/alt, and +/// command/meta. If you need to handle other keys, contact me and I will +/// add them. +class Key +{ +public: + /// \name Constructor and factories + /// @{ + + /// Constructs the empty key, with type Key::Type::other. + Key() NOEXCEPT : Key{Type::other} { } + + /// Constructs a key with the given Unicode code point code. + /// Throws exceptions::Client_logic_error if `c` is not a valid + /// Unicode code point. Valid code points are from 0 to + /// 0x10FFFF, [except for 0xD800 to + /// 0xDFFF](https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates). + static Key code(char32_t c) + { + if (detail::is_valid_unicode(c)) + return Key{c}; + + throw Client_logic_error{"Not a valid Unicode code point"}; + } + + /// Constructs the up arrow key. + static Key up() NOEXCEPT { return Key{Type::up}; }; + + /// Constructs the down arrow key. + static Key down() NOEXCEPT { return Key{Type::down}; }; + + /// Constructs the left arrow key. + static Key left() NOEXCEPT { return Key{Type::left}; }; + + /// Constructs the right arrow key. + static Key right() NOEXCEPT { return Key{Type::right}; }; + + /// Constructs the shift key. + static Key shift() NOEXCEPT { return Key{Type::shift}; }; + + /// Constructs the control key. + static Key control() NOEXCEPT { return Key{Type::control}; }; + + /// Constructs the alt (or option) key. + static Key alt() NOEXCEPT { return Key{Type::alt}; }; + + /// Constructs the command (or meta) key. + static Key command() NOEXCEPT { return Key{Type::command}; }; + + /// Constructs an invalid or unknown key. This returns the same + /// value as the default constructor Key(). + static Key other() NOEXCEPT { return Key{Type::other}; } + + /// @} + + /// The possible types of keys. + enum class Type + { + /// Indicates a key with an Unicode value, which can be gotten + /// with Key::code() const. + code, + /// The up arrow key. + up, + /// The down arrow key. + down, + /// The left arrow key. + left, + /// The right arrow key. + right, + /// The shift key. + shift, + /// The control key + control, + /// The alt or option key + alt, + /// The command or meta key + command, + /// Any other, unknown or invalid key. + other, + }; + + /// The type of the key. + Type type() const NOEXCEPT { return type_; } + + /// The Unicode code point of the key, if it has one. + char32_t code() const NOEXCEPT { return code_; } + + /// Does the key represent printable text? This is true for some but not + /// all Type::code keys. It's never true for other types of keys. + bool is_textual() const NOEXCEPT; + + /// Returns a representation of the key's code as a std::string. This could + /// be useful if you want to capture typing text, rather than game control, + /// because concatenating a string to a string is easier than concatenating + /// the `char32_t` code() const to a string, when that could be an + /// arbitrary Unicode code point. + /// + /// The result of this function is only meaningful when + /// is_textual() const returns true. + std::string as_text() const; + +private: + explicit Key(Type type) NOEXCEPT + : type_{type}, + code_{0} + { } + + explicit Key(char32_t c) NOEXCEPT + : type_{Type::code}, + code_{c} + { } + + friend class detail::Engine; + explicit Key(SDL_KeyboardEvent const&) NOEXCEPT; + + Type type_; + char32_t code_; +}; + +/// Equality for keys. +inline bool operator==(Key a, Key b) NOEXCEPT +{ + return a.type() == b.type() && a.code() == b.code(); +} + +/// Disequality for keys. +inline bool operator!=(Key a, Key b) NOEXCEPT +{ + return !(a == b); +} + +/// Prints a Key::Type on a std::ostream. This function prints a representation +/// suitable for debugging, but probably not suitable for end users. +std::ostream& operator<<(std::ostream&, Key::Type); + +/// Prints a Key on a std::ostream. This function prints a representation +/// suitable for debugging, but probably not suitable for end users. +std::ostream& operator<<(std::ostream&, Key); + +} // end namespace events + +} diff --git a/.cs211/lib/ge211/include/ge211_forward.hxx b/.cs211/lib/ge211/include/ge211_forward.hxx new file mode 100644 index 0000000..12c72aa --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_forward.hxx @@ -0,0 +1,122 @@ +#pragma once + +// Prevent SDL2 from taking over main(). +#ifndef SDL_MAIN_HANDLED +# define SDL_MAIN_HANDLED +#endif + +// Forward declarations for some useful SDL2 types. +struct SDL_KeyboardEvent; +union SDL_Event; +struct SDL_Renderer; +struct SDL_RWops; +struct SDL_Surface; +struct SDL_Texture; +struct SDL_Window; +struct Mix_Chunk; +struct _Mix_Music; +typedef struct _Mix_Music Mix_Music; +struct _TTF_Font; +typedef struct _TTF_Font TTF_Font; + +// Forward declarations for all ge211 types. +namespace ge211 { + +class Abstract_game; +class Color; +class Font; +class Sprite_set; +class Window; + +/// Internal implementation details. +namespace detail { + +class Engine; +class File_resource; +struct Placed_sprite; +class Renderer; +class Session; +class Render_sprite; +class Texture; +class Texture_sprite; + +} // end namespace detail + +namespace audio { + +enum class Channel_state; +class Mixer; +class Audio_clip; +class Music_track; +class Sound_effect; +class Sound_effect_handle; + +} // end namespace audio + +namespace events { + +class Key; +enum class Mouse_button; + +} // end namespace events + +namespace exceptions { + +class Exception_base; +class Client_logic_error; +class Session_needed_error; +class Environment_error; +class Ge211_logic_error; +class Host_error; +class File_error; +class Font_error; +class Image_error; +class Mixer_error; + +} // end namespace exception + +namespace geometry { + +template struct Basic_dimensions; +template struct Basic_position; +template struct Basic_rectangle; + +using Dimensions = Basic_dimensions; +using Position = Basic_position; +using Rectangle = Basic_rectangle; +class Transform; + +} // end namespace geometry + +namespace sprites { + +class Sprite; + +class Circle_sprite; +class Image_sprite; +class Multiplexed_sprite; +class Rectangle_sprite; +class Text_sprite; + +} // end namespace sprites + +namespace time { + +class Duration; +class Time_point; + +class Timer; +class Pausable_timer; + +} // end namespace time + +// Bring everything but detail into the ge211 namespace. + +using namespace audio; +using namespace events; +using namespace exceptions; +using namespace geometry; +using namespace sprites; +using namespace time; + +} diff --git a/.cs211/lib/ge211/include/ge211_geometry.hxx b/.cs211/lib/ge211/include/ge211_geometry.hxx new file mode 100644 index 0000000..0197e3b --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_geometry.hxx @@ -0,0 +1,811 @@ +#pragma once + +#include "ge211_forward.hxx" +#include "ge211_noexcept.hxx" +#include "ge211_util.hxx" + +#include + +#include +#include + +namespace ge211 { + +/// Geometric objects and their operations. +namespace geometry { + +/// Represents the dimensions of an object, or more generally, +/// the displacement between two Basic_position%s. Note that +/// much of the library uses geometry::Dimensions, which is a +/// type alias for Basic_dimensions. +template +struct Basic_dimensions +{ + /// The coordinate type for the dimensions. This is an alias of + /// type parameter `T`. + using Coordinate = T; + + Coordinate width; ///< The width of the object. + Coordinate height; ///< The height of the object. + + /// Converts a Basic_dimensions to another coordinate type. + /// For example: + /// + /// ```cpp + /// Basic_dimensions d1{3, 4}; + /// Basic_dimensions d2 = d1.into(); + /// ``` + template + Basic_dimensions into() const + NOEXCEPT_(detail::is_nothrow_convertible()) + { + return {U(width), U(height)}; + } +}; + +/// Type alias for the most common use of Basic_dimensions, which is with +/// a coordinate type of `int`. +using Dimensions = Basic_dimensions; + +/// Equality for Basic_dimensions. +template +bool operator==(Basic_dimensions a, Basic_dimensions b) + NOEXCEPT_(detail::is_nothrow_comparable()) +{ + return a.width == b.width && a.height == b.height; +} + +/// Disequality for Basic_dimensions. +template +bool operator!=(Basic_dimensions a, Basic_dimensions b) + NOEXCEPT_(detail::is_nothrow_comparable()) +{ + return !(a == b); +} + +/// Adds two Basic_dimensions%es. This is vector addition. +template +Basic_dimensions operator+(Basic_dimensions d1, + Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return {d1.width + d2.width, d1.height + d2.height}; +} + +/// Subtracts two Basic_dimensions%es. This is vector subtraction. +template +Basic_dimensions operator-(Basic_dimensions d1, + Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return {d1.width - d2.width, d1.height - d2.height}; +} + +/// Multiplies a Basic_dimensions by a scalar. +template +Basic_dimensions operator*(Basic_dimensions d1, T s2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return {d1.width * s2, d1.height * s2}; +} + +/// Multiplies a Basic_dimensions by a scalar. +template +Basic_dimensions operator*(T s1, Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return d2 * s1; +} + +/// Multiplies a Basic_dimensions by a `double`. This is vector-scalar +/// multiplication. If the result components would be fractional, they are +/// truncated. +/// +/// This function is disabled if `T` is `double`, as there is another +/// function for that. +template ::value, void>> +Basic_dimensions operator*(Basic_dimensions d1, double s2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return {static_cast(d1.width * s2), + static_cast(d1.height * s2)}; +} + +/// Multiplies a Basic_dimensions by a `double`. This is vector-scalar +/// multiplication. If the result components would be fractional, they are +/// truncated. +/// +/// This function is disabled if `T` is `double`, as there is another +/// function for that. +template ::value, void>> +Basic_dimensions operator*(double s1, Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return d2 * s1; +} + +/// Divides a Basic_dimensions by a `T`. This is vector-scalar +/// division. If the result components would be fractional, they are +/// truncated. +/// +/// \preconditions +/// - `z` cannot be `0` if `T` is an integral type. +template +Basic_dimensions operator/(Basic_dimensions d1, T s2) + NOEXCEPT_(detail::has_nothrow_division()) +{ + return {d1.width / s2, d1.height / s2}; +} + +/// Divides a Basic_dimensions by an `double`. This is vector-scalar +/// division. If the result components would be fractional, they are +/// truncated. +/// +/// This function is disabled if `T` is `double`, as there is another +/// function for that. +template ::value, void>> +Basic_dimensions operator/(Basic_dimensions d1, double s2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return d1 * (1 / s2); +} + +/// Succinct Basic_dimensions addition. +template +Basic_dimensions& operator+=(Basic_dimensions& d1, + Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return d1 = d1 + d2; +} + +/// Succinct Basic_dimensions subtraction. +template +Basic_dimensions& operator-=(Basic_dimensions& d1, + Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return d1 = d1 - d2; +} + +/// Succinct Basic_dimensions-scalar multiplication. +template +Basic_dimensions& operator*=(Basic_dimensions& d1, T s2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return d1 = d1 * s2; +} + +/// Succinct Basic_dimensions-scalar multiplication. +template +Basic_dimensions& operator*=(Basic_dimensions& d1, double s2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return d1 = d1 * s2; +} + +/// Succinct Basic_dimensions-scalar division. +/// +/// \preconditions +/// - `s2 != 0` +template +Basic_dimensions& operator/=(Basic_dimensions& d1, T s2) + NOEXCEPT_(detail::has_nothrow_division()) +{ + return d1 = d1 / s2; +} + +/// Succinct Basic_dimensions-scalar division. +template +Basic_dimensions& operator/=(Basic_dimensions& d1, double s2) + NOEXCEPT_(detail::has_nothrow_division()) +{ + return d1 = d1 / s2; +} + +/// A position in the T-valued Cartesian plane. In graphics, +/// the origin is traditionally in the upper left, so the *x* coordinate +/// increases to the right and the *y* coordinate increases downward. +/// Note that much of the library uses geometry::Position, which is a +/// type alias for Basic_position. +template +struct Basic_position +{ + /// The coordinate type for the position. This is an alias of + /// type parameter `T`. + using Coordinate = T; + /// A dimensions type having the same coordinate type as this position + /// type. + using Dimensions = Basic_dimensions; + + Coordinate x; ///< The *x* coordinate + Coordinate y; ///< The *y* coordiante + + /// \name Constructors + /// @{ + + /// Constructs a position from the given *x* and *y* coordinates. + Basic_position(Coordinate x, Coordinate y) + NOEXCEPT_(detail::is_nothrow_convertible()) + : x{x}, y{y} + { } + + /// Constructs a position from a Basic_dimensions, which gives the + /// displacement of the position from the origin. + explicit Basic_position(Dimensions dims) + NOEXCEPT_(detail::is_nothrow_convertible()) + : Basic_position{dims.width, dims.height} + { } + + /// Converts a Basic_position to another coordinate type. + /// For example: + /// + /// ```cpp + /// Basic_position p1{3, 4}; + /// Basic_position p2 = d1.into(); + /// ``` + template + Basic_position into() const + NOEXCEPT_(detail::is_nothrow_convertible()) + { + return {U(x), U(y)}; + } + + /// @} + + /// \name Shifting member functions + /// @{ + + /// Constructs the position that is above this position by the given + /// amount. + Basic_position up_by(Coordinate dy) const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x, y - dy}; + } + + /// Constructs the position that is below this position by the given + /// amount. + Basic_position down_by(Coordinate dy) const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x, y + dy}; + } + + /// Constructs the position that is to the left of this position by + /// the given amount. + Basic_position left_by(Coordinate dx) const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x - dx, y}; + } + + /// Constructs the position that is to the right of this position by + /// the given amount. + Basic_position right_by(Coordinate dx) const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x + dx, y}; + } + + /// Constructs the position that is above and left of this position + /// by the given dimensions. + Basic_position up_left_by(Dimensions dims) const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x - dims.width, y - dims.height}; + } + + /// Constructs the position that is above and right of this position + /// by the given dimensions. + Basic_position up_right_by(Dimensions dims) const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x + dims.width, y - dims.height}; + } + + /// Constructs the position that is below and left of this position + /// by the given dimensions. + Basic_position down_left_by(Dimensions dims) const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x - dims.width, y + dims.height}; + } + + /// Constructs the position that is below and right of this position + /// by the given dimensions. + Basic_position down_right_by(Dimensions dims) const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x + dims.width, y + dims.height}; + } + + /// @} +}; + +/// Type alias for the most common use of Basic_position, which is with +/// a coordinate type of `int`. +using Position = Basic_position; + +/// Equality for positions. +template +bool operator==(Basic_position p1, Basic_position p2) + NOEXCEPT_(detail::is_nothrow_comparable()) +{ + return p1.x == p2.x && p1.y == p2.y; +} + +/// Disequality for positions. +template +bool operator!=(Basic_position p1, Basic_position p2) + NOEXCEPT_(detail::is_nothrow_comparable()) +{ + return !(p1 == p2); +} + +/// Translates a position by some displacement. This is the same as +/// Position::below_right_by(Basic_dimensions) const. +template +Basic_position operator+(Basic_position p1, Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return p1.down_right_by(d2); +} + +/// Translates a position by some displacement. +template +Basic_position operator+(Basic_dimensions d1, Basic_position p2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return p2.down_right_by(d1); +} + +/// Translates a position by the opposite of some displacement. This is +/// the same as Position::above_left_by(Basic_dimensions) const. +template +Basic_position operator-(Basic_position p1, Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return p1.up_left_by(d2); +} + +/// Translates a position by the opposite of some displacement. +template +Basic_dimensions operator-(Basic_position p1, Basic_position p2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return {p1.x - p2.x, p1.y - p2.y}; +} + +/// Succinct position translation. +template +Basic_position& operator+=(Basic_position& p1, Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return p1 = p1 + d2; +} + +/// Succinct position translation. +template +Basic_position& operator-=(Basic_position& p1, Basic_dimensions d2) + NOEXCEPT_(detail::has_nothrow_arithmetic()) +{ + return p1 = p1 - d2; +} + +/// Represents a positioned rectangle. +template +struct Basic_rectangle +{ + /// The coordinate type for the rectangle. This is an alias of + /// type parameter `T`. + using Coordinate = T; + /// A dimensions type having the same coordinate type as this rectangle + /// type. + using Dimensions = Basic_dimensions; + /// A position type having the same coordinate type as this rectangle + /// type. + using Position = Basic_position; + + Coordinate x; ///< The *x* coordinate of the upper-left vertex. + Coordinate y; ///< The *y* coordinate of the upper-left vertex. + Coordinate width; ///< The width of the rectangle in pixels. + Coordinate height; ///< The height of the rectangle in pixels. + + /// Converts a Basic_rectangle to another coordinate type. + template + Basic_rectangle into() const + NOEXCEPT_(detail::is_nothrow_convertible()) + { + return {U(x), U(y), U(width), U(height)}; + } + + /// Creates a Basic_rectangle given the position of its top left vertex + /// and its dimensions. + static Basic_rectangle from_top_left(Position tl, Dimensions dims) + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {tl.x, tl.y, dims.width, dims.height}; + } + + /// Creates a Basic_rectangle given the position of its top right vertex + /// and its dimensions. + static Basic_rectangle from_top_right(Position tr, Dimensions dims) + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return from_top_left(tr.left_by(dims.width), dims); + } + + /// Creates a Basic_rectangle given the position of its bottom left vertex + /// and its dimensions. + static Basic_rectangle from_bottom_left(Position bl, Dimensions dims) + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return from_top_left(bl.up_by(dims.height), dims); + } + + /// Creates a Basic_rectangle given the position of its bottom right vertex + /// and its dimensions. + static Basic_rectangle from_bottom_right(Position br, Dimensions dims) + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return from_top_left(br.up_left_by(dims), dims); + } + + /// Creates a Basic_rectangle given the position of its center + /// and its dimensions. + static Basic_rectangle from_center(Position center, Dimensions dims) + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return from_top_left(center.up_left_by(dims / Coordinate(2)), dims); + } + + /// The dimensions of the rectangle. Equivalent to + /// `Basic_dimensions{rect.width, rect.height}`. + Dimensions dimensions() const + NOEXCEPT_(detail::is_nothrow_convertible()) + { + return {width, height}; + } + + /// The position of the top left vertex. + Position top_left() const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return {x, y}; + } + + /// The position of the top right vertex. + Position top_right() const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return top_left().right_by(width); + } + + /// The position of the bottom left vertex. + Position bottom_left() const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return top_left().down_by(height); + } + + /// The position of the bottom right vertex. + Position bottom_right() const + NOEXCEPT_(detail::has_nothrow_arithmetic()) + { + return top_left().down_right_by(dimensions()); + } + + /// The position of the center of the rectangle. + Position center() const + NOEXCEPT_(detail::has_nothrow_arithmetic() && + detail::has_nothrow_division()) + { + return top_left().down_right_by(dimensions() / Coordinate(2)); + } + + class iterator; + + /// Returns an `iterator` to the top left corner of this rectangle. + iterator begin() const + { + return {top_left(), y, y + height}; + } + + /// Returns an `iterator` one past the end of this rectangle. + iterator end() const + { + return {top_left().right_by(width), y, y + height}; + } + +private: + friend Circle_sprite; + friend class detail::Render_sprite; + friend class detail::Renderer; + + /// Converts this rectangle to an internal SDL rectangle. + operator SDL_Rect() const + NOEXCEPT_(detail::is_nothrow_convertible()) + { + SDL_Rect result; + result.x = static_cast(x); + result.y = static_cast(y); + result.w = static_cast(width); + result.h = static_cast(height); + return result; + } +}; + +/// Type alias for the most common use of Basic_rectangle, which is with +/// a coordinate type of `int`. +using Rectangle = Basic_rectangle; + +/// Equality for rectangles. Note that this is naïve, in that it considers +/// empty rectangles with different positions to be different. +template +bool operator==(const Basic_rectangle& r1, + const Basic_rectangle& r2) + NOEXCEPT_(detail::is_nothrow_comparable()) +{ +return r1.x == r2.x && + r1.y == r2.y && + r1.width == r2.width && + r1.height == r2.height; +} + +/// Disequality for rectangles. +template +bool operator!=(const Basic_rectangle &r1, + const Basic_rectangle &r2) + NOEXCEPT_(detail::is_nothrow_comparable()) +{ + return !(r1 == r2); +} + +/// An iterator over the `Basic_position`s of a `Basic_rectangle`. +/// +/// Iterates in column-major order. +template +class Basic_rectangle::iterator + : public std::iterator< + std::input_iterator_tag, + typename Basic_rectangle::Position const > +{ +public: + + /// Returns the current `Position` of this iterator. + Position operator*() const + { + return current_; + } + + /// Returns a pointer to the current `Position` of this iterator. + Position const* operator->() const + { + return ¤t_; + } + + /// Pre-increments, advancing this iterator to the next `Position`. + iterator& operator++() + { + if (++current_.y >= y_end_) { + ++current_.x; + current_.y = y_begin_; + } + + return *this; + } + + /// Pre-decrements, retreating this iterator to the previous `Position`. + iterator& operator--() + { + if (current_.y == y_begin_) { + current_.y = y_end_; + --current_.x; + } + + --current_.y; + + return *this; + } + + /// Post-increments, advancing this iterator to the next `Position`. + iterator operator++(int) + { + iterator result(*this); + ++*this; + return result; + } + + /// Post-decrements, retreating this iterator to the previous `Position`. + iterator operator--(int) + { + iterator result(*this); + --*this; + return result; + } + + /// Compares whether two iterators are equal. Considers only the current + /// position, not the bounds of the stripe we're iterating through. + bool operator==(iterator const& that) const + { + return **this == *that; + } + + /// Iterator inequality. + bool operator!=(iterator const& that) const + { + return !(*this == that); + } + +private: + friend Basic_rectangle; + + iterator(Position current, Coordinate y_begin, Coordinate y_end) + : current_(current) + , y_begin_(y_begin) + , y_end_(y_end) + { } + + Position current_; + Coordinate y_begin_; + Coordinate y_end_; +}; + +/// A rendering transform, which can scale, flip, and rotate. A Transform +/// can be given to +/// Sprite_set::add_sprite(const Sprite&, Position, int, const Transform&) +/// to specify how a sprites::Sprite should be rendered. +/// +/// To construct a transform that does just one thing, you can use one of +/// the static factory functions: +/// +/// - Transform::rotation(double) +/// - Transform::flip_h() +/// - Transform::flip_v() +/// - Transform::scale(double) +/// - Transform::scale_x(double) +/// - Transform::scale_y(double) +/// +/// It is also possible to modify a transform with the setter functions +/// such as set_rotation(double) and set_scale(double). This can be used +/// to configure a transform that does more than one thing: +/// +/// ```cpp +/// Transform my_transform = +/// Transform{}.set_flip_h(true) +/// .set_flip_v(true) +/// .scale_x(2); +/// ``` +/// +class Transform +{ +public: + /// \name Constructor and factory functions + /// @{ + + /// Constructs the identity transform, which has no effect. + Transform() NOEXCEPT; + + /// Constructs a rotating transform, given the rotation in degrees + /// clockwise. + static Transform rotation(double) NOEXCEPT; + + /// Constructs a transform that flips the sprite horizontally. + static Transform flip_h() NOEXCEPT; + + /// Constructs a transform that flips the sprite vertically. + static Transform flip_v() NOEXCEPT; + + /// Constructs a transform that scales the sprite in both dimensions. + static Transform scale(double) NOEXCEPT; + + /// Constructs a transform that scales the sprite in the *x* dimension. + static Transform scale_x(double) NOEXCEPT; + + /// Constructs a transform that scales the sprite in the *y* dimension. + static Transform scale_y(double) NOEXCEPT; + + /// @} + + /// \name Setters + /// @{ + + /// Modifies this transform to have the given rotation, in degrees. + Transform& set_rotation(double) NOEXCEPT; + /// Modifies this transform to determine whether to flip horizontally. + Transform& set_flip_h(bool) NOEXCEPT; + /// Modifies this transform to determine whether to flip vertically. + Transform& set_flip_v(bool) NOEXCEPT; + /// Modifies this transform to scale the sprite by the given amount in + /// both dimensions. This overwrites the effect of previous calls to + /// set_scale_x(double) and set_scale_y(double). + Transform& set_scale(double) NOEXCEPT; + /// Modifies this transform to scale the sprite horizontally. This + /// overwrites the effect of previous calls to `set_scale(double)` + /// as well as itself. + Transform& set_scale_x(double) NOEXCEPT; + /// Modifies this transform to scale the sprite vertically. This + /// overwrites the effect of previous calls to `set_scale(double)` + /// as well as itself. + Transform& set_scale_y(double) NOEXCEPT; + + /// @} + + /// \name Getters + /// @{ + + /// Returns the rotation that will be applied to the sprite. + double get_rotation() const NOEXCEPT; + /// Returns whether the sprite will be flipped horizontally. + bool get_flip_h() const NOEXCEPT; + /// Returns whether the sprite will be flipped vertically. + bool get_flip_v() const NOEXCEPT; + /// Returns how much the sprite will be scaled horizontally. + double get_scale_x() const NOEXCEPT; + /// Returns how much the sprite will be scaled vertically. + double get_scale_y() const NOEXCEPT; + + /// @} + + /// \name Combining transforms + /// @{ + + /// Is this transformation the identity transformation that does nothing? + /// Because floating point is approximate, this may answer `false` for + /// transforms that are nearly the identity. But it should answer `true` + /// for any transform constructed by the default constructor Transform(). + bool is_identity() const NOEXCEPT; + + /// Composes two transforms to combine both of their effects. + Transform operator*(const Transform&) const NOEXCEPT; + + /// Returns the inverse of this transform. Composing a transform with its + /// inverse should result in the identity transformation, though because + /// floating point is approximate, is_identity() const may not actually + /// answer `true`. + Transform inverse() const NOEXCEPT; + + /// @} + +private: + double rotation_; + double scale_x_; + double scale_y_; + bool flip_h_; + bool flip_v_; +}; + +/// Equality for transforms. +bool operator==(const Transform&, const Transform&) NOEXCEPT; +/// Disequality for transforms. +bool operator!=(const Transform&, const Transform&) NOEXCEPT; + +} // end namespace geometry. + +} // end namespace ge211 + +// specializations in std: +namespace std +{ + +/// Template specialization to define hashing of Basic_position, +/// which allows storing them in a `std::unordered_set`, or using +/// them as keys in a `std::unordered_map`. +template +struct hash> +{ + /// Hashes a Basic_position, provided that T is hashable. + std::size_t operator()(ge211::Basic_position pos) const NOEXCEPT + { + return hash_t_(pos.x) * 31 ^ hash_t_(pos.y); + } + +private: + std::hash hash_t_; +}; + +} // end namespace std diff --git a/.cs211/lib/ge211/include/ge211_noexcept.hxx b/.cs211/lib/ge211/include/ge211_noexcept.hxx new file mode 100644 index 0000000..5158ddb --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_noexcept.hxx @@ -0,0 +1,13 @@ +#pragma once + +// This lets us disable `noexcept`. One reason to do this is +// to produce simpler API documentation. + +#ifdef NO_NOEXCEPT +# define NOEXCEPT_(...) +# define NOEXCEPT +#else +# define NOEXCEPT_(...) noexcept(__VA_ARGS__) +# define NOEXCEPT noexcept +#endif + diff --git a/.cs211/lib/ge211/include/ge211_random.hxx b/.cs211/lib/ge211/include/ge211_random.hxx new file mode 100644 index 0000000..0da2f18 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_random.hxx @@ -0,0 +1,150 @@ +#pragma once + +#include "ge211_forward.hxx" +#include +#include +#include +#include + +struct Random_test_access; + +namespace ge211 { + +namespace detail { + +template +struct Between +{ + static_assert( + std::is_integral::value || std::is_floating_point::value, + "Random::between: only works on built-in numeric types" + ); +}; + +template +struct Up_to { + static_assert( + std::is_integral::value || std::is_floating_point::value, + "Random::up_to: only works on built-in numeric types" + ); +}; + +template +struct Between::value>> + : std::uniform_int_distribution +{ + using std::uniform_int_distribution::uniform_int_distribution; +}; + +template +struct Between::value>> + : std::uniform_real_distribution +{ + using std::uniform_real_distribution::uniform_real_distribution; +}; + +template +struct Up_to::value>> + : Between +{ + explicit Up_to(T max) : Between{0, max - 1} + { } +}; + +template +struct Up_to::value>> + : Between +{ + explicit Up_to(T max) : Between{0, max} + { } +}; + +} // end namespace detail + +/// A pseudo-random number generator. +/// +/// This class has member functions for generating random numbers. +/// +/// Classes derived from Abstract_game can access an instance of +/// this class via Abstract_game::get_random(), which returns a +/// reference to a Random object maintained by Abstract_game. There +/// is no way for clients to construct their own instances of the +/// Random class. +class Random +{ +public: + /// Returns a random `T` between 0 (inclusive) and `max` (exclusive). + /// + /// Example: + /// + /// ```cpp + /// int roll_the_die(Random& random) + /// { + /// return random.up_to(6) + 1; + /// } + /// ``` + template + T up_to(T max) + { + return detail::Up_to{max}(generator_); + } + + /// Returns a random `T` between `min` and `max`. The right bound + /// is inclusive for integral types but exclusive for floating point + /// types. The left bound is always inclusive. + /// + /// Example: + /// + /// ```cpp + /// int roll_the_die(Random& random) + /// { + /// return random.between(1, 6); + /// } + /// ``` + template + T between(T min, T max) + { + return detail::Between{min, max}(generator_); + } + + /// Returns a random `T` from the whole range of `T`. + /// Only enabled for integral types `T`. + template < + class T, + class = std::enable_if_t::value> + > + T any() + { + return between(std::numeric_limits::min(), + std::numeric_limits::max()); + } + + /// Returns a random `bool` that is `true` with probability + /// `ptrue`. + bool random_bool(double ptrue = 0.5); + + /// Can't copy the random number generator. + Random(Random &) = delete; + + /// Can't copy the random number generator. + Random& operator=(Random &) = delete; + + /// Can't move the random number generator. + Random(Random &&) = delete; + + /// Can't move the random number generator. + Random& operator=(Random &&) = delete; + +private: + // Creator: + friend Abstract_game; + + // Random friend: + friend Random_test_access; + + Random(); + + std::mt19937_64 generator_; +}; + +} diff --git a/.cs211/lib/ge211/include/ge211_render.hxx b/.cs211/lib/ge211/include/ge211_render.hxx new file mode 100644 index 0000000..2ff7540 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_render.hxx @@ -0,0 +1,102 @@ +#pragma once + +#include "ge211_color.hxx" +#include "ge211_forward.hxx" +#include "ge211_geometry.hxx" +#include "ge211_noexcept.hxx" +#include "ge211_window.hxx" +#include "ge211_util.hxx" + +#include +#include + +#include + +namespace ge211 { + +namespace detail { + +using Uniq_SDL_Renderer = delete_ptr; +using Uniq_SDL_Surface = delete_ptr; +using Uniq_SDL_Texture = delete_ptr; + +class Renderer +{ +public: + explicit Renderer(const Window&); + + bool is_vsync() const NOEXCEPT; + + void set_color(Color); + + void clear(); + void copy(const Texture&, Position); + void copy(const Texture&, Position, const Transform&); + + // Prepares a texture for rendering with this given renderer, without + // actually copying it. + void prepare(const Texture&) const; + + void present() NOEXCEPT; + +private: + friend Texture; + + Borrowed get_raw_() const NOEXCEPT; + + static Owned create_renderer_(Borrowed); + + Uniq_SDL_Renderer ptr_; +}; + +// A texture is initially created as a (device-independent) `SDL_Surface`, +// and then turned into an `SDL_Texture` the first time it gets rendered. +// The SDL_Texture is cached and the original `SDL_Surface` is deleted. +class Texture +{ +public: + // An empty texture; don't render this or even ask for its dimensions. + Texture() NOEXCEPT; + + // Takes ownership of the `SDL_Surface` and will delete it. + // + // \preconditions + // - The surface is not zero-sized. + explicit Texture(Owned surface); + explicit Texture(Uniq_SDL_Surface); + + Dimensions dimensions() const NOEXCEPT; + + // Returns nullptr if this `Texture` has been rendered, and can no + // longer be updated as an `SDL_Surface`. + Borrowed as_surface() NOEXCEPT; + + bool empty() const NOEXCEPT; + +private: + friend Renderer; + + struct Impl_ + { + Impl_(Owned) NOEXCEPT; + Impl_(Owned) NOEXCEPT; + + Impl_(Uniq_SDL_Surface) NOEXCEPT; + Impl_(Uniq_SDL_Texture) NOEXCEPT; + + Uniq_SDL_Surface surface_; + Uniq_SDL_Texture texture_; + // Invariant: + // - Exactly one surface_ and texture_ is non-null. + // - Whichever is non-null is non-zero-sized. + // Note: impl_ below is null for the empty Texture. + }; + + Borrowed get_raw_(const Renderer&) const; + + std::shared_ptr impl_; +}; + +} // end namespace detail + +} diff --git a/.cs211/lib/ge211/include/ge211_resource.hxx b/.cs211/lib/ge211/include/ge211_resource.hxx new file mode 100644 index 0000000..cdd770d --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_resource.hxx @@ -0,0 +1,71 @@ +#pragma once + +#include "ge211_forward.hxx" +#include "ge211_noexcept.hxx" +#include "ge211_util.hxx" +#include "ge211_error.hxx" + +#include + +// Copied from SDL_ttf.h in order to avoid getting including all of +// SDL.h from the GE211 headers. +extern "C" { +extern DECLSPEC void SDLCALL TTF_CloseFont(TTF_Font*); +} + +#include +#include + +namespace ge211 { + +namespace detail { + +class File_resource +{ +public: + explicit File_resource(const std::string&); + + Borrowed get_raw() const NOEXCEPT { return ptr_.get(); } + + Owned release() && { return ptr_.release(); } + +private: + static void close_rwops_(Owned); + + delete_ptr ptr_; +}; + +} // end namespace detail + +/// Represents a font that can be used to render a sprites::Text_sprite. +/// To create a font, you must specify the TrueType font file (`.ttf`) to +/// load, and that file must be in the `Resources/` directory of your +/// project. You can create multiple Font instances for the same font +/// file at different sizes. +/// +/// One TTF file, `sans.tff`, is included among %ge211's built-in resources, +/// and can always be used even if you haven't added any fonts yourself. +/// +/// Note that Font%s cannot be constructed until the text subsystem is +/// initialized. The text subsystem is initialized with the rest of ge211 +/// when the Abstract_game underlying your game struct or class is +/// constructed. Thus, you cannot create namespace level (or global) +/// Font%s. The usual place to define Font%s is as member variables in +/// your game struct, since member variables of a derived class are +/// initialized after the base class is initialized. +class Font +{ +public: + /// Loads a font from the specified TrueType font file, at the specified + /// size. + Font(const std::string& filename, int size); + +private: + friend Text_sprite; + + Borrowed get_raw_() const NOEXCEPT { return ptr_.get(); } + + detail::delete_ptr ptr_; +}; + +} diff --git a/.cs211/lib/ge211/include/ge211_session.hxx b/.cs211/lib/ge211/include/ge211_session.hxx new file mode 100644 index 0000000..147bafa --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_session.hxx @@ -0,0 +1,62 @@ +#pragma once + +#include + +namespace ge211 { + +namespace detail { + +struct PINNED +{ + PINNED() = default; + + PINNED(PINNED&&) = delete; + PINNED& operator=(PINNED&&) = delete; + PINNED(const PINNED&) = delete; + PINNED& operator=(const PINNED&) = delete; +}; + +struct Sdl_session : PINNED +{ + Sdl_session(); + ~Sdl_session(); +}; + +struct Img_session : PINNED +{ + Img_session(); + ~Img_session(); +}; + +struct Ttf_session : PINNED +{ + Ttf_session(); + ~Ttf_session(); +}; + +struct Text_input_session : PINNED +{ + Text_input_session(); + ~Text_input_session(); +}; + +class Session +{ +public: + Session(); + ~Session(); + + static void check_session(const char*); + +private: + Sdl_session sdl_; + Img_session img_; + Ttf_session ttf_; + Text_input_session text_input_; + + static std::atomic session_count_; +}; + +} // end namespace detail + +} diff --git a/.cs211/lib/ge211/include/ge211_sprites.hxx b/.cs211/lib/ge211/include/ge211_sprites.hxx new file mode 100644 index 0000000..90d774e --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_sprites.hxx @@ -0,0 +1,405 @@ +#pragma once + +#include "ge211_color.hxx" +#include "ge211_forward.hxx" +#include "ge211_geometry.hxx" +#include "ge211_noexcept.hxx" +#include "ge211_time.hxx" +#include "ge211_render.hxx" +#include "ge211_resource.hxx" + +#include +#include + +namespace ge211 { + +/// Sprites are images that can be rendered to the screen. This namespace +/// defines a base Sprite class that declares common sprite operations, +/// and four specific types of sprites with different purposes. +namespace sprites { + +/// A sprite is an image that knows how to render itself to the screen at +/// a given location, under a particular transformation. You cannot create +/// a Sprite object directly, but must create one of its derived classes, +/// such as Image_sprite or Rectangle_sprite. +/// You can find out any sprite's dimensions with the +/// Sprite::dimensions() const member function. +/// Specific derived classes of Sprite, such as Rectangle_sprite, may have +/// more specific operations. +/// +/// \internal +/// A sprite is anything with dimensions that knows how to render itself +/// at a given `Position`. Derived classes need to implement both the +/// public `dimensions` function and the private `render` function. +class Sprite +{ +public: + /// Returns the current dimensions of this Sprite. + /// + /// When deriving from Sprite to implement your own kind of sprite + /// (or when deriving from Multiplexed_sprite), you need to make sure + /// that this returns the *maximum* dimensions that the sprite could + /// render at. For animated sprites, it's usually best if all the + /// frames have the same dimensions anyway. + virtual Dimensions dimensions() const = 0; + + virtual ~Sprite() {} + +private: + friend class detail::Engine; + friend struct detail::Placed_sprite; + friend Multiplexed_sprite; + + virtual void render(detail::Renderer&, + Position, + Transform const&) const = 0; + + virtual void prepare(detail::Renderer const&) const {} +}; + +} // end namespace sprites + +namespace detail { + +// A `Texture_sprite` is a `Sprite` that can be rendered by copying +// a texture. Instead of specifying how to render themselves directly, +// derived classes must specify how to get a `Texture` representing +// the sprite. The dimensions of the sprite are the dimensions of the +// resulting texture. The return type of `Texture const&` means that +// `get_texture_` cannot just create and return a texture, but must +// store it somewhere. This is because `Texture`s will usually be +// cached. (Otherwise, you wouldn't use a `Texture_sprite`.) +class Texture_sprite : public Sprite +{ +public: + Dimensions dimensions() const override; + +private: + void render(detail::Renderer&, Position, Transform const&) const override; + void prepare(detail::Renderer const&) const override; + + virtual Texture const& get_texture_() const = 0; +}; + +// A `Render_sprite` works by allowing its derived classes to render +// themselves onto an `SDL_Surface`, which it creates. It then converts +// that surface to a `Texture`, which it caches. +// +// The constructor of the derived class should pass the required +// dimensions to the `Render_sprite` constructor. Then, in its own +// constructor, use `as_surface` to access the underlying surface, and +// render the sprite image to that surface. +class Render_sprite : public Texture_sprite +{ +protected: + /// \preconditions + /// - Both dimensions must be positive. + explicit Render_sprite(Dimensions); + + /// Fills the whole surface with the given color. + /// This should only be called from the derived class's constructor. + void fill_surface(Color); + + /// Fills the given rectangle in the given color. + /// This should only be called from the derived class's constructor. + void fill_rectangle(Rectangle, Color); + + /// Sets one pixel to the given color. + /// This should only be called from the derived class's constructor. + void set_pixel(Position, Color); + +private: + Texture texture_; + Texture const& get_texture_() const override; + + /// This is called by fill_surface/fill_rectangle/set_pixel, so + /// it should only be called during the derived class's constructor. + SDL_Surface& as_surface(); + + static Uniq_SDL_Surface create_surface_(Dimensions); +}; + +} // end namespace detail + +namespace sprites { + +/// A Sprite that renders as a solid rectangle. +class Rectangle_sprite : public detail::Render_sprite +{ +public: + /// Constructs a rectangle sprite from required Dimensions + /// and an optional Color, which defaults to white. + /// + /// \preconditions + /// - both dimensions must be positive + explicit Rectangle_sprite(Dimensions, Color = Color::white()); + + /// Changes the color of this rectangle sprite. + void recolor(Color); +}; + +/// A Sprite that renders as a solid circle. +class Circle_sprite : public detail::Render_sprite +{ +public: + /// Constructs a circle sprite from its radius and optionally + /// a Color, which defaults to white. Note that when positioned, + /// the reference point is the upper-left corner of the bounding + /// box of the sprite, not the center of the circle. + /// + /// \preconditions + /// - radius must be positive + explicit Circle_sprite(int radius, Color = Color::white()); + + /// Changes the color of this circle sprite. + void recolor(Color); + +private: + int radius_() const; +}; + +/// A Sprite that displays a bitmap image. +class Image_sprite : public detail::Texture_sprite +{ +public: + /// Constructs an image sprite, given the filename of the + /// image to display. The image must be saved in the project's + /// `Resources/` directory. Many image formats are supported, + /// including JPEG, PNG, GIF, BMP, etc. + explicit Image_sprite(std::string const& filename); + +private: + detail::Texture const& get_texture_() const override; + + static detail::Texture load_texture_(std::string const& filename); + + detail::Texture texture_; +}; + +/// A Sprite that displays text. +class Text_sprite : public detail::Texture_sprite +{ +public: + /// Constructs an empty text sprite. This is useful when you + /// don't know the message at the point where the sprite is created, + /// but note that passing an empty text sprite to + /// Sprite_set::add_sprite(Sprite const&, Position, int) is an error. + Text_sprite(); + + /// Constructs a white text sprite with the given text and font. + /// For more control (color, wrapping, turning off anti-aliasing), + /// use the Builder API instead. + /// + /// While it is okay to construct a text sprite with no text, it + /// cannot be rendered into a scene. Use empty() const to check + /// if you haven't kept track. + Text_sprite(std::string const&, Font const&); + + /// Is this Text_sprite empty? (If so, you shouldn't try to use + /// it.) + bool empty() const; + + /// Is this Text_sprite non-empty (and thus renderable)? + operator bool() const; + + // Defined below. + class Builder; + + /// Resets this text sprite with the configuration from the given Builder. + void reconfigure(Builder const&); + +private: + explicit Text_sprite(Builder const&); + + void assert_initialized_() const; + + detail::Texture const& get_texture_() const override; + + static detail::Texture create_texture(Builder const&); + + detail::Texture texture_; +}; + +/// Builder-style API for configuring and constructing Text_sprite%s. +/// The idea is that a Text_sprite::Builder allows configuring a +/// Text_sprite in detail before actually constructing it. For example: +/// +/// ```cpp +/// Font sans("sans.ttf", 24); +/// +/// Text_sprite sprite = +/// Text_sprite::Builder(sans) +/// .message("Hello, world!") +/// .color(Color::medium_red()) +/// .build(); +/// ``` +class Text_sprite::Builder +{ +public: + /// \name Constructor and builder + /// @{ + + /// Constructs a new Text_sprite::Builder with the given Font. + explicit Builder(Font const&); + + /// Builds the configured Text_sprite. + Text_sprite build() const; + + /// @} + + /// \name Builder-style setters + /// @{ + + /// Adds to the builder's message. This takes any printable type + /// and prints it à la `operator<<`. Returns the builder, for call + /// chaining. + template + Builder& add_message(T const& value) + { + message_ << value; + return *this; + } + + /// Adds to the builder's message. This is an alias for + /// add_message(const T&). + /// + /// For example: + /// + /// ```cpp + /// Text_sprite position_sprite(Position position) + /// { + /// Builder builder(font); + /// builder << "(" << position.x << ", " << position.y << ")"; + /// return builder.build(); + /// } + /// ``` + template + Builder& operator<<(T const& value) + { + return add_message(value); + } + + /// Replaces the configured message with the given message. + /// Returns a reference to the Builder for call chaining. + Builder& message(std::string const&); + /// Sets font to use. + /// Returns a reference to the Builder for call chaining. + Builder& font(Font const&); + /// Sets the color to use. + /// Returns a reference to the Builder for call chaining. + Builder& color(Color); + /// Sets whether to use anti-aliasing. Anti-aliasing, on by default, makes + /// text smoother but can make it take longer to render. + /// Returns a reference to the Builder for call chaining. + Builder& antialias(bool); + /// Sets the pixel width for wrapping the text. If set to 0, the text does not + /// wrap at all. Newlines in the text will produces newlines in the output + /// only if wrapping is on (non-zero). + /// Returns a reference to the Builder for call chaining. + Builder& word_wrap(int); + + /// @} + + /// \name Getters + /// @{ + + /// Gets the configured message. + std::string message() const; + /// Gets the font that will be used. + Font const& font() const; + /// Gets the color that will be used. + Color color() const; + /// Gets whether anti-aliasing will be used. + bool antialias() const; + /// Gets the wrapping width that will be used. + int word_wrap() const; + + /// @} + +private: + std::ostringstream message_; + const Font* font_; + Color color_; + bool antialias_; + uint32_t word_wrap_; +}; + +/// A Sprite that allows switching between other sprites based on the +/// time at rendering. +class Multiplexed_sprite : public Sprite +{ +public: + /// Resets the age of the sprite to 0. + void reset(); + +protected: + /// Override this to specify what sprite to render, based on the + /// age of this sprite. This can be used to implement animation. + virtual const Sprite& select_(Duration age) const = 0; + +private: + void render(detail::Renderer& renderer, Position position, + Transform const& transform) const override; + + Timer since_; +}; + +} // end namespace sprites + +namespace detail { + +struct Placed_sprite +{ + const Sprite* sprite; + Position xy; + int z; + Transform transform; + + Placed_sprite(Sprite const&, Position, int, Transform const&) NOEXCEPT; + + void render(Renderer&) const; +}; + +bool operator<(Placed_sprite const&, Placed_sprite const&) NOEXCEPT; + +} // end namespace detail + +/// A collection of positioned sprites ready to be rendered to the screen. Each +/// time Abstract_game::draw(Sprite_set&) is called by the game engine, it is +/// given an empty Sprite_set, and it must add every sprites::Sprite that +/// should appear on the screen to that Sprite_set. Each Sprite is added +/// with an x–y geometry::Position and a z +/// coordinate that determines stacking order. Each sprite may have a +/// geometry::Transform applied as well. +/// +/// \sa add_sprite(Sprite const&, Position, int) +/// \sa add_sprite(Sprite const&, Position, int, Transform const&) +class Sprite_set +{ +public: + /// Adds the given sprite at the given x–y geometry::Position and optional z + /// coordinate, which defaults to 0. + /// Sprites with higher `z` values will be rendered on top of those with + /// lower `z` values. Two sprites with the same `z` value that interfere + /// will be stacked in an arbitrary order, so if you care about the layering + /// of your sprites, provide different `z` values. + /// + /// Note that the Sprite_set does not copy the sprite it is given, but + /// just stores a reference to it. Thus, the Sprite must live somewhere + /// else, and continue to live until it is rendered. + Sprite_set& add_sprite(Sprite const&, Position, int z = 0); + + /// Adds the given sprite as the given geometry::Position and + /// z coordinate, to be rendered with the given geometry::Transform. The + /// transform allows scaling, flipping, and rotating the Sprite when + /// rendered. + Sprite_set& add_sprite(Sprite const&, Position, int z, Transform const&); + +private: + friend class detail::Engine; + + Sprite_set(); + std::vector sprites_; +}; + +} diff --git a/.cs211/lib/ge211/include/ge211_time.hxx b/.cs211/lib/ge211/include/ge211_time.hxx new file mode 100644 index 0000000..11caf2c --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_time.hxx @@ -0,0 +1,402 @@ +#pragma once + +#include "ge211_forward.hxx" + +#include +#include +#include + +namespace ge211 { + +namespace detail { + +using Clock = std::conditional_t< + std::chrono::high_resolution_clock::is_steady, + std::chrono::high_resolution_clock, + std::chrono::steady_clock>; + +} // end namespace detail + +/// Types for representing time and timers. +namespace time { + +/// A length of time. This is an opaque type representing +/// a high-precision segment of time that can be extracted +/// as a `double` of seconds by the +/// seconds() const member function. +/// A Duration can be constructed from the number of seconds, +/// also as a `double`. Duration values can also be compared, +/// added, subtracted, and added and subtracted to Time_point values. +class Duration +{ +public: + /// Constructs the zero duration. + Duration() : duration_{} {} + + /// Constructs the duration of the given number of seconds. + explicit Duration(double seconds) + : Duration{std::chrono::duration{seconds}} {} + + /// Gets this duration in seconds. + double seconds() const + { + auto seconds = + std::chrono::duration_cast>( + duration_); + return seconds.count(); + } + + /// Gets this duration, approximately, in milliseconds. + long milliseconds() const + { + auto millis = + std::chrono::duration_cast>( + duration_); + return millis.count(); + } + + /// \name Comparisons + ///@{ + + /// Does this Duration equal another one? + bool operator==(Duration other) const + { + return duration_ == other.duration_; + } + + /// Does this Duration NOT equal another one? + bool operator!=(Duration other) const + { + return duration_ != other.duration_; + } + + /// Less-than for Duration. + bool operator<(Duration other) const + { + return duration_ < other.duration_; + } + + /// Less-than-or-equal-to for Duration. + bool operator<=(Duration other) const + { + return duration_ <= other.duration_; + } + + /// Greater-than for Duration. + bool operator>(Duration other) const + { + return duration_ > other.duration_; + } + + /// Greater-than-or-equal-to for Duration. + bool operator>=(Duration other) const + { + return duration_ >= other.duration_; + } + + ///@} + + ///\name Arithmetic + ///@{ + + /// Addition for Duration. + Duration operator+(Duration other) const + { + return {duration_ + other.duration_}; + } + + /// Subtraction for Duration. + Duration operator-(Duration other) const + { + return {duration_ - other.duration_}; + } + + /// Multiplication for Duration. + Duration operator*(double factor) const + { + return {duration_ * factor}; + } + + /// Division for Duration. + Duration operator/(double factor) const + { + return {duration_ / factor}; + } + + /// Addition for Duration. + Duration& operator+=(Duration other) + { + return *this = *this + other; + } + + /// Subtraction for Duration. + Duration& operator-=(Duration other) + { + return *this = *this - other; + } + + /// Multiplication for Duration. + Duration& operator*=(double factor) + { + return *this = *this * factor; + } + + /// Division for Duration. + Duration& operator/=(double factor) + { + return *this = *this / factor; + } + + ///@} + +private: + friend Time_point; + friend class detail::Engine; + + Duration(std::chrono::duration duration) + : Duration{std::chrono::duration_cast + (duration)} {} + + Duration(detail::Clock::duration duration) + : duration_{duration} {} + + void sleep_for() const + { + std::this_thread::sleep_for(duration_); + } + + detail::Clock::duration duration_; +}; + +/// A point in time. Time_point values can be compared; they cannot +/// be added, but they can be subtracted to produce Duration values, +/// and they can be shifted by Duration values. +class Time_point +{ +public: + /// Constructs the zero time point (the epoch). + Time_point() : time_point_{} {} + + /// Returns the current time. + static Time_point now() { return Time_point(detail::Clock::now()); } + + /// \name Comparisons + ///@{ + + /// Equality for Time_point. + bool operator==(Time_point other) const + { + return time_point_ == other.time_point_; + } + + /// Disequality for Time_point. + bool operator!=(Time_point other) const + { + return time_point_ != other.time_point_; + } + + /// Is this Time_point earlier than that one? + bool operator<(Time_point other) const + { + return time_point_ < other.time_point_; + } + + /// Is this Time_point earlier than or equal to that one? + bool operator<=(Time_point other) const + { + return time_point_ <= other.time_point_; + } + + /// Is this Time_point later than that one? + bool operator>(Time_point other) const + { + return time_point_ > other.time_point_; + } + + /// Is this Time_point later than or equal to that one? + bool operator>=(Time_point other) const + { + return time_point_ >= other.time_point_; + } + + ///@} + + ///\name Arithmetic + ///@{ + + /// Finds the Duration between one Time_point and another. + Duration operator-(Time_point other) const + { + return Duration{time_point_ - other.time_point_}; + } + + /// Offsets a Time_point by adding a Duration. + Time_point operator+(Duration duration) const + { + return Time_point{time_point_ + duration.duration_}; + } + + /// Offsets a Time_point subtracting by a Duration. + Time_point operator-(Duration duration) const + { + return Time_point{time_point_ - duration.duration_}; + } + + /// Offsets a Time_point by adding on a Duration. + Time_point& operator+=(Duration duration) + { + return *this = *this + duration; + } + + /// Offsets a Time_point subtracting off a Duration. + Time_point& operator-=(Duration duration) + { + return *this = *this - duration; + } + + ///@} + +private: + Time_point(detail::Clock::time_point time_point) + : time_point_{time_point} {} + + detail::Clock::time_point time_point_; +}; + +/// A class for timing intervals. The result is a Duration. +class Timer +{ +public: + /// Creates a new timer, running from the time it was created. + Timer() : start_time_{now_()} {} + + /// Creates a timer whose “start time” is some Duration in the future. + /// Suppose we want to wait 30 seconds and then do something. We could + /// create a Timer whose “start time” is in 30 seconds, and then poll it + /// on each frame. When it returns a non-negative number for its + /// elapsed_time() const, we know that the time as arrived. + static Timer future(Duration duration) + { + Timer result; + result.start_time_ += duration; + return result; + } + + /// Resets a timer, returning the time it was at before it was reset. + Duration reset() + { + Time_point previous = start_time_; + start_time_ = now_(); + return start_time_ - previous; + } + + /** Returns the actual time when this timer was started or most recently + * reset. + */ + Time_point start_time() const + { + return start_time_; + } + + /** Returns how much time has elapsed since this timer was started or + * most recently reset. + */ + Duration elapsed_time() const + { + return now_() - start_time_; + } + +private: + Time_point start_time_; + + static Time_point now_() { return Time_point::now(); } +}; + +/// A class for timing intervals while supporting pausing. +class Pausable_timer +{ +public: + /// Constructs a new pausable timer. The timer is started running by + /// default, but can be started paused by passing `true`. + explicit Pausable_timer(bool start_paused = false) + { + is_paused_ = start_paused; + + if (is_paused_) + elapsed_time_ = Duration{}; + else + fake_start_time_ = now_(); + } + + /// Checks whether the timer is currently paused. + bool is_paused() const + { + return is_paused_; + } + + /** The elapsed time since the start or most recent reset, not counting + * paused times. + */ + Duration elapsed_time() const + { + if (is_paused_) { + return elapsed_time_; + } else { + return now_() - fake_start_time_; + } + } + + /// Pauses the timer. If the timer is already paused, has no effect. In + /// either case, the elapsed time thus far is saved, and can be queried + /// with elapsed_time() const, or will continue to accumulate if we + /// unpause(). + Duration pause() + { + if (!is_paused_) { + elapsed_time_ = now_() - fake_start_time_; + is_paused_ = true; + } + + return elapsed_time_; + } + + /// Unpauses the timer. If the timer is already running, has no effect. + void resume() + { + if (is_paused_) { + fake_start_time_ = now_() - elapsed_time_; + is_paused_ = false; + } + } + + /// Resets the timer, returning the elapsed time since starting or the + /// most recent reset(). Leaves the pause state unchanged. + Duration reset() + { + if (is_paused_) { + auto result = elapsed_time_; + elapsed_time_ = Duration{}; + return result; + } else { + auto now = now_(); + auto result = now - fake_start_time_; + fake_start_time_ = now; + return result; + } + } + +private: + union + { + Time_point fake_start_time_; // when not paused + Duration elapsed_time_; // when paused + }; + bool is_paused_; + + static Time_point now_() { return Time_point::now(); } +}; + +} // end namespace time + +} + diff --git a/.cs211/lib/ge211/include/ge211_util.hxx b/.cs211/lib/ge211/include/ge211_util.hxx new file mode 100644 index 0000000..9f4f3b2 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_util.hxx @@ -0,0 +1,228 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ge211 { + +/// Type alias to indicate that the given pointer owns +/// its object. +template +using Owned = T*; + +/// Type alias to indicate that the given pointer does +/// not own its object. +template +using Borrowed = T*; + +/// Converts any printable type to a `std::string`. +template +std::string to_string(const T& value) +{ + std::ostringstream oss; + oss << value; + return oss.str(); +} + +// `detail` is for implementation details +namespace detail { + +template +using deleter_t = void (*)(Owned); + +template +void no_op_deleter(Owned) {} + +template +void c_heap_deleter(Owned o) +{ + std::free(o); +} + +template< + class T, + deleter_t Deleter = &c_heap_deleter, + bool Delete_null = false +> +class delete_ptr +{ +public: + using object_type = T; + using owned_pointer = Owned; + using borrowed_pointer = Borrowed; + using deleter_function_type = deleter_t; + + static constexpr deleter_function_type deleter_function = Deleter; + static constexpr bool delete_null_v = Delete_null; + + delete_ptr() noexcept + : ptr_(nullptr) { } + + explicit delete_ptr(owned_pointer ptr) noexcept + : ptr_(ptr) { } + + delete_ptr(std::nullptr_t) noexcept + : ptr_(nullptr) { } + + delete_ptr(delete_ptr const&) = delete; + delete_ptr& operator=(delete_ptr const&) = delete; + + delete_ptr(delete_ptr&& that) noexcept + : ptr_(that.release()) { } + + delete_ptr& operator=(delete_ptr&& that) noexcept + { + delete_it_(); + ptr_ = that.release(); + return *this; + } + + delete_ptr& operator=(owned_pointer that) noexcept + { + return *this = delete_ptr(that); + } + + ~delete_ptr() + { + delete_it_(); + } + + owned_pointer release() noexcept + { + return std::exchange(ptr_, nullptr); + } + + borrowed_pointer get() const noexcept + { + return ptr_; + } + + object_type& operator*() const + { + return *ptr_; + } + + borrowed_pointer operator->() const noexcept + { + return ptr_; + } + + operator bool() const noexcept + { + return ptr_ != nullptr; + } + + explicit operator std::unique_ptr() + && noexcept + { + return {release(), deleter_function}; + }; + + friend void swap(delete_ptr& a, delete_ptr& b) noexcept + { + std::swap(a.ptr_, b.ptr_); + } + +private: + void delete_it_() noexcept + { + if (delete_null_v || ptr_) + deleter_function(ptr_); + } + + owned_pointer ptr_; +}; + +template deleter> +bool operator==(delete_ptr const& a, + delete_ptr const& b) +{ + return a.get() == b.get(); +} + +template deleter> +bool operator!=(delete_ptr const& a, + delete_ptr const& b) +{ + return !(a == b); +} + +template +class lazy_ptr +{ +public: + using value = T; + using reference = T&; + using pointer = T*; + + lazy_ptr() + { } + + bool is_forced() const + { + return ptr_ != nullptr; + } + + reference operator*() const + { + force_(); + return *ptr_; + } + + pointer operator->() const + { + return std::addressof(operator*()); + } + +private: + mutable std::unique_ptr ptr_; + + void force_() const + { + if (!ptr_) + ptr_.reset(new value); + } +}; + +/// Can type `T` be converted to type `U` without risk of an exception? +template +constexpr bool is_nothrow_convertible() +{ + T t{}; + return noexcept(U(t)); +} + +/// Can type `T` be compared to itself without risk of an exception? +template +constexpr bool is_nothrow_comparable() +{ + T t{}; + return noexcept(t == t) && noexcept(t != t); +} + +/// Can types `T` and `U` be used for basic arithmetic (addition, +/// subtraction, multiplication) without risk of an exception? +template +constexpr bool has_nothrow_arithmetic() +{ + T t{}; + U u{}; + return noexcept(t + u) && noexcept(t - u) && noexcept(t * u); +} + +/// Can types `T` and `U` be used for division without risk of an exception? +template +constexpr bool has_nothrow_division() +{ + T t{}; + U u{}; + return noexcept(t / u); +} + +} // end namespace detail + +} // end namespace ge211 + diff --git a/.cs211/lib/ge211/include/ge211_version.hxx.in b/.cs211/lib/ge211/include/ge211_version.hxx.in new file mode 100644 index 0000000..d483e89 --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_version.hxx.in @@ -0,0 +1,5 @@ +#pragma once + +#define GE211_VERSION_MAJOR @ge211_VERSION_MAJOR@ +#define GE211_VERSION_MINOR @ge211_VERSION_MINOR@ +#define GE211_VERSION_PATCH @ge211_VERSION_PATCH@ diff --git a/.cs211/lib/ge211/include/ge211_window.hxx b/.cs211/lib/ge211/include/ge211_window.hxx new file mode 100644 index 0000000..69264ae --- /dev/null +++ b/.cs211/lib/ge211/include/ge211_window.hxx @@ -0,0 +1,80 @@ +#pragma once + +#include "ge211_forward.hxx" +#include "ge211_geometry.hxx" +#include "ge211_noexcept.hxx" +#include "ge211_util.hxx" + +#include +#include + +#include + +namespace ge211 { + +/// Provides access to the game window and its properties. +class Window +{ +public: + /// Returns the current dimensions of this window. + Dimensions get_dimensions() const NOEXCEPT; + /// Changes the size of the window. Throws exceptions::Environment_error + /// if the dimensions are negative or outside the allowable range. + void set_dimensions(Dimensions); + + /// Gets the position of the upper-left corner of the window with + /// respect to the upper-left corner of the screen. + Position get_position() const NOEXCEPT; + /// Sets the position of the upper-left corner of the window with + /// respect to the upper-left corner of the screen. + void set_position(Position); + /// A special value to pass to set_position(Position) to center + /// the window on the screen. + static const Position centered; + + /// Returns the current title of this window. + /// + /// The returned pointer should not be freed by the client. It is + /// borrowed from a buffer stored within the window, and should be + /// valid until the next call to set_title(const std::string&). + /// Copy it to a std::string if you need to hold it longer. + const char* get_title() const NOEXCEPT; + /// Changes the title of this window. + void set_title(const std::string&) NOEXCEPT; + +#if SDL_VERSION_ATLEAST(2, 0, 5) + /// Returns whether the user can resize this window. + bool get_resizeable() const NOEXCEPT; + /// Changes whether the user can resize this window. + void set_resizeable(bool) NOEXCEPT; +#endif + + /// Returns whether the program is in fullscreen mode. + bool get_fullscreen() const NOEXCEPT; + /// Sets whether the program should be in fullscreen mode. Throws + /// exceptions::Host_error if change fails. + void set_fullscreen(bool); + + /// Returns the maximum dimensions for a non-fullscreen window. + /// This is the size of the screen, minus space reserved for the + /// system (such as the Windows taskbar or Mac menu and dock). + Dimensions max_window_dimensions() const NOEXCEPT; + + /// Returns the maximum dimensions for a fullscreen window. Call + /// this before switching to fullscreen mode, since if you fullscreen + /// a smaller window, the video mode may change. + static Dimensions max_fullscreen_dimensions() NOEXCEPT; + +private: + friend class detail::Engine; + friend class detail::Renderer; + + Window(const std::string&, Dimensions dim); + + Borrowed get_raw_() const NOEXCEPT { return ptr_.get(); } + uint32_t get_flags_() const NOEXCEPT; + + detail::delete_ptr ptr_; +}; + +} diff --git a/.cs211/lib/ge211/scripts/autotools_install.sh b/.cs211/lib/ge211/scripts/autotools_install.sh new file mode 100755 index 0000000..9c0c197 --- /dev/null +++ b/.cs211/lib/ge211/scripts/autotools_install.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +set -e + +if [ -z "$PREFIX" ]; then + echo >&2 "$0: \$PREFIX must be set" + exit 1 +fi + +if [ $# != 1 ]; then + echo >&2 "Usage: $0 SRCURL" + exit 2 +fi + +srcurl="$1"; shift + +retry () { + "$@" && return + + local attempt + for attempt in 2 3; do + echo >&2 "Command failed: $*" + echo >&2 "Retrying in 5 seconds" + sleep 5 + "$@" && return + done + + false +} + +file="/tmp/$$-$(basename "$srcurl")" +retry wget --quiet -O "$file" "$srcurl" + +dir="/tmp/$(basename "$srcurl").d" +mkdir -p "$dir" +tar -xf "$file" --strip-components=1 -C "$dir" + +cd "$dir" +./configure --prefix "$PREFIX" +make -j 2 +make install + diff --git a/.cs211/lib/ge211/src/CMakeLists.txt b/.cs211/lib/ge211/src/CMakeLists.txt new file mode 100644 index 0000000..c452718 --- /dev/null +++ b/.cs211/lib/ge211/src/CMakeLists.txt @@ -0,0 +1,64 @@ +add_library(ge211 + ge211_base.cxx + ge211_color.cxx + ge211_engine.cxx + ge211_event.cxx + ge211_error.cxx + ge211_geometry.cxx + ge211_audio.cxx + ge211_random.cxx + ge211_render.cxx + ge211_resource.cxx + ge211_session.cxx + ge211_sprites.cxx + ge211_window.cxx) + +set_target_properties(ge211 + PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED On + CXX_EXTENSIONS Off + VERSION ${PROJECT_VERSION}) + +if(MSVC) + target_compile_options(ge211 PRIVATE /W4) +else(MSVC) + target_compile_options(ge211 PRIVATE -Wall -Wextra -pedantic) +endif(MSVC) + +include(GNUInstallDirs) + +target_include_directories(ge211 + SYSTEM INTERFACE + $ + $ + $ + ${SDL2_INCLUDE_DIRS}) + +target_include_directories(ge211 + PRIVATE + ../include + ${SDL2_INCLUDE_DIRS} + ${SDL2_IMAGE_INCLUDE_DIRS} + ${SDL2_MIXER_INCLUDE_DIRS} + ${SDL2_TTF_INCLUDE_DIRS}) + +target_link_libraries(ge211 + PUBLIC + ${SDL2_LIBRARIES} + PRIVATE + ${SDL2_IMAGE_LIBRARIES} + ${SDL2_MIXER_LIBRARIES} + ${SDL2_TTF_LIBRARIES} + utf8-cpp) + +set(GE211_RESOURCE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/../Resources/" + "${CMAKE_INSTALL_FULL_DATADIR}/ge211/" + CACHE STRING + "Where to search for GE211 resource files.") + +target_compile_definitions(ge211 + PRIVATE + GE211_RESOURCES="$\">") + diff --git a/.cs211/lib/ge211/src/ge211_audio.cxx b/.cs211/lib/ge211/src/ge211_audio.cxx new file mode 100644 index 0000000..d93f54c --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_audio.cxx @@ -0,0 +1,464 @@ +#include "ge211_audio.hxx" +#include "ge211_resource.hxx" +#include "ge211_session.hxx" + +#include +#include + +#include +#include + +namespace ge211 { + +using namespace detail; + +namespace audio { + +static inline int unit_to_volume(double unit_volume) +{ + return int(unit_volume * MIX_MAX_VOLUME); +} + +static inline double volume_to_unit(int int_volume) +{ + return int_volume / double(MIX_MAX_VOLUME); +} + +Audio_clip::Audio_clip() +{ + Session::check_session("Audio loading"); +} + +bool Audio_clip::try_load(const std::string& filename, const Mixer& mixer) +{ + return mixer.is_enabled() && real_try_load(filename, mixer); +} + +void Audio_clip::load(const std::string& filename, const Mixer& mixer) +{ + if (!try_load(filename, mixer)) + throw Mixer_error::could_not_load(filename); +} + +void Audio_clip::clear() +{ + real_clear(); +} + +Music_track::Music_track(const std::string& filename, const Mixer& mixer) +{ + load(filename, mixer); +} + +bool Music_track::real_try_load(const std::string& filename, const Mixer&) +{ + Mix_Music* raw = Mix_LoadMUS_RW(File_resource(filename).release(), 1); + if (raw) { + ptr_ = {raw, &Mix_FreeMusic}; + return true; + } else { + return false; + } +} + +void Music_track::real_clear() +{ + ptr_ = nullptr; +} + +bool Music_track::real_empty() const +{ + return ptr_ == nullptr; +} + +Sound_effect::Sound_effect(const std::string& filename, const Mixer& mixer) +{ + load(filename, mixer); +} + +bool Sound_effect::real_try_load(const std::string& filename, const Mixer&) +{ + Mix_Chunk* raw = Mix_LoadWAV_RW(File_resource(filename).release(), 1); + + if (raw) { + ptr_ = {raw, &Mix_FreeChunk}; + return true; + } else { + return false; + } +} + +void Sound_effect::real_clear() +{ + ptr_ = nullptr; +} + +bool Sound_effect::real_empty() const +{ + return ptr_ == nullptr; +} + +Mixer::Mixer() + : enabled_{0 == Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, + MIX_DEFAULT_FORMAT, + 2, + 4096)} + , channels_(MIX_CHANNELS) + , available_effect_channels_(MIX_CHANNELS) +{ + if (!enabled_) { + warn_sdl() << "Could not open audio device"; + return; + } + + int mix_want = MIX_INIT_OGG | MIX_INIT_MP3; + int mix_have = Mix_Init(mix_want); + if (mix_have == 0) { + warn_sdl() << "Could not initialize audio mixer"; + } else if ((mix_have & mix_want) != mix_want) { + warn_sdl() << "Could not initialize all audio formats"; + } + + int music_decoders = Mix_GetNumMusicDecoders(); + info_sdl() << "Number of music decoders is " << music_decoders; + for (int i = 0; i < music_decoders; ++i) { + info_sdl() << " [" << i << "] " << Mix_GetMusicDecoder(i); + } + + int chunk_decoders = Mix_GetNumChunkDecoders(); + info_sdl() << "Number of chunk decoders is " << chunk_decoders; + for (int i = 0; i < chunk_decoders; ++i) { + info_sdl() << " [" << i << "] " << Mix_GetChunkDecoder(i); + } +} + +Mixer::~Mixer() +{ + if (enabled_) { + Mix_Quit(); + Mix_CloseAudio(); + } +} + + +void Mixer::play_music(Music_track music) +{ + attach_music(std::move(music)); + resume_music(); +} + +void Mixer::attach_music(Music_track music) +{ + switch (music_state_) { + case State::paused: + case State::detached: + break; + + case State::playing: + throw Client_logic_error("Mixer::attach_music: still playing"); + + case State::fading_out: + throw Client_logic_error("Mixer::attach_music: fading out"); + } + + current_music_ = std::move(music); + + if (current_music_) { + music_state_ = State::paused; + } else { + music_state_ = State::detached; + } +} + +void Mixer::resume_music(Duration fade_in) +{ + switch (music_state_) { + case State::detached: + throw Client_logic_error("Mixer::resume_music: no music attached"); + + case State::paused: + Mix_RewindMusic(); + Mix_FadeInMusicPos(current_music_.ptr_.get(), + 0, + int(fade_in.milliseconds()), + music_position_.elapsed_time().seconds()); + music_position_.resume(); + music_state_ = State::playing; + break; + + case State::fading_out: + throw Client_logic_error("Mixer::resume_music: fading out"); + + case State::playing: + // idempotent + break; + } +} + +void Mixer::pause_music(Duration fade_out) +{ + switch (music_state_) { + case State::detached: + throw Client_logic_error("Mixer::pause_music: no music attached"); + + case State::paused: + // Idempotent + break; + + case State::fading_out: + throw Client_logic_error("Mixer::pause_music: fading out"); + + case State::playing: + if (fade_out == Duration(0)) { + Mix_HaltMusic(); + music_position_.pause(); + music_state_ = State::paused; + } else { + Mix_FadeOutMusic(int(fade_out.milliseconds())); + music_state_ = State::fading_out; + } + break; + } +} + +void Mixer::rewind_music() +{ + switch (music_state_) { + case State::paused: + music_position_.reset(); + break; + + case State::detached: + case State::playing: + case State::fading_out: + throw Client_logic_error( + "Mixer::rewind_music: must be paused"); + } +} + +const Sound_effect& Sound_effect_handle::get_effect() const +{ + return ptr_->effect; +} + +Mixer::State Sound_effect_handle::get_state() const +{ + return ptr_->state; +} + +int Mixer::find_empty_channel_() const +{ + auto iter = std::find_if(channels_.begin(), + channels_.end(), + [](auto& h) { return h.empty(); }); + + if (iter == channels_.end()) + return -1; + else + return (int) std::distance(channels_.begin(), iter); +} + +void Mixer::poll_channels_() +{ + if (!enabled_) return; + + if (current_music_) { + if (!Mix_PlayingMusic()) { + switch (music_state_) { + case State::detached: + case State::paused: + break; + + case State::playing: + music_position_.pause(); + music_position_.reset(); + music_state_ = State::paused; + break; + + case State::fading_out: + music_position_.pause(); + music_state_ = State::paused; + break; + } + } + } + + for (int channel = 0; channel < int(channels_.size()); ++channel) { + if (channels_[channel] && !Mix_Playing(channel)) + { + unregister_effect_(channel); + } + } +} + +Sound_effect_handle +Mixer::play_effect(Sound_effect effect, double volume) +{ + if (!enabled_) throw Mixer_error::not_enabled(); + + auto handle = try_play_effect(std::move(effect), volume); + if (!handle) throw Mixer_error::out_of_channels(); + + return handle; +} + +Sound_effect_handle +Mixer::try_play_effect(Sound_effect effect, double volume) +{ + if (!enabled_) return {}; + + int channel = find_empty_channel_(); + if (channel < 0) return {}; + + Mix_Volume(channel, unit_to_volume(volume)); + Mix_PlayChannel(channel, effect.ptr_.get(), 0); + + return register_effect_(channel, std::move(effect)); +} + +void Sound_effect_handle::resume() +{ + switch (ptr_->state) { + case Mixer::State::detached: + throw Client_logic_error("Sound_effect_handle::resume: detached"); + + case Mixer::State::paused: + ptr_->state = Mixer::State::playing; + Mix_Resume(ptr_->channel); + break; + + case Mixer::State::playing: + // idempotent + break; + + case Mixer::State::fading_out: + throw Client_logic_error("Sound_effect_handle::resume: fading out"); + } +} + +void Sound_effect_handle::pause() +{ + switch (ptr_->state) { + case Mixer::State::detached: + throw Client_logic_error("Sound_effect_handle::pause: detached"); + + case Mixer::State::paused: + // idempotent + break; + + case Mixer::State::playing: + ptr_->state = Mixer::State::paused; + Mix_Pause(ptr_->channel); + break; + + case Mixer::State::fading_out: + throw Client_logic_error("Sound_effect_handle::pause: fading out"); + } +} + +void Sound_effect_handle::stop() +{ + switch (ptr_->state) { + case Mixer::State::detached: + throw Client_logic_error("Sound_effect_handle::stop: detached"); + + case Mixer::State::paused: + ptr_->mixer.unregister_effect_(ptr_->channel); + Mix_HaltChannel(ptr_->channel); + break; + + case Mixer::State::playing: + ptr_->mixer.unregister_effect_(ptr_->channel); + Mix_HaltChannel(ptr_->channel); + break; + + case Mixer::State::fading_out: + throw Client_logic_error("Sound_effect_handle::stop: fading out"); + } +} + +void Mixer::pause_all_effects() +{ + Mix_Pause(-1); + + for (const auto& handle : channels_) { + if (handle && handle.ptr_->state == State::playing) + handle.ptr_->state = State::paused; + } +} + +void Mixer::resume_all_effects() +{ + Mix_Resume(-1); + + for (const auto& handle : channels_) { + if (handle && handle.ptr_->state == State::paused) + handle.ptr_->state = State::playing; + } +} + +int Mixer::available_effect_channels() const +{ + return available_effect_channels_; +} + +Sound_effect_handle +Mixer::register_effect_(int channel, Sound_effect effect) +{ + assert(!channels_[channel]); + channels_[channel] = Sound_effect_handle(*this, std::move(effect), channel); + --available_effect_channels_; + return channels_[channel]; +} + +void Mixer::unregister_effect_(int channel) +{ + assert(channels_[channel]); + channels_[channel].ptr_->state = State::detached; + channels_[channel] = {}; + ++available_effect_channels_; +} + +double Mixer::get_music_volume() const +{ + return volume_to_unit(Mix_VolumeMusic(-1)); +} + +void Mixer::set_music_volume(double unit_value) +{ + Mix_VolumeMusic(unit_to_volume(unit_value)); +} + +bool Sound_effect_handle::empty() const +{ + return ptr_ == nullptr; +} + +Sound_effect_handle::operator bool() const +{ + return !empty(); +} + +Sound_effect_handle::Sound_effect_handle(Mixer& mixer, + Sound_effect effect, + int channel) + : ptr_(std::make_shared(mixer, std::move(effect), channel)) +{ } + +double Sound_effect_handle::get_volume() const +{ + if (ptr_->state == Mixer::State::detached) + return 0; + else + return volume_to_unit(Mix_Volume(ptr_->channel, -1)); +} + +void Sound_effect_handle::set_volume(double unit_value) +{ + if (ptr_->state != Mixer::State::detached) + Mix_Volume(ptr_->channel, unit_to_volume(unit_value)); +} + +} // end namespace audio + +} // end namespace ge211 diff --git a/.cs211/lib/ge211/src/ge211_base.cxx b/.cs211/lib/ge211/src/ge211_base.cxx new file mode 100644 index 0000000..e71c47d --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_base.cxx @@ -0,0 +1,92 @@ +#include "ge211_base.hxx" +#include "ge211_engine.hxx" +#include "ge211_error.hxx" + +#include + +namespace ge211 { + +using namespace detail; + +// Storage for constexprs, just in case. +const Dimensions Abstract_game::default_window_dimensions{800, 600}; +const char* const Abstract_game::default_window_title = "ge211 window"; +const Color Abstract_game::default_background_color = Color::black(); + +// How many frames to run before calculating the frame rate. +static int const frames_per_sample = 30; + +Dimensions Abstract_game::initial_window_dimensions() const +{ + return default_window_dimensions; +} + +std::string Abstract_game::initial_window_title() const +{ + return default_window_title; +} + +void Abstract_game::run() +{ + Engine(*this).run(); +} + +void Abstract_game::quit() NOEXCEPT +{ + quit_ = true; +} + +Window& Abstract_game::get_window() const +{ + if (engine_) return engine_->get_window(); + + throw Client_logic_error{"Abstract_game::window: Window does not exist " + "until engine is initialized"}; +} + +Random& Abstract_game::get_random() const NOEXCEPT +{ + return rng_; +} + +void Abstract_game::prepare(const sprites::Sprite& sprite) const +{ + if (engine_) + engine_->prepare(sprite); + else { + warn() << "Abstract_game::prepare: Could not prepare sprite " + << "because engine is not initialized"; + } +} + +void Abstract_game::mark_present_() NOEXCEPT +{ + busy_time_.pause(); +} + +void Abstract_game::mark_frame_() NOEXCEPT +{ + busy_time_.resume(); + prev_frame_length_ = frame_start_.reset(); + + if (++sample_counter_ == frames_per_sample) { + auto sample_duration = real_time_.reset().seconds(); + auto busy_duration = busy_time_.reset().seconds(); + fps_ = frames_per_sample / sample_duration; + load_ = 100 * busy_duration / sample_duration; + sample_counter_ = 0; + } +} + +void Abstract_game::poll_channels_() +{ + if (mixer_.is_forced()) + mixer_->poll_channels_(); +} + +void Abstract_game::on_key_down(Key key) +{ + if (key.code() == '\u001B') quit(); +} + +} // end namespace ge211 diff --git a/.cs211/lib/ge211/src/ge211_color.cxx b/.cs211/lib/ge211/src/ge211_color.cxx new file mode 100644 index 0000000..aeb5fe0 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_color.cxx @@ -0,0 +1,298 @@ +#include "ge211_color.hxx" + +#include +#include +#include + + +namespace ge211 { + +template +static T weighted_average(T t, double weight, U u) NOEXCEPT +{ + auto f1 = static_cast(t); + auto f2 = static_cast(u); + double result = (1 - weight) * f1 + weight * f2; + return T(result); +} + +template +static Whole adjust_field(Whole result, Field Whole::*field, + double weight, Goal goal) NOEXCEPT +{ + result.*field = weighted_average(result.*field, weight, goal); + return result; +} + +Color +Color::from_rgba(double red, double green, double blue, double alpha) NOEXCEPT +{ + return Color{uint8_t(255 * red), + uint8_t(255 * green), + uint8_t(255 * blue), + uint8_t(255 * alpha)}; +} + +// Creates a color from the HSL/HSV-style hue, the chroma, an adjustment +// for brightness, and the alpha. +// +// From https://en.wikipedia.org/wiki/HSL_and_HSV +static Color from_hcma(double hue, + double C, + double m, + double alpha) NOEXCEPT +{ + double H6 = std::fmod(hue, 360.0) / 60.0; + double X = C * (1 - std::fabs(std::fmod(H6, 2) - 1)); + + double r1 = 0, g1 = 0, b1 = 0; + + if (H6 <= 1) { + r1 = C; + g1 = X; + } else if (H6 <= 2) { + r1 = X; + g1 = C; + } else if (H6 <= 3) { + g1 = C; + b1 = X; + } else if (H6 <= 4) { + g1 = X; + b1 = C; + } else if (H6 <= 5) { + b1 = C; + r1 = X; + } else { + b1 = X; + r1 = C; + } + + return Color::from_rgba(r1 + m, g1 + m, b1 + m, alpha); +} + +Color Color::from_hsla(double hue, double saturation, double lightness, + double alpha) NOEXCEPT +{ + double C = (1 - std::fabs(2 * lightness - 1)) * saturation; + double m = lightness - 0.5 * C; + return from_hcma(hue, C, m, alpha); +} + +Color Color::from_hsva(double hue, double saturation, double value, + double alpha) NOEXCEPT +{ + double C = value * saturation; + double m = value - C; + return from_hcma(hue, C, m, alpha); +} + +Color Color::blend(double weight, Color that) const NOEXCEPT +{ + return Color{ + weighted_average(red(), weight, that.red()), + weighted_average(green(), weight, that.green()), + weighted_average(blue(), weight, that.blue()), + weighted_average(alpha(), weight, that.alpha()) + }; +} + +Color Color::invert() const NOEXCEPT +{ + return Color{uint8_t(~red_), uint8_t(~blue_), uint8_t(~green_), alpha_}; +} + +Color Color::rotate_hue(double degrees) const NOEXCEPT +{ + return to_hsva().rotate_hue(degrees).to_rgba(); +} + +Color Color::lighten(double unit_amount) const NOEXCEPT +{ + return to_hsla().lighten(unit_amount).to_rgba(); +} + +Color Color::darken(double unit_amount) const NOEXCEPT +{ + return to_hsla().darken(unit_amount).to_rgba(); +} + +Color Color::saturate(double unit_amount) const NOEXCEPT +{ + return to_hsla().saturate(unit_amount).to_rgba(); +} + +Color Color::desaturate(double unit_amount) const NOEXCEPT +{ + return to_hsla().desaturate(unit_amount).to_rgba(); +} + +static std::tuple to_HCMm(Color color) NOEXCEPT +{ + double R = color.red() / 255.0; + double G = color.green() / 255.0; + double B = color.blue() / 255.0; + + double M = std::max(R, std::max(G, B)); + double m = std::min(R, std::min(G, B)); + double C = M - m; + + double H6 = + (M == R) ? std::fmod((G - B) / C, 6) : + (M == G) ? (B - R) / C + 2 : + (R - G) / C + 4; + + double H = 60 * H6; + + return std::make_tuple(H, C, M, m); +} + +Color::HSLA Color::to_hsla() const NOEXCEPT +{ + double H, C, M, m; + std::tie(H, C, M, m) = to_HCMm(*this); + + double L = (M + m) / 2; + double S = (L == 1) ? 0 : C / (1 - std::fabs(2 * L - 1)); + + return {H, S, L, alpha() / 255.0}; +} + +Color::HSVA Color::to_hsva() const NOEXCEPT +{ + double H, C, M, m; + std::tie(H, C, M, m) = to_HCMm(*this); + + double V = M; + double S = V == 0 ? 0 : C / V; + + return {H, S, V, alpha() / 255.0}; +} + +SDL_Color Color::to_sdl_() const NOEXCEPT +{ + SDL_Color result; + result.a = alpha_; + result.r = red_; + result.g = green_; + result.b = blue_; + return result; +} + +uint32_t Color::to_sdl_(const SDL_PixelFormat* format) const NOEXCEPT +{ + return SDL_MapRGBA(format, red_, green_, blue_, alpha_); +} + +Color Color::fade_in(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &Color::alpha_, unit_amount, 255); +} + +Color Color::fade_out(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &Color::alpha_, unit_amount, 0); +} + +Color::HSLA::HSLA(double hue, + double saturation, + double lightness, + double alpha) NOEXCEPT + : hue{hue} + , saturation{saturation} + , lightness{lightness} + , alpha{alpha} +{ } + +Color Color::HSLA::to_rgba() const NOEXCEPT +{ + return Color::from_hsla(hue, saturation, lightness, alpha); +} + +Color::HSLA Color::HSLA::rotate_hue(double degrees) const NOEXCEPT +{ + auto result = *this; + result.hue = std::fmod(result.hue + degrees, 360); + return result; +} + +Color::HSLA Color::HSLA::saturate(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSLA::saturation, unit_amount, 1.0); +} + +Color::HSLA Color::HSLA::desaturate(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSLA::saturation, unit_amount, 0.0); +} + +Color::HSLA Color::HSLA::lighten(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSLA::lightness, unit_amount, 1.0); +} + +Color::HSLA Color::HSLA::darken(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSLA::lightness, unit_amount, 0.0); +} + +Color::HSLA Color::HSLA::fade_in(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSLA::alpha, unit_amount, 1.0); +} + +Color::HSLA Color::HSLA::fade_out(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSLA::alpha, unit_amount, 0.0); +} + +Color Color::HSVA::to_rgba() const NOEXCEPT +{ + return Color::from_hsva(hue, saturation, value, alpha); +} + +Color::HSVA::HSVA(double hue, double saturation, + double value, double alpha) NOEXCEPT + : hue(hue) + , saturation(saturation) + , value(value) + , alpha(alpha) +{ } + +Color::HSVA Color::HSVA::rotate_hue(double degrees) const NOEXCEPT +{ + auto result = *this; + result.hue = std::fmod(result.hue + degrees, 360); + return result; +} + +Color::HSVA Color::HSVA::saturate(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSVA::saturation, unit_amount, 1.0); +} + +Color::HSVA Color::HSVA::desaturate(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSVA::saturation, unit_amount, 0.0); +} + +Color::HSVA Color::HSVA::revalue(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSVA::value, unit_amount, 1.0); +} + +Color::HSVA Color::HSVA::devalue(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSVA::value, unit_amount, 0.0); +} + +Color::HSVA Color::HSVA::fade_in(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSVA::alpha, unit_amount, 1.0); +} + +Color::HSVA Color::HSVA::fade_out(double unit_amount) const NOEXCEPT +{ + return adjust_field(*this, &HSVA::alpha, unit_amount, 0.0); +} + +} diff --git a/.cs211/lib/ge211/src/ge211_engine.cxx b/.cs211/lib/ge211/src/ge211_engine.cxx new file mode 100644 index 0000000..b9418a7 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_engine.cxx @@ -0,0 +1,184 @@ +#include "ge211_engine.hxx" +#include "ge211_base.hxx" +#include "ge211_render.hxx" +#include "ge211_sprites.hxx" + +#include +#include "utf8.h" + +#include +#include + +namespace ge211 { + +namespace detail { + +// Used to control the frame rate if we have to fallback to +// software rendering, or for when the window is hidden (and +// vsync stops working). +static const int software_fps = 60; +static const Duration software_frame_length = Duration(1) / software_fps; +static const Duration min_frame_length = software_frame_length / 2; + +Engine::Engine(Abstract_game& game) + : game_{game}, + window_{game_.initial_window_title(), game_.initial_window_dimensions()}, + renderer_{window_} +{ + game_.engine_ = this; +} + +Engine::~Engine() +{ + game_.engine_ = nullptr; +} + +void Engine::prepare(const sprites::Sprite& sprite) const +{ + sprite.prepare(renderer_); +} + +void Engine::run() +{ + SDL_Event e; + Sprite_set sprites; + + bool has_vsync = renderer_.is_vsync(); + + try { + game_.on_start(); + + while (!game_.quit_) { + handle_events_(e); + game_.on_frame(game_.get_prev_frame_length().seconds()); + game_.poll_channels_(); + game_.draw(sprites); + + renderer_.set_color(game_.background_color); + renderer_.clear(); + paint_sprites_(sprites); + + game_.mark_present_(); + renderer_.present(); + + Duration allowed_frame_length = + (is_focused_ && has_vsync)? + min_frame_length : software_frame_length; + + auto frame_length = game_.frame_start_.elapsed_time(); + if (frame_length < allowed_frame_length) { + auto duration = allowed_frame_length - frame_length; + duration.sleep_for(); + game_.mark_frame_(); + debug() << "Software vsync slept for " + << duration.seconds() << " s"; + } else { + game_.mark_frame_(); + } + } + + game_.on_quit(); + } catch (const Exception_base& e) { + fatal() << "Uncaught exception:\n " << e.what(); + exit(1); + } +} + +void Engine::handle_events_(SDL_Event& e) +{ + while (SDL_PollEvent(&e) != 0) { + switch (e.type) { + case SDL_QUIT: + game_.quit(); + break; + + case SDL_TEXTINPUT: { + const char* str = e.text.text; + const char* end = str + std::strlen(str); + + while (str < end) { + uint32_t code = utf8::next(str, end); + if (code) game_.on_key(Key{code}); + } + + break; + } + + case SDL_KEYDOWN: { + Key key(e.key); + if (!e.key.repeat) { + game_.on_key_down(key); + } + if (!key.is_textual()) { + game_.on_key(key); + } + break; + } + + case SDL_KEYUP: + game_.on_key_up(Key{e.key}); + break; + + case SDL_MOUSEBUTTONDOWN: { + Mouse_button button; + if (map_button(e.button.button, button)) + game_.on_mouse_down(button, {e.button.x, e.button.y}); + break; + } + + case SDL_MOUSEBUTTONUP: { + Mouse_button button; + if (map_button(e.button.button, button)) + game_.on_mouse_up(button, {e.button.x, e.button.y}); + break; + } + + case SDL_MOUSEMOTION: + game_.on_mouse_move({e.motion.x, e.motion.y}); + break; + + case SDL_WINDOWEVENT: + switch (e.window.event) { + case SDL_WINDOWEVENT_FOCUS_GAINED: + is_focused_ = true; + break; + + case SDL_WINDOWEVENT_FOCUS_LOST: + is_focused_ = false; + break; + + default: + ; + } + break; + + default: + ; + } + } +} + +void Engine::paint_sprites_(Sprite_set& sprite_set) +{ + auto& vec = sprite_set.sprites_; + auto begin = vec.begin(), + end = vec.end(); + + std::make_heap(begin, end); + + while (begin != end) { + std::pop_heap(begin, end--); + end->render(renderer_); + } + + vec.clear(); +} + +Window& Engine::get_window() NOEXCEPT +{ + return window_; +} + +} // end namespace detail + +} diff --git a/.cs211/lib/ge211/src/ge211_error.cxx b/.cs211/lib/ge211/src/ge211_error.cxx new file mode 100644 index 0000000..be7f524 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_error.cxx @@ -0,0 +1,208 @@ +#include "ge211_error.hxx" + +#include +#include +#include + +#include + +namespace ge211 { + +namespace exceptions { + +static const char* take_sdl_error() +{ + const char* result = SDL_GetError(); + SDL_ClearError(); + return result; +} + +const char* Exception_base::what() const NOEXCEPT +{ + return message_->c_str(); +} + +Exception_base::Exception_base(const std::string& message) + : message_{std::make_shared(message)} +{ } + +Environment_error::Environment_error(const std::string& message) + : Exception_base(message) +{ } + +Client_logic_error::Client_logic_error(const std::string& message) + : Exception_base(message) +{ } + +static std::string build_no_session_message(const std::string& action) { + std::ostringstream oss; + oss << "\n\nERROR\n=====\n\n" + << action << " requires an active GE211 session. GE211 sessions\n" + << "are managed RAII-style by the ge211::Abstract_game class, so\n" + << "a session will be active whenever you have an instance of a\n" + << "class derived from Abstract_game, including within that derived\n" + << "game class's constructor and member functions.\n"; + + return oss.str(); +} + +Session_needed_error::Session_needed_error(const std::string& action) + : Client_logic_error(build_no_session_message(action)) + , action_(action) +{ } + +static std::string build_sdl_error_message(const std::string& message) { + const char* reason = take_sdl_error(); + + std::ostringstream oss; + if (message.empty()) + oss << "SDL Error: " << reason; + else + oss << message << "\n (reason from SDL: " << reason << ")"; + + return oss.str(); +} + +Host_error::Host_error(const std::string& message) + : Environment_error{build_sdl_error_message(message)} +{ } + +File_error::File_error(const std::string& message) + : Host_error{message} +{ } + +File_error File_error::could_not_open(const std::string& filename) +{ + return File_error("Could not open: " + filename); +} + +Font_error::Font_error(const std::string& message) + : Host_error{message} +{ } + +Font_error Font_error::could_not_load(const std::string& filename) +{ + return Font_error("Could not load font: " + filename); +} + +Ge211_logic_error::Ge211_logic_error(const std::string& message) + : Environment_error("Apparent ge211 bug! " + message) +{ } + +Image_error::Image_error(const std::string& message) + : Host_error{message} +{ } + +Image_error Image_error::could_not_load(const std::string& filename) +{ + return Image_error("Could not load image: " + filename); +} + +Mixer_error::Mixer_error(const std::string& message) + : Host_error{message} +{ } + +Mixer_error Mixer_error::could_not_load(const std::string& filename) +{ + return Mixer_error("Could not load music: " + filename); +} + +Mixer_error Mixer_error::out_of_channels() +{ + return Mixer_error("Could not play effect: out of channels"); +} + +Mixer_error Mixer_error::not_enabled() +{ + return Mixer_error("Mixer is not enabled"); +} + +} + +namespace detail { + +static const char* log_level_string(Log_level level) +{ + switch (level) { + case Log_level::debug: + return "debug"; + case Log_level::info: + return "info"; + case Log_level::warn: + return "warn"; + case Log_level::fatal: + return "fatal"; + } + + // Shouldn't happen, because switch above is exhaustive. But this + // makes gcc warn less. + return ""; +} + +Log_message debug(std::string reason) +{ + return Log_message{std::move(reason), Log_level::debug}; +} + +Log_message info(std::string reason) +{ + return Log_message{std::move(reason), Log_level::info}; +} + +Log_message warn(std::string reason) +{ + return Log_message{std::move(reason), Log_level::warn}; +} + +Log_message fatal(std::string reason) +{ + return Log_message{std::move(reason), Log_level::fatal}; +} + +Log_message info_sdl() +{ + return info(take_sdl_error()); +} + +Log_message warn_sdl() +{ + return warn(take_sdl_error()); +} + +Log_message fatal_sdl() +{ + return fatal(take_sdl_error()); +} + +Logger& Logger::instance() NOEXCEPT +{ + static Logger instance; + return instance; +} + +Log_message::Log_message(std::string reason, Log_message::Level level) NOEXCEPT + : reason_{std::move(reason)} + , message_{} + , active_{level >= Logger::instance().level()} +{ + if (active_) + message_ << "ge211[" << log_level_string(level) << "]: "; +} + +Log_message::Log_message(Log_message::Level level) + : Log_message{"", level} +{ } + +Log_message::~Log_message() +{ + if (active_) { + std::cerr << message_.str(); + if (!reason_.empty()) std::cerr << "\n (Reason: " << reason_ << ")"; + std::cerr << std::endl; + } +} + +} // end namespace detail + +} + diff --git a/.cs211/lib/ge211/src/ge211_event.cxx b/.cs211/lib/ge211/src/ge211_event.cxx new file mode 100644 index 0000000..4976755 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_event.cxx @@ -0,0 +1,152 @@ +#include "ge211_event.hxx" + +#include +#include "utf8.h" + +#include + +namespace ge211 { + +namespace detail { + +bool map_button(uint8_t input, Mouse_button& output) NOEXCEPT +{ + switch (input) { + case SDL_BUTTON_LEFT: + output = Mouse_button::left; + return true; + case SDL_BUTTON_MIDDLE: + output = Mouse_button::middle; + return true; + case SDL_BUTTON_RIGHT: + output = Mouse_button::right; + return true; + default: + return false; + } +} + +} // end namespace detail + +namespace events { + +static Key map_key(const SDL_KeyboardEvent& e) NOEXCEPT +{ + if (e.keysym.sym >= 0 && e.keysym.sym < 128) { + return Key::code(uint32_t(e.keysym.sym)); + } else { + switch (e.keysym.sym) { + case SDLK_KP_ENTER: + return Key::code('\r'); + case SDLK_UP: + return Key::up(); + case SDLK_DOWN: + return Key::down(); + case SDLK_LEFT: + return Key::left(); + case SDLK_RIGHT: + return Key::right(); + case SDLK_LSHIFT: + case SDLK_RSHIFT: + return Key::shift(); + case SDLK_LCTRL: + case SDLK_RCTRL: + return Key::control(); + case SDLK_LALT: + case SDLK_RALT: + return Key::alt(); + case SDLK_LGUI: + case SDLK_RGUI: + return Key::command(); + default: + return Key(); + } + } +} + +Key::Key(const SDL_KeyboardEvent& e) NOEXCEPT + : Key{map_key(e)} +{ } + +static const char* mouse_button_name(Mouse_button button) NOEXCEPT +{ + switch (button) { + case Mouse_button::left: + return "left"; + case Mouse_button::middle: + return "middle"; + case Mouse_button::right: + return "right"; + } + + return ""; +} + +std::ostream& operator<<(std::ostream& os, Mouse_button button) +{ + return os << mouse_button_name(button); +} + +static const char* key_type_name(Key::Type type) NOEXCEPT +{ + switch (type) { + case Key::Type::code: + return "ascii"; + case Key::Type::up: + return "up"; + case Key::Type::down: + return "down"; + case Key::Type::left: + return "left"; + case Key::Type::right: + return "right"; + case Key::Type::shift: + return "shift"; + case Key::Type::control: + return "control"; + case Key::Type::alt: + return "alt"; + case Key::Type::command: + return "command"; + case Key::Type::other: + return "other"; + } + + return ""; +} + +std::ostream& operator<<(std::ostream& os, Key::Type type) +{ + return os << key_type_name(type); +} + + +std::ostream& operator<<(std::ostream& os, Key key) +{ + if (key.type() == Key::Type::code) { + if (key.code() < 128 && key.is_textual()) + return os << "Key::code('" << char(key.code()) << "')"; + else + return os << "Key::code(" << key.code() << ")"; + } else { + return os << "Key::" << key.type() << "()"; + } +} + +bool Key::is_textual() const NOEXCEPT +{ + return type_ == Type::code && !iswcntrl(code_); +} + +std::string Key::as_text() const +{ + if (!is_textual()) return std::string{}; + + char buffer[4]; + char* end = utf8::append(code_, buffer); + return std::string(buffer, end); +} + +} // end namespace events + +} diff --git a/.cs211/lib/ge211/src/ge211_geometry.cxx b/.cs211/lib/ge211/src/ge211_geometry.cxx new file mode 100644 index 0000000..2c998a7 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_geometry.cxx @@ -0,0 +1,150 @@ +#include "ge211_geometry.hxx" + +#include + +namespace ge211 { + +namespace geometry { + +Transform::Transform() NOEXCEPT + : rotation_{0}, scale_x_{1.0}, scale_y_{1.0}, + flip_h_{false}, flip_v_{false} +{ } + +Transform Transform::rotation(double degrees) NOEXCEPT +{ + return Transform().set_rotation(degrees); +} + +Transform Transform::flip_h() NOEXCEPT +{ + return Transform().set_flip_h(true); +} + +Transform Transform::flip_v() NOEXCEPT +{ + return Transform().set_flip_v(true); +} + +Transform Transform::scale(double factor) NOEXCEPT +{ + return Transform().set_scale(factor); +} + +Transform Transform::scale_x(double factor) NOEXCEPT +{ + return Transform().set_scale_x(factor); +} + +Transform Transform::scale_y(double factor) NOEXCEPT +{ + return Transform().set_scale_y(factor); +} + +Transform& Transform::set_rotation(double rotation) NOEXCEPT +{ + while (rotation < 0) rotation += 360; + rotation_ = std::fmod(rotation, 360); + return *this; +} + +Transform& Transform::set_flip_h(bool flip_h) NOEXCEPT +{ + flip_h_ = flip_h; + return *this; +} + +Transform& Transform::set_flip_v(bool flip_v) NOEXCEPT +{ + flip_v_ = flip_v; + return *this; +} + +Transform& Transform::set_scale(double scale) NOEXCEPT +{ + scale_x_ = scale; + scale_y_ = scale; + return *this; +} + +Transform& Transform::set_scale_x(double scale_x) NOEXCEPT +{ + scale_x_ = scale_x; + return *this; +} + +Transform& Transform::set_scale_y(double scale_y) NOEXCEPT +{ + scale_y_ = scale_y; + return *this; +} + +double Transform::get_rotation() const NOEXCEPT +{ + return rotation_; +} + +bool Transform::get_flip_h() const NOEXCEPT +{ + return flip_h_; +} + +bool Transform::get_flip_v() const NOEXCEPT +{ + return flip_v_; +} + +double Transform::get_scale_x() const NOEXCEPT +{ + return scale_x_; +} + +double Transform::get_scale_y() const NOEXCEPT +{ + return scale_y_; +} + +bool Transform::is_identity() const NOEXCEPT +{ + return *this == Transform(); +} + +Transform Transform::operator*(const Transform& other) const NOEXCEPT +{ + Transform result; + result.set_rotation(rotation_ + other.rotation_); + result.set_flip_h(flip_h_ ^ other.flip_h_); + result.set_flip_v(flip_v_ ^ other.flip_v_); + result.set_scale_x(scale_x_ * other.scale_x_); + result.set_scale_y(scale_y_ * other.scale_y_); + return result; +} + +Transform Transform::inverse() const NOEXCEPT +{ + Transform result; + result.set_rotation(-rotation_); + result.set_flip_h(flip_h_); + result.set_flip_v(flip_v_); + result.set_scale_x(1 / scale_x_); + result.set_scale_y(1 / scale_y_); + return result; +} + +bool operator==(const Transform& t1, const Transform& t2) NOEXCEPT +{ + return t1.get_rotation() == t2.get_rotation() && + t1.get_flip_h() == t2.get_flip_h() && + t1.get_flip_v() == t2.get_flip_v() && + t1.get_scale_x() == t2.get_scale_x() && + t1.get_scale_y() == t2.get_scale_y(); +} + +bool operator!=(const Transform& t1, const Transform& t2) NOEXCEPT +{ + return !(t1 == t2); +} + +} + +} diff --git a/.cs211/lib/ge211/src/ge211_random.cxx b/.cs211/lib/ge211/src/ge211_random.cxx new file mode 100644 index 0000000..b033b72 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_random.cxx @@ -0,0 +1,27 @@ +#include "ge211_random.hxx" + +#include + +using namespace std; + +namespace ge211 { + +static auto construct_engine() +{ + random_device rd; + auto time = static_cast( + chrono::high_resolution_clock() + .now().time_since_epoch().count()); + return mt19937_64(rd() ^ time); +} + +Random::Random() + : generator_{construct_engine()} +{ } + +bool Random::random_bool(double ptrue) +{ + return up_to(1.0) < ptrue; +} + +} diff --git a/.cs211/lib/ge211/src/ge211_render.cxx b/.cs211/lib/ge211/src/ge211_render.cxx new file mode 100644 index 0000000..7fa9a24 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_render.cxx @@ -0,0 +1,220 @@ +#include "ge211_render.hxx" +#include "ge211_error.hxx" +#include "ge211_util.hxx" + +#include + +#include + +static inline SDL_RendererFlip& +operator|=(SDL_RendererFlip& f1, SDL_RendererFlip f2) +{ + return f1 = SDL_RendererFlip(f1 | f2); +} + +namespace ge211 +{ + +namespace detail +{ + +namespace +{ + +struct Renderer_flag +{ + uint32_t value; + const char* description; +}; + +#pragma push_macro("RF") +#define RF(E) Renderer_flag{(E), #E} + +static const Renderer_flag renderer_flags_to_try[] = { + RF(SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC), + RF(SDL_RENDERER_SOFTWARE | SDL_RENDERER_PRESENTVSYNC), + RF(SDL_RENDERER_ACCELERATED), + RF(SDL_RENDERER_SOFTWARE), + RF(0), +}; + +#pragma pop_macro("RF") + +} // end anonymous namespace + +SDL_Renderer* Renderer::create_renderer_(SDL_Window* window) +{ + SDL_Renderer* result; + +#if SDL_VIDEO_RENDER_METAL + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); +#endif + + for (auto flag : renderer_flags_to_try) { + result = SDL_CreateRenderer(window, -1, flag.value); + if (result) { + SDL_SetRenderDrawBlendMode(result, SDL_BLENDMODE_BLEND); + return result; + } + + info_sdl() + << "Could not initialize renderer (" + << flag.description << "); trying next."; + } + + return nullptr; +} + +Renderer::Renderer(const Window& window) + : ptr_{create_renderer_(window.get_raw_())} +{ + if (!ptr_) + throw Host_error{"Could not initialize renderer."}; +} + +bool Renderer::is_vsync() const NOEXCEPT +{ + SDL_RendererInfo info; + SDL_GetRendererInfo(get_raw_(), &info); + return (info.flags & SDL_RENDERER_PRESENTVSYNC) != 0; +} + +void Renderer::clear() +{ + if (SDL_RenderClear(get_raw_())) + throw Host_error{"Could not clear window"}; +} + +SDL_Renderer* Renderer::get_raw_() const NOEXCEPT +{ + return ptr_.get(); +} + +void Renderer::set_color(Color color) +{ + if (SDL_SetRenderDrawColor( + get_raw_(), + color.red(), color.green(), color.blue(), color.alpha())) + throw Host_error{"Could not set renderer color"}; +} + +void Renderer::present() NOEXCEPT +{ + SDL_RenderPresent(get_raw_()); +} + +void Renderer::copy(const Texture& texture, Position xy) +{ + auto raw_texture = texture.get_raw_(*this); + if (!raw_texture) return; + + SDL_Rect dstrect = Rectangle::from_top_left(xy, texture.dimensions()); + + int render_result = SDL_RenderCopy(get_raw_(), raw_texture, + nullptr, &dstrect); + if (render_result < 0) { + warn_sdl() << "Could not render texture"; + } +} + +void Renderer::copy(const Texture& texture, + Position xy, + const Transform& transform) +{ + auto raw_texture = texture.get_raw_(*this); + if (!raw_texture) return; + + SDL_Rect dstrect = Rectangle::from_top_left(xy, texture.dimensions()); + dstrect.w = int(dstrect.w * transform.get_scale_x()); + dstrect.h = int(dstrect.h * transform.get_scale_y()); + + SDL_RendererFlip flip = SDL_FLIP_NONE; + if (transform.get_flip_h()) flip |= SDL_FLIP_HORIZONTAL; + if (transform.get_flip_v()) flip |= SDL_FLIP_VERTICAL; + + int render_result = SDL_RenderCopyEx(get_raw_(), raw_texture, + nullptr, &dstrect, + transform.get_rotation(), + nullptr, + flip); + if (render_result < 0) { + warn_sdl() << "Could not render texture"; + } +} + +void Renderer::prepare(const Texture& texture) const +{ + texture.get_raw_(*this); +} + +Texture::Impl_::Impl_(Owned surface) NOEXCEPT + : surface_(surface) +{ } + +Texture::Impl_::Impl_(Owned texture) NOEXCEPT + : texture_(texture) +{ } + +Texture::Impl_::Impl_(Uniq_SDL_Surface surface) NOEXCEPT + : surface_(std::move(surface)) +{ } + +Texture::Impl_::Impl_(Uniq_SDL_Texture texture) NOEXCEPT + : texture_(std::move(texture)) +{ } + +Texture::Texture() NOEXCEPT +{ } + +Texture::Texture(Owned surface) + : Texture(Uniq_SDL_Surface(surface)) +{ } + +Texture::Texture(Uniq_SDL_Surface surface) + : impl_(std::make_shared(std::move(surface))) +{ } + +SDL_Texture* Texture::get_raw_(const Renderer& renderer) const +{ + if (impl_->texture_) return impl_->texture_.get(); + + if (!impl_->surface_) return nullptr; + + SDL_Texture* raw = SDL_CreateTextureFromSurface(renderer.get_raw_(), + impl_->surface_.get()); + if (raw) { + *impl_ = Impl_(raw); + return raw; + } + + throw Host_error{"Could not create texture from surface"}; +} + +Dimensions Texture::dimensions() const NOEXCEPT +{ + Dimensions result{0, 0}; + + if (impl_->texture_) { + SDL_QueryTexture(impl_->texture_.get(), nullptr, nullptr, + &result.width, &result.height); + } else if (impl_->surface_) { + result.width = impl_->surface_->w; + result.height = impl_->surface_->h; + } + + return result; +} + +SDL_Surface* Texture::as_surface() NOEXCEPT +{ + return impl_->surface_.get(); +} + +bool Texture::empty() const NOEXCEPT +{ + return impl_ == nullptr; +} + +} // end namespace detail + +} diff --git a/.cs211/lib/ge211/src/ge211_resource.cxx b/.cs211/lib/ge211/src/ge211_resource.cxx new file mode 100644 index 0000000..767cf7c --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_resource.cxx @@ -0,0 +1,68 @@ +#include "ge211_resource.hxx" +#include "ge211_error.hxx" +#include "ge211_session.hxx" + +#include +#include + +#include + +namespace ge211 { + +using namespace detail; + +static const char* search_prefixes[] = { + GE211_RESOURCES, + "Resources/", + "../Resources/", +}; + +namespace detail { + +static Owned open_rwops_(const std::string& filename) +{ + std::string path; + + for (auto prefix : search_prefixes) { + path.clear(); + path += prefix; + path += filename; + + auto rwops = SDL_RWFromFile(path.c_str(), "rb"); + if (rwops) return rwops; + + info_sdl() << "File_resource: could not load"; + } + + return nullptr; +} + +File_resource::File_resource(const std::string& filename) + : ptr_(open_rwops_(filename)) +{ + if (!ptr_) + throw File_error::could_not_open(filename); +} + +void File_resource::close_rwops_(Owned ptr) +{ + SDL_RWclose(ptr); +} + +} // end namespace detail + +static Owned open_ttf_(const std::string& filename, int size) +{ + return TTF_OpenFontRW(File_resource(filename).release(), 1, size); +} + +Font::Font(const std::string& filename, int size) + : ptr_(open_ttf_(filename, size)) +{ + Session::check_session("Font loading"); + + if (!ptr_) + throw Font_error::could_not_load(filename); +} + +} diff --git a/.cs211/lib/ge211/src/ge211_session.cxx b/.cs211/lib/ge211/src/ge211_session.cxx new file mode 100644 index 0000000..a1e2d54 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_session.cxx @@ -0,0 +1,88 @@ +#include "ge211_session.hxx" +#include "ge211_error.hxx" +#include "ge211_util.hxx" + +#include +#include +#include + +#include + +namespace ge211 { + +namespace detail { + +Sdl_session::Sdl_session() +{ + SDL_SetMainReady(); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { + fatal_sdl() << "Could not initialize SDL2"; + exit(1); + } +} + +Sdl_session::~Sdl_session() +{ + SDL_Quit(); +} + +Img_session::Img_session() +{ + int img_flags = IMG_INIT_JPG | IMG_INIT_PNG; + if (IMG_Init(img_flags) != img_flags) { + fatal_sdl() << "Could not initialize image loading support"; + exit(1); + } +} + +Img_session::~Img_session() +{ + IMG_Quit(); +} + +Ttf_session::Ttf_session() +{ + if (TTF_Init() < 0) { + fatal_sdl() << "Could not initialize font rendering support"; + exit(1); + } +} + +Ttf_session::~Ttf_session() +{ + TTF_Quit(); +} + +Text_input_session::Text_input_session() +{ + SDL_StartTextInput(); +} + +Text_input_session::~Text_input_session() +{ + SDL_StopTextInput(); +} + +Session::Session() +{ + setlocale(LC_ALL, "en_US.utf8"); + ++session_count_; +} + +Session::~Session() +{ + --session_count_; +} + +std::atomic Session::session_count_{0}; + +void Session::check_session(const char* action) +{ + if (session_count_ <= 0) + throw Session_needed_error(action); +} + +} // end namespace detail + +} diff --git a/.cs211/lib/ge211/src/ge211_sprites.cxx b/.cs211/lib/ge211/src/ge211_sprites.cxx new file mode 100644 index 0000000..716452f --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_sprites.cxx @@ -0,0 +1,343 @@ +#include "ge211_sprites.hxx" +#include "ge211_error.hxx" + +#include +#include +#include + +#include + +namespace ge211 { + +using namespace detail; + +Sprite_set::Sprite_set() {} + +Sprite_set& +Sprite_set::add_sprite(const Sprite& sprite, Position xy, int z, + const Transform& t) +{ + sprites_.emplace_back(sprite, xy, z, t); + return *this; +} + +Sprite_set& Sprite_set::add_sprite(const Sprite& sprite, Position xy, int z) +{ + return add_sprite(sprite, xy, z, Transform{}); +} + +namespace detail { + +Placed_sprite::Placed_sprite(const Sprite& sprite, Position xy, + int z, const Transform& transform) NOEXCEPT + : sprite{&sprite}, xy{xy}, z{z}, transform{transform} +{ } + +void Placed_sprite::render(Renderer& dst) const +{ + sprite->render(dst, xy, transform); +} + +bool operator<(const Placed_sprite& s1, const Placed_sprite& s2) NOEXCEPT +{ + return s1.z > s2.z; +} + +Dimensions Texture_sprite::dimensions() const +{ + return get_texture_().dimensions(); +} + +void Texture_sprite::render(Renderer& renderer, + Position position, + const Transform& transform) const +{ + if (transform.is_identity()) + renderer.copy(get_texture_(), position); + else + renderer.copy(get_texture_(), position, transform); +} + +void Texture_sprite::prepare(const Renderer& renderer) const +{ + renderer.prepare(get_texture_()); +} + +Uniq_SDL_Surface Render_sprite::create_surface_(Dimensions dimensions) +{ + SDL_Surface* surface = + SDL_CreateRGBSurfaceWithFormat(0, + dimensions.width, + dimensions.height, + 32, + SDL_PIXELFORMAT_RGBA32); + if (!surface) + throw Host_error{"Could not create sprite surface"}; + + return Uniq_SDL_Surface(surface); +} + +Render_sprite::Render_sprite(Dimensions dimensions) + : texture_{create_surface_(dimensions)} +{ } + +const Texture& Render_sprite::get_texture_() const +{ + return texture_; +} + +SDL_Surface& Render_sprite::as_surface() +{ + SDL_Surface* result = texture_.as_surface(); + if (result) return *result; + + throw Ge211_logic_error{"Render_sprite::as_surface: already a texture"}; +} + +void Render_sprite::fill_surface(Color color) +{ + auto& surface = as_surface(); + SDL_FillRect(&surface, nullptr, color.to_sdl_(surface.format)); +} + +void Render_sprite::fill_rectangle(Rectangle rect, Color color) +{ + auto& surface = as_surface(); + SDL_Rect rect_buf = rect; + SDL_FillRect(&surface, &rect_buf, color.to_sdl_(surface.format)); +} + +void Render_sprite::set_pixel(Position xy, Color color) +{ + fill_rectangle({xy.x, xy.y, 1, 1}, color); +} + +} // end namespace detail + +namespace sprites { + +static Dimensions check_rectangle_dimensions(Dimensions dims) +{ + if (dims.width <= 0 || dims.height <= 0) { + throw Client_logic_error( + "Rectangle_sprite: width and height must both be positive"); + } + + return dims; +} + +Rectangle_sprite::Rectangle_sprite(Dimensions dims, Color color) + : Render_sprite{check_rectangle_dimensions(dims)} +{ + fill_surface(color); +} + +void Rectangle_sprite::recolor(Color color) +{ + *this = Rectangle_sprite{dimensions(), color}; +} + +static Dimensions compute_circle_dimensions(int radius) +{ + if (radius <= 0) { + throw Client_logic_error("Circle_sprite: radius must be positive"); + } + + return {radius * 2, radius * 2}; +} + +Circle_sprite::Circle_sprite(int radius, Color color) + : Render_sprite{compute_circle_dimensions(radius)} +{ + const int cx = radius; + const int cy = radius; + + for (int y = 0; y < radius; ++y) { + for (int x = 0; x < radius; ++x) { + if (x * x + y * y < radius * radius) { + set_pixel({cx + x, cy + y}, color); + set_pixel({cx + x, cy - y - 1}, color); + set_pixel({cx - x - 1, cy + y}, color); + set_pixel({cx - x - 1, cy - y - 1}, color); + } + } + } +} + +void Circle_sprite::recolor(Color color) +{ + *this = Circle_sprite{radius_(), color}; +} + +int Circle_sprite::radius_() const +{ + return dimensions().width >> 1; +} + +Texture +Image_sprite::load_texture_(const std::string& filename) +{ + File_resource file(filename); + SDL_Surface* raw = IMG_Load_RW(file.get_raw(), 0); + if (raw) return Texture(raw); + + throw Image_error::could_not_load(filename); +} + +Image_sprite::Image_sprite(const std::string& filename) + : texture_{load_texture_(filename)} {} + +const Texture& Image_sprite::get_texture_() const +{ + return texture_; +} + +Texture +Text_sprite::create_texture(const Builder& config) +{ + SDL_Surface* raw; + + std::string message = config.message(); + + if (message.empty()) + return Texture{}; + + if (config.word_wrap() > 0) { + raw = TTF_RenderUTF8_Blended_Wrapped( + config.font().get_raw_(), + message.c_str(), + config.color().to_sdl_(), + static_cast(config.word_wrap())); + } else { + auto render = config.antialias() ? + &TTF_RenderUTF8_Blended : + &TTF_RenderUTF8_Solid; + raw = render(config.font().get_raw_(), + message.c_str(), + config.color().to_sdl_()); + } + + if (!raw) + throw Host_error{"Could not render text: “" + message + "”"}; + else + return Texture{raw}; +} + +Text_sprite::Text_sprite(const Text_sprite::Builder& config) + : texture_{create_texture(config)} {} + +Text_sprite::Text_sprite() + : texture_{} {} + +Text_sprite::Text_sprite(const std::string& message, + const Font& font) + : Text_sprite{Builder{font}.message(message)} {} + +const Texture& Text_sprite::get_texture_() const +{ + assert_initialized_(); + return texture_; +} + +void Text_sprite::assert_initialized_() const +{ + if (texture_.empty()) + throw Client_logic_error{"Attempt to render empty Text_sprite"}; +} + +Text_sprite::Builder::Builder(const Font& font) + : message_{}, font_{&font}, color_{Color::white()}, antialias_{true}, + word_wrap_{0} {} + +Text_sprite::Builder& Text_sprite::Builder::message(const std::string& message) +{ + message_.str(message); + return *this; +} + +Text_sprite::Builder& Text_sprite::Builder::font(const Font& font) +{ + font_ = &font; + return *this; +} + +Text_sprite::Builder& Text_sprite::Builder::color(Color color) +{ + color_ = color; + return *this; +} + +Text_sprite::Builder& Text_sprite::Builder::antialias(bool antialias) +{ + antialias_ = antialias; + return *this; +} + +Text_sprite::Builder& Text_sprite::Builder::word_wrap(int word_wrap) +{ + if (word_wrap < 0) word_wrap = 0; + word_wrap_ = static_cast(word_wrap); + return *this; +} + +Text_sprite Text_sprite::Builder::build() const +{ + return Text_sprite{*this}; +} + +std::string Text_sprite::Builder::message() const +{ + return message_.str(); +} + +const Font& Text_sprite::Builder::font() const +{ + return *font_; +} + +Color Text_sprite::Builder::color() const +{ + return color_; +} + +bool Text_sprite::Builder::antialias() const +{ + return antialias_; +} + +int Text_sprite::Builder::word_wrap() const +{ + return static_cast(word_wrap_); +} + +void Text_sprite::reconfigure(const Text_sprite::Builder& config) +{ + texture_ = create_texture(config); +} + +bool Text_sprite::empty() const +{ + return texture_.empty(); +} + +Text_sprite::operator bool() const +{ + return !empty(); +} + +void Multiplexed_sprite::reset() +{ + since_.reset(); +} + +void Multiplexed_sprite::render(detail::Renderer& renderer, + Position position, + Transform const& transform) const +{ + const Sprite& selection = select_(since_.elapsed_time()); + selection.render(renderer, position, transform); +} + +} // end namespace sprites + +} diff --git a/.cs211/lib/ge211/src/ge211_window.cxx b/.cs211/lib/ge211/src/ge211_window.cxx new file mode 100644 index 0000000..1121825 --- /dev/null +++ b/.cs211/lib/ge211/src/ge211_window.cxx @@ -0,0 +1,109 @@ +#include "ge211_window.hxx" +#include "ge211_error.hxx" + +#include + +namespace ge211 { + +using namespace detail; + +Window::Window(const std::string& title, Dimensions dim) + : ptr_{SDL_CreateWindow(title.c_str(), + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + dim.width, + dim.height, + SDL_WINDOW_SHOWN)} +{ + if (!ptr_) + throw Host_error{"Could not create window"}; +} + +uint32_t Window::get_flags_() const NOEXCEPT +{ + return SDL_GetWindowFlags(get_raw_()); +} + +Dimensions Window::get_dimensions() const NOEXCEPT +{ + Dimensions result{0, 0}; + SDL_GetWindowSize(get_raw_(), &result.width, &result.height); + return result; +} + +void Window::set_dimensions(Dimensions dims) +{ + SDL_SetWindowSize(get_raw_(), dims.width, dims.height); + + if (get_dimensions() != dims) + throw Environment_error{"Window::set_dimensions: out of range"}; +} + +const char* Window::get_title() const NOEXCEPT +{ + return SDL_GetWindowTitle(get_raw_()); +} + +void Window::set_title(const std::string& title) NOEXCEPT +{ + SDL_SetWindowTitle(get_raw_(), title.c_str()); +} + +#if SDL_VERSION_ATLEAST(2, 0, 5) +bool Window::get_resizeable() const NOEXCEPT +{ + return (get_flags_() & SDL_WINDOW_RESIZABLE) != 0; +} + +void Window::set_resizeable(bool resizable) NOEXCEPT +{ + SDL_SetWindowResizable(get_raw_(), resizable? SDL_TRUE : SDL_FALSE); +} +#endif + +Position Window::get_position() const NOEXCEPT +{ + Position result{0, 0}; + SDL_GetWindowPosition(get_raw_(), &result.x, &result.y); + return result; +} + +void Window::set_position(Position position) +{ + SDL_SetWindowPosition(get_raw_(), position.x, position.y); +} + +const Position Window::centered{SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED}; + +bool Window::get_fullscreen() const NOEXCEPT +{ + return (get_flags_() & SDL_WINDOW_FULLSCREEN) != 0; +} + +void Window::set_fullscreen(bool fullscreen) +{ + uint32_t flags = fullscreen? SDL_WINDOW_FULLSCREEN : 0; + + if (SDL_SetWindowFullscreen(get_raw_(), flags) < 0) + throw Host_error{"Window::set_fullscreen: failed"}; +} + +Dimensions Window::max_fullscreen_dimensions() NOEXCEPT +{ + SDL_Rect rect; + SDL_GetDisplayBounds(0, &rect); + return {rect.w, rect.h}; +} + +Dimensions Window::max_window_dimensions() const NOEXCEPT +{ + int top, left, bottom, right; + SDL_GetWindowBordersSize(get_raw_(), &top, &left, &bottom, &right); + + SDL_Rect rect; + SDL_GetDisplayUsableBounds(0, &rect); + + return {rect.w - left - right, rect.h - top - bottom}; +} + +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b44cfdf --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# CMake build directories: +build +build.* +builds +cmake-build-* + +# CLion configuration +.idea/* +!.idea/codeStyles +!.idea/inspectionProfiles +!.idea/misc.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..ab160ac --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,98 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/CS_211.xml b/.idea/inspectionProfiles/CS_211.xml new file mode 100644 index 0000000..5396b92 --- /dev/null +++ b/.idea/inspectionProfiles/CS_211.xml @@ -0,0 +1,31 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..c084d7e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,21 @@ + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..c3f3e01 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7734fe6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/helper_test.run.xml b/.run/helper_test.run.xml new file mode 100644 index 0000000..6c0e3cc --- /dev/null +++ b/.run/helper_test.run.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.run/model_test.run.xml b/.run/model_test.run.xml new file mode 100644 index 0000000..1cf242b --- /dev/null +++ b/.run/model_test.run.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.run/reversi.run.xml b/.run/reversi.run.xml new file mode 100644 index 0000000..1c3ff28 --- /dev/null +++ b/.run/reversi.run.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ce568e8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.3) +project(reversi CXX) +include(.cs211/cmake/CMakeLists.txt) + +# Defines a variable to stand for the .cxx files that implement +# the model. +set(MODEL_SRC + src/player.cxx + src/move.cxx + src/board.cxx + src/model.cxx) + +# Adds a program named `reversi` built from the listed source +# files and the common model files. +add_program(reversi + src/reversi.cxx + src/controller.cxx + src/view.cxx + ${MODEL_SRC}) +target_link_libraries(reversi ge211) + +# Adds a test program named `model_test` built from the listed +# source file and the common model files. +add_test_program(model_test + test/model_test.cxx + ${MODEL_SRC}) +target_link_libraries(model_test ge211) + +# Adds a test program named `helper_test` for testing the helper +# classes. +add_test_program(helper_test + test/player_test.cxx + test/move_test.cxx + test/board_test.cxx + ${MODEL_SRC}) +target_link_libraries(helper_test ge211) + +## Uncommment this next line to get board multireferences: +target_compile_definitions(helper_test PUBLIC BOARD_INDEX_OVERLOAD) + +# vim: ft=cmake diff --git a/src/board.cxx b/src/board.cxx new file mode 100644 index 0000000..88b852e --- /dev/null +++ b/src/board.cxx @@ -0,0 +1,209 @@ +/*******************************************/ +/*** DO NOT CHANGE ANYTHING IN THIS FILE ***/ +/*******************************************/ + +#include "board.hxx" +#include + +using namespace ge211; + +Board::Board(Dimensions dims) + : dims_(dims) +{ + if (dims_.width < 2 || dims_.height < 2) + throw Client_logic_error("Board::Board: dims too small"); + + if (dims_.width > Position_set::coord_limit || + dims_.height > Position_set::coord_limit) + throw Client_logic_error("Board::Board: dims too large"); +} + +Dimensions Board::dimensions() const +{ + return dims_; +} + +bool Board::good_position(Position pos) const +{ + return 0 <= pos.x && pos.x < dims_.width && + 0 <= pos.y && pos.y < dims_.height; +} + +Player Board::operator[](Position pos) const +{ + bounds_check_(pos); + return get_(pos); +} + +Board::reference Board::operator[](Position pos) +{ + bounds_check_(pos); + return reference(*this, pos); +} + +size_t Board::count_player(Player player) const +{ + switch (player) { + case Player::light: + return light_.size(); + case Player::dark: + return dark_.size(); + default: + return dims_.width * dims_.height - + light_.size() - dark_.size(); + } +} + +Rectangle Board::center_positions() const +{ + return Rectangle::from_top_left({ dims_.width / 2 - 1, + dims_.height / 2 - 1 }, + { 2, 2 }); +} + +static std::vector build_directions() +{ + std::vector result; + + for (int dx = -1; dx <= 1; ++dx) + for (int dy = -1; dy <= 1; ++dy) + if (dx || dy) + result.push_back({dx, dy}); + + return result; +} + +std::vector const& Board::all_directions() +{ + static std::vector result = build_directions(); + return result; +} + +Rectangle Board::all_positions() const +{ + return Rectangle::from_top_left({0, 0}, dims_); +} + +bool operator==(Board const& b1, Board const& b2) +{ + return b1.dims_ == b2.dims_ && + b1.light_ == b2.light_ && + b1.dark_ == b2.dark_; +} + +Player Board::get_(ge211::Position pos) const +{ + if (dark_[pos]) + return Player::dark; + else if (light_[pos]) + return Player::light; + else + return Player::neither; +} + +void Board::set_(ge211::Position pos, Player player) +{ + switch (player) { + case Player::dark: + dark_[pos] = true; + light_[pos] = false; + break; + + case Player::light: + dark_[pos] = false; + light_[pos] = true; + break; + + default: + dark_[pos] = false; + light_[pos] = false; + } +} + +void Board::set_all(Position_set pos_set, Player player) +{ + switch (player) { + case Player::light: + light_ |= pos_set; + dark_ &= ~pos_set; + break; + + case Player::dark: + dark_ |= pos_set; + light_ &= ~pos_set; + break; + + default: + dark_ &= ~pos_set; + light_ &= ~pos_set; + } +} + +void Board::bounds_check_(ge211::Position pos) const +{ + if (!good_position(pos)) + throw Client_logic_error("Board: position out of bounds"); +} + +bool operator!=(Board const& b1, Board const& b2) +{ + return !(b1 == b2); +} + +std::ostream& operator<<(std::ostream& os, Board const& board) +{ + Dimensions dims = board.dimensions(); + + for (int y = 0; y < dims.height; ++y) { + for (int x = 0; x < dims.width; ++x) { + os << board[{x, y}]; + } + os << "\n"; + } + + return os; +} + +Board::reference::reference(Board& board, ge211::Position pos) noexcept + : board_(board) + , pos_(pos) +{ } + +Board::reference& +Board::reference::operator=(reference const& that) noexcept +{ + *this = Player(that); + return *this; +} + +Board::reference& +Board::reference::operator=(Player player) noexcept +{ + board_.set_(pos_, player); + return *this; +} + +Board::reference::operator Player() const noexcept +{ + return board_.get_(pos_); +} + +Board::multi_reference Board::at_set(Position_set pos_set) +{ + return multi_reference(*this, pos_set); +} + +Board::multi_reference::multi_reference( + Board& board, + Position_set pos_set) noexcept + : board_(board) + , pos_set_(pos_set) +{ } + +Board::multi_reference& +Board::multi_reference::operator=(Player player) noexcept +{ + board_.set_all(pos_set_, player); + return *this; +} + diff --git a/src/board.hxx b/src/board.hxx new file mode 100644 index 0000000..2b13903 --- /dev/null +++ b/src/board.hxx @@ -0,0 +1,209 @@ +/*******************************************/ +/*** DO NOT CHANGE ANYTHING IN THIS FILE ***/ +/*******************************************/ + +#pragma once + +#include "player.hxx" +#include "move.hxx" + +#include + +#include +#include +#include +#include + +// Represents the state of the board. +class Board +{ +public: + // Constructs a board with the given dimensions. + // + // ERRORS: + // + // - Throws `ge211::Client_logic_error` if either dimension is less + // than 2 or greater than 8. + explicit Board(ge211::Dimensions dims); + + // Returns the same `ge211::Dimensions` value passed to the + // constructor. + ge211::Dimensions dimensions() const; + + // Returns whether the given position is in bounds. + bool good_position(ge211::Position) const; + + // Returns the `Player` stored at `pos`. + // + // ERRORS: + // - throws `ge211::Client_logic_error` if `!good_position(pos)`. + Player operator[](ge211::Position pos) const; + + // Defined and documented below. + class reference; + + // Returns a reference to the `Player` stored at `pos`. This can + // be assigned to update the board: + // + // // Light player plays at (3, 4) + // board[{3, 4}] = Player::light; + // + // ERRORS: + // - throws `ge211::Client_logic_error` if `!good_position(pos)`. + reference operator[](ge211::Position pos); + + // Stores the given player in all the positions in the given set. + // For example, + // + // // Sets three positions to dark: + // Position_set positions{{0, 0}, {1, 1}, {2, 2}}; + // board.set_all(positions, Player::dark); + // + // ERRORS: + // - behavior is undefined if any positions in the `Position_set` + // are out of bounds. + void set_all(Position_set, Player); + + // Counts the number of occurrences of the given player in the board. + size_t count_player(Player) const; + + // Returns a rectangle containing all the positions of the board. This + // can be used to iterate over the positions: + // + // for (ge211::Position pos : a_board.all_positions()) { + // ... a_board[pos] ...; + // } + // + // Note that `ge211::Rectangles` are considered to be closed on the top + // and left, but open on the bottom and right. The iterator will visit + // the correct positions for the board. + ge211::Rectangle all_positions() const; + + // Returns a rectangle containing the four center positions which + // much be occupied for play to move beyond them. This can be used + // to iterate over those positions. + ge211::Rectangle center_positions() const; + + // Returns a reference to a `std::vector` containimg all eight "unit + // direction vectors". In Python notation, these are: + // + // { Dimensions(dx, dy) + // for dx in [-1, 0, 1] + // for dy in [-1, 0, 1] + // if dx or dy } + // + static std::vector const& all_directions(); + + // Equality for boards. + friend bool operator==(Board const&, Board const&); + + // Defined and documented below. + class multi_reference; + + // Returns an object that allows assigning to all the positions in + // `pset`. See below for the documentation of + // `Board::multi_reference::operator=(Player)`. + // + // ERRORS: + // - behavior is undefined if any positions in the `Position_set` + // are out of bounds. + multi_reference at_set(Position_set pset); + +#ifdef BOARD_INDEX_OVERLOAD + // Optional alias for `at_set`, so you can write + // + // board[pset] = player; + // + // This is disabled by default because GCC can't distinguish + // this overload from `operator[](Position)` when given something + // like `{3, 4}`. + multi_reference operator[](Position_set pset); +#endif // BOARD_INDEX_OVERLOAD + + +private: + Player get_(ge211::Position) const; + void set_(ge211::Position, Player); + + void bounds_check_(ge211::Position) const; + + ge211::Dimensions dims_; + Position_set light_; + Position_set dark_; + // Invariant: (light_ & dark_).empty() +}; + +// Inequality for boards. +bool operator!=(Board const&, Board const&); + +// Board printing, suitable for debugging. +std::ostream& operator<<(std::ostream&, Board const&); + +/// +/// Helper classes +/// + +// Class returned by `operator[](ge211::Position)` that simulates +// an assignable reference to a `Position`. This is what allows +// you to write +// +// board[pos] = player; +// +// to place `player` at `pos`. +// +// The definition of the class follows this definition of the +// `Board` class. +class Board::reference +{ + friend class Board; + +public: + // Assigns the value of `that` to the object of `this`. + reference& operator=(reference const&) noexcept; + + // Assigns to the object of the reference. + reference& operator=(Player) noexcept; + + // Returns the value of the reference. + operator Player() const noexcept; + +private: + reference(Board&, ge211::Position) noexcept; + + Board& board_; + ge211::Position pos_; +}; + + +// Class returned by `at_set(Position_set)` that allows assigning +// one player to all the positions in the given `Position_set`. +class Board::multi_reference +{ + friend class Board; + +public: + // Assigns the given player to all the positions of this + // multi-reference, which are all the positions in the set that + // was given to `Board::at_set(Position_set)`. Thus, you can + // mass-assign a player to a set of positions like so: + // + // // Sets three positions to dark: + // Position_set positions{{0, 0}, {1, 1}, {2, 2}}; + // board.at_set(positions) = Player::dark; + // + multi_reference& operator=(Player) noexcept; + +private: + multi_reference(Board&, Position_set) noexcept; + + Board& board_; + Position_set pos_set_; +}; + +#ifdef BOARD_INDEX_OVERLOAD +inline Board::multi_reference Board::operator[](Position_set pset) +{ + return at_set(pset); +} +#endif // BOARD_INDEX_OVERLOAD + diff --git a/src/controller.cxx b/src/controller.cxx new file mode 100644 index 0000000..d380c81 --- /dev/null +++ b/src/controller.cxx @@ -0,0 +1,41 @@ +#include "controller.hxx" + +Controller::Controller(int size) + : Controller(size, size) +{ } + +Controller::Controller(int width, int height) + : model_(width, height) + , view_(model_) +{ } + +void Controller::draw(ge211::Sprite_set& sprites) +{ + view_.draw(sprites); +} + +void Controller::on_key(ge211::Key key) +{ + if (key.code() == 'q') quit(); +} + +void Controller::on_mouse_up(ge211::Mouse_button btn, ge211::Position position) +{ + if(model_.turn() == Player::neither) return; + if(btn != ge211::Mouse_button::left) return; + + ge211::Position on_board = view_.screen_to_board_(position); + if(model_.find_move(on_board)) { + model_.play_move(on_board); + } +} + +ge211::Dimensions Controller::initial_window_dimensions() const +{ + return view_.initial_window_dimensions(); +} + +std::string Controller::initial_window_title() const +{ + return view_.initial_window_title(); +} diff --git a/src/controller.hxx b/src/controller.hxx new file mode 100644 index 0000000..ea6798a --- /dev/null +++ b/src/controller.hxx @@ -0,0 +1,53 @@ +#pragma once + +#include "model.hxx" +#include "view.hxx" + +// The main game class for Reversi. +class Controller : public ge211::Abstract_game +{ +public: + // Constructs a game with `size` as both its width and height. + // + // ERRORS: + // - Throws `ge211::Client_logic_error` if `size` is less than 2 + // or greater than 8. + explicit Controller(int size = 8); + + // Constructs a game with the given width and height. + // + // ERRORS: + // - Throws `ge211::Client_logic_error` if either dimension is less + // than 2 or greater than 8. + Controller(int width, int height); + +// `protected` means that GE211 (via base class `ge211::Abstract_game`) +// can access these members, but arbitrary other code cannot: +protected: + // + // Controller operations called by GE211 + // + + // TODO: Add any input handlers you need, e.g.: + // - on_mouse_up for mouse clicks, + // - on_mouse_move for mouse tracking, or + // - on_frame for animation (?). + + void on_key(ge211::Key key) override; + + void on_mouse_up(ge211::Mouse_button btn, ge211::Position position) + override; + + // These three delegate to the view: + void draw(ge211::Sprite_set&) override; + ge211::Dimensions initial_window_dimensions() const override; + std::string initial_window_title() const override; + +private: + Model model_; + View view_; + + // TODO: Add any UI state you need, e.g.: + // - the position of the mouse, or + // - the position of a keyboard-controller cursor. +}; diff --git a/src/model.cxx b/src/model.cxx new file mode 100644 index 0000000..671f097 --- /dev/null +++ b/src/model.cxx @@ -0,0 +1,194 @@ +#include "model.hxx" +#include "iostream" + +using namespace ge211; + +Model::Model(int size) + : Model(size, size) +{ } + + +int count = 0; + +Model::Model(int width, int height) + : board_({width, height}) +{ + this->compute_next_moves_(); +} + +Rectangle Model::board() const +{ + return board_.all_positions(); +} + +Player Model::operator[](Position pos) const +{ + return board_[pos]; +} + +Move const* Model::find_move(Position pos) const +{ + auto i = next_moves_.find(pos); + + if (i == next_moves_.end()) { + return nullptr; + } else + return &*i; +} + +void Model::play_move(Position pos) +{ + if (is_game_over()) + throw Client_logic_error("Model::play_move: game over"); + + Move const* movep = find_move(pos); + if (!movep) + throw Client_logic_error("Model::play_move: no such move"); + + really_play_move_(*movep); +} + +// +// BELOW ARE *OPTIONAL* HELPER FUNCTIONS +// + +Position_set Model::find_flips_(Position current, Dimensions dir) const +{ + int addition = 1; + bool isValid = false; + + Position_set original_position_set = {}; + Position_set new_position_set; + + while (true) { + Position toCheck = current + addition * dir; + + if (!this->board_.good_position(toCheck)) { + break; + } + + if (this->board_[toCheck] == this->turn_) { + isValid = true; + break; + } else if (this->board_[toCheck] == Player::neither) { + break; + } else { + new_position_set = {toCheck}; + original_position_set = original_position_set | new_position_set; + } + + addition = addition + 1; + } + if (original_position_set.size() > 0 && isValid) { + return original_position_set; + } else { + return {}; + } +} + +Position_set Model::evaluate_position_(Position pos) const +{ + Position_set position_set; + Dimensions dimensions; + dimensions.width = -1; + dimensions.height = -1; + position_set = position_set | find_flips_(pos, dimensions); + for (int i = 0; i < 2; i++) { + dimensions.height = dimensions.height + 1; + position_set = position_set | find_flips_(pos, dimensions); + } + + dimensions.height = -1; + + for (int i = 0; i < 2; i++) { + dimensions.width = dimensions.width + 1; + position_set = position_set | find_flips_(pos, dimensions); + } + + dimensions.width = 1; + dimensions.height = 1; + position_set = position_set | find_flips_(pos, dimensions); + + dimensions.width = 0; + dimensions.height = 1; + position_set = position_set | find_flips_(pos, dimensions); + + dimensions.width = 1; + dimensions.height = 0; + position_set = position_set | find_flips_(pos, dimensions); + + if(position_set.size() > 0) { + Position_set positionSet = {pos}; + position_set = position_set | positionSet; + } + + return position_set; +} + +void Model::compute_next_moves_() +{ + + Rectangle centre = this->board_.center_positions(); + count = 0; + for (Position each_pos: centre) { + if (this->board_[each_pos] == Player::neither) { + Position_set position_set = {{each_pos}}; + this->next_moves_[each_pos] = {each_pos}; + count = count + 1; + } + } + if (count == 0) { + for (int i = 0; i < this->board_.dimensions().height; i++) { + for (int j = 0; j < this->board_.dimensions().width; j++) { + Position position = {i, j}; + Player player = this->board_[position]; + if (player != Player::neither) { + continue; + } + Position_set position_set = evaluate_position_(position); + if (!position_set.empty()) { + this->next_moves_[position] = {position_set}; + } + } + } + } +} + +bool Model::advance_turn_() +{ + this->turn_ = other_player(this->turn_); + this->next_moves_.clear(); + this->compute_next_moves_(); + return !this->next_moves_.empty(); +} + +void Model::set_game_over_() +{ + this->turn_ = Player::neither; + size_t light_count = this->board_.count_player(Player::light); + size_t dark_count = this->board_.count_player(Player::dark); + if (light_count > dark_count) { + this->winner_ = Player::light; + } else if (dark_count > light_count) { + this->winner_ = Player::dark; + } else { + this->winner_ = Player::neither; + } +} + +void Model::really_play_move_(Move move) +{ + Position position = move.first; + Position_set position_set = move.second; + this->board_[position] = this->turn_; + for (auto const& item : position_set) { + this->board_[item] = this->turn_; + } + bool advance = this->advance_turn_(); + if (!advance) { + bool advance_same = this->advance_turn_(); + if (!advance_same) { + this->set_game_over_(); + } + } +} diff --git a/src/model.hxx b/src/model.hxx new file mode 100644 index 0000000..c94f3b1 --- /dev/null +++ b/src/model.hxx @@ -0,0 +1,180 @@ +/*************************************************/ +/*** DO NOT CHANGE THE PUBLIC API IN THIS FILE ***/ +/*************************************************/ +// +// You may add private members if you like, or even remove +// the private helper functions, but you mustn't change the +// existing public members, nor add new ones. If you do, your +// code may not build for automated tests. + +#pragma once + +#include "player.hxx" +#include "board.hxx" + +#include + +#include +#include + +// Represents the state of the Reversi game. +class Model +{ +public: + /***************************************************/ + /*** DON'T CHANGE ANYTHING IN THE PUBLIC SECTION ***/ + /***************************************************/ + + // Constructs a model with `size` as both its width and height. + // + // ERRORS: + // - Throws `ge211::Client_logic_error` if `size` is less than 2 + // or greater than 8. + explicit Model(int size = 8); + + // Constructs a model with the given width and height. + // + // ERRORS: + // - Throws `ge211::Client_logic_error` if either dimension is less + // than 2 or greater than 8. + Model(int width, int height); + + // Returns a rectangle containing all the positions of the board. + // This can be used to iterate over the positions. + ge211::Rectangle board() const; + + // Returns whether the game is finished. This is true when neither + // player can move. + bool is_game_over() const + { return turn() == Player::neither; } + + // Returns the current turn, or `Player::neither` if the game is + // over. + Player turn() const + { return turn_; } + + // Returns the winner, or `Player::neither` if there is no winner + // (either because the game isn't over, or because it's a draw). + Player winner() const + { return winner_; } + + // Returns the player at the given position, or `Player::neither` if + // the position is unoccupied. + // + // ERRORS: + // - Throws `ge211::Client_logic_error` if the position is out of + // bounds. + Player operator[](ge211::Position) const; + + // Returns a pointer to the move that will result if the current + // player plays at the given position. If the current player cannot + // play at the given position, returns `nullptr`. (Also returns + // `nullptr` if the position is out of bounds.) + // + // Note that the returned pointer must be borrowed from `next_moves_`, + // not a pointer to a local variable defined within this function. + // + Move const* find_move(ge211::Position) const; + + // Attempts to play a move at the given position for the current + // player. If successful, advances the state of the game to the + // correct player or game over. + // + // ERRORS: + // - Throws `ge211::Client_logic_error` if the game is over. + // - Throws `ge211::Client_logic_error` if the move is not currently + // allowed for the current player. + // + void play_move(ge211::Position); + + // vvv DON'T REMOVE THIS LINE, OR MY TESTS WON'T COMPILE vvv + friend struct Test_access; + // ^^^ DON'T REMOVE THIS LINE, OR MY TESTS WON'T COMPILE ^^^ + +private: + /// + /// PRIVATE MEMBER VARIABLES + /// (Don't change these.) + /// + + Player turn_ = Player::dark; + Player winner_ = Player::neither; + Board board_; + int count = 0; + + Move_map next_moves_; + // INVARIANT: + // - `next_moves_` is always current for the state of the game. + + /**********************************************/ + /*** DO NOT CHANGE ANYTHING ABOVE THIS LINE ***/ + /**********************************************/ + // + // You may add or change anything you like below this point. + // + + /// + /// PRIVATE HELPER FUNCTIONS + /// + /// Implementing these is optional, but likely a good idea. + /// + + // Computes the set of positions to be flipped in direction `dir` if + // the current player (`turn_`) were to play at position `start`. + // In particular, if there is some `n` such that all of these hold: + // + // - board_[start + 1 * dir] == other_player(turn_) + // - board_[start + 2 * dir] == other_player(turn_) + // - . . . + // - board_[start + n * dir] == other_player(turn_) + // - board_[start + (n + 1) * dir] == turn_ + // + // then it returns the `Position_set` + // + // {start + 1 * dir, ..., start + n * dir} + // + // Otherwise, it returns the empty set. + // + // (Helper for `evaluate_position_`.) + Position_set find_flips_(ge211::Position start, + ge211::Dimensions dir) const; + + // Returns the set of positions that the current player would gain + // by playing in the given position. If the current player cannot + // play in the given position then the result is empty. + // + // (Helper for `compute_next_moves_`.) + Position_set evaluate_position_(ge211::Position) const; + + // Updates `next_moves_` to contain the moves available the current + // player. + // + // (Helper for `advance_turn_` and `Model(int, int)`.) + void compute_next_moves_(); + + // Advances to the next turn by flipping `turn_` and updating + // `next_moves_`. Checks for game over. Returns whether any moves + // are now available (meaning game not over). + // + // (Helper for `really_play_move_`.) + bool advance_turn_(); + + // Sets the turn to neither and determines the winner, if any. + // + // (Helper for `really_play_move_`.) + void set_game_over_(); + + // Assuming `move` has been validated, actually executes it by setting + // the relevant board positions and then advancing the turn and checking + // for the game to be over. + // + // (Helper for `play_move`.) + // + // PRECONDITION (UNCHECKED): + // + // - `move` is a valid move right now, meaning it is present in + // `next_moves_` + void really_play_move_(Move move); + +}; + diff --git a/src/move.cxx b/src/move.cxx new file mode 100644 index 0000000..2af7e53 --- /dev/null +++ b/src/move.cxx @@ -0,0 +1,234 @@ +/*******************************************/ +/*** DO NOT CHANGE ANYTHING IN THIS FILE ***/ +/*******************************************/ + +#include "move.hxx" + +Position_set::Position_set(std::initializer_list elements) +{ + for (value_type element : elements) + (*this)[element] = true; +} + +bool Position_set::empty() const +{ + return bits_.none(); +} + +size_t Position_set::size() const +{ + return bits_.count(); +} + +bool +Position_set::operator[](Position_set::value_type p) const +{ + return bits_.test(index_of_checked_(p)); +} + +Position_set::reference +Position_set::operator[](Position_set::value_type p) +{ + return bits_[index_of_checked_(p)]; +} + +Position_set& Position_set::operator&=(Position_set that) +{ + bits_ &= that.bits_; + return *this; +} + +Position_set& Position_set::operator|=(Position_set that) +{ + bits_ |= that.bits_; + return *this; +} + +Position_set& Position_set::operator^=(Position_set that) +{ + bits_ ^= that.bits_; + return *this; +} + +Position_set Position_set::operator&(Position_set that) const +{ + Position_set result(*this); + return result &= that; +} + +Position_set Position_set::operator|(Position_set that) const +{ + Position_set result(*this); + return result |= that; +} + +Position_set Position_set::operator^(Position_set that) const +{ + Position_set result(*this); + return result ^= that; +} + +Position_set Position_set::operator~() const +{ + Position_set result(*this); + result.bits_ = ~result.bits_; + return result; +} + +void Position_set::clear() +{ + bits_.reset(); +} + +Position_set::iterator +Position_set::begin() const +{ + for (size_t i = 0; i < index_limit; ++i) + if (bits_.test(i)) + return iterator_(position_of_(i)); + + return end(); +} + +Position_set::iterator +Position_set::end() const +{ + return iterator_(position_of_(index_limit)); +} + +bool operator==(Position_set a, Position_set b) +{ + return a.bits_ == b.bits_; +} + +bool operator!=(Position_set a, Position_set b) +{ + return !(a == b); +} + +size_t Position_set::index_of_checked_(value_type p) +{ + size_t index = index_of_(p); + + if (index >= index_limit) + throw ge211::Client_logic_error("Position_set: out of bounds"); + + return index; +} + +size_t Position_set::index_of_(value_type p) +{ + return size_t(coord_limit * p.x + p.y); +} + +Position_set::value_type +Position_set::position_of_(size_t index) +{ + auto x = int(index / coord_limit); + auto y = int(index % coord_limit); + return {x, y}; +} + +Position_set::iterator +Position_set::iterator_(value_type p) const +{ + return {p, &bits_}; +} + +Position_set::iterator::iterator(value_type p, bits_t const* bits) noexcept + : bits_(bits) + , current_(p) +{ } + +Position_set::iterator::value_type +Position_set::iterator::operator*() const +{ + return current_; +} + +Position_set::iterator::value_type* +Position_set::iterator::operator->() const +{ + return ¤t_; +} + +Position_set::iterator& +Position_set::iterator::operator++() +{ + size_t current = index_of_(current_); + + do + current = current == index_limit ? 0 : current + 1; + while (!stopping_point_(current)); + + current_ = position_of_(current); + + return *this; +} + +Position_set::iterator& +Position_set::iterator::operator--() +{ + size_t current = index_of_(current_); + + do + current = current == 0 ? index_limit : current - 1; + while (!stopping_point_(current)); + + current_ = position_of_(current); + + return *this; +} + +Position_set::iterator +Position_set::iterator::operator++(int) +{ + iterator result(*this); + ++*this; + return result; +} + +Position_set::iterator +Position_set::iterator::operator--(int) +{ + iterator result(*this); + --*this; + return result; +} + +bool Position_set::iterator::stopping_point_(size_t index) const +{ + return index == index_limit || bits_->test(index); +} + +bool operator==(Position_set::iterator a, Position_set::iterator b) +{ + return *a == *b; +} + +bool operator!=(Position_set::iterator a, Position_set::iterator b) +{ + return !(a == b); +} + +std::ostream& operator<<(std::ostream& os, Position_set pset) +{ + os << "{"; + + bool first = true; + for (ge211::Position pos : pset) { + if (first) first = false; + else os << ", "; + os << "{" << pos.x << ", " << pos.y << "}"; + } + + return os << "}"; +} + +std::ostream& operator<<(std::ostream& os, Move const& move) +{ + return os + << "Move{{" << move.first.x << ", " << move.first.y << "}, " + << move.second << "}"; +} + diff --git a/src/move.hxx b/src/move.hxx new file mode 100644 index 0000000..e15c83d --- /dev/null +++ b/src/move.hxx @@ -0,0 +1,248 @@ +/*******************************************/ +/*** DO NOT CHANGE ANYTHING IN THIS FILE ***/ +/*******************************************/ + +#pragma once + +#include + +#include +#include +#include + +// Forward declaration for a set of `ge211::Position`s. The actual +// definition is below. +class Position_set; + +// A move. The two fields are: +// +// - first: the position played in +// - second: all positions changed by the move, *including* `first` +using Move = std::pair; + +// A map from move positions to their change sets. An +// `std::unordered_map` acts like a container holding +// `std::pair`s in which 1) the `K` values are distinct, +// and 2) pairs can be found easily by their `K` values. Thus +// a `Move_map` is a collection of `Move`s that we can look up +// by `first` (the position played in). +using Move_map = std::unordered_map; + +// An efficient set of `ge211::Position`s. +// +// The allowed range of the positions is limited by the constant +// `coord_limit` below. Right now it's 8, which means that the largest +// position a set can hold is {7, 7}. +class Position_set +{ +public: + // The element type of the set. + using value_type = ge211::Position; + + // All position coordinates must be between 0 (inclusive) and + // `coord_limit` (exclusive). + static constexpr int coord_limit = 8; + +private: + + // Maximum number of possible positions. + static constexpr size_t index_limit = coord_limit * coord_limit; + + // Private representation type. + using bits_t = std::bitset; + +public: + + /// + /// CONSTRUCTORS + /// + + // Constructs the default position set. + Position_set() noexcept = default; + + // Constructs the position set containing the given positions. + // For example: + // + // return Position_set{p1, p2}; + // + Position_set(std::initializer_list); + + // Constructs a position set given an iterator range of positions. + // For example: + // + // std::vector v; + // ... + // + // Position_set pos_set(v.begin(), v.end()); + // + template + explicit Position_set(FwdIter begin, FwdIter end); + + + /// + /// BASIC OPERATIONS + /// + + // Returns whether this set is empty. + bool empty() const; + + // Returns the number of positions in this set. + size_t size() const; + + // Returns whether position `p` is present in this set. + // + // PRECONDITIONS: + // - 0 <= p.x < coord_limit + // - 0 <= p.y < coord_limit + // + bool operator[](value_type p) const; + + // A proxy class that allows assigning a `bool` to add or remove + // a position. This is returned by `operator[](value_type)` to + // allow assigning a bool to the result (see below). + using reference = bits_t::reference; + + // Returns an object that, when assigned to, changes whether `p` is + // in the set. For example: + // + // set[pos] = false; + // + // + // PRECONDITIONS: + // - 0 <= p.x < coord_limit + // - 0 <= p.y < coord_limit + // + reference operator[](value_type p); + + // Removes all positions from the set. + void clear(); + + + /// + /// SET-LIKE OPERATIONS + /// + + // Sets the elements of `*this` to be the intersection `*this` and + // `that`. That is, `*this` will contain only the positions that + // were in both. + Position_set& operator&=(Position_set that); + + // Sets the elements of `*this` to be the union `*this` and `that`. + // That is, `*this` will contain all the positions of both. + Position_set& operator|=(Position_set that); + + // Sets the elements of `*this` to be the symmetric + // difference of `*this` and `that`. That is, `*this` will + // contain the positions that were in one or the other but + // not both. + Position_set& operator^=(Position_set that); + + // Returns the intersection of two sets. + Position_set operator&(Position_set) const; + + // Returns the union of two sets. + Position_set operator|(Position_set) const; + + // Returns the symmetric difference of two sets. + Position_set operator^(Position_set) const; + + // Returns the complement of this set, containing all positions + // that `*this` does not, and no positions that it does. + Position_set operator~() const; + + + /// + /// ITERATOR OPERATIONS + /// + + // An iterator over the positions of a set of positions. This is + // used not only for iteration, but as the result of `find(value_type)`, + // which checks for set membership. + class iterator; + + // Returns an iterator to the first position in the set. + iterator begin() const; + + // Returns an iterator one past the end of the set. + iterator end() const; + + + /// + /// FRIENDS + /// + + // Are two position sets equal? + friend bool operator==(Position_set, Position_set); + + // Are two position sets unequal? + friend bool operator!=(Position_set, Position_set); + +private: + + // Private helpers + static size_t index_of_checked_(value_type); + static size_t index_of_(value_type); + static value_type position_of_(size_t); + iterator iterator_(value_type) const; + + // Data + bits_t bits_; +}; + +class Position_set::iterator + : public std::iterator< + std::input_iterator_tag, + Position_set::value_type const> +{ +public: + // Returns the current `Position`. + value_type operator*() const; + + // Returns a pointer to the current `Position`. + value_type* operator->() const; + + // Advances to the next element of the set. + iterator& operator++(); + + // Retreats to the previous element of the set. + iterator& operator--(); + + // Advances to the next element of the set. + iterator operator++(int); + + // Retreats to the previous element of the set. + iterator operator--(int); + +private: + friend Position_set; + + iterator(value_type, bits_t const*) noexcept; + + bool stopping_point_(size_t index) const; + + bits_t const* bits_; + Position_set::value_type current_; +}; + +// Two iterators are equal when they dereference to +// the same position (even if they refer to different +// `Position_set`s). +bool operator==(Position_set::iterator, Position_set::iterator); + +// Two iterators are unequal when they aren't equal. +bool operator!=(Position_set::iterator, Position_set::iterator); + +// Prints a `Position_set`; suitable for debugging. +std::ostream& operator<<(std::ostream&, Position_set); + +// Prints a `Move`; suitable for debugging. +std::ostream& operator<<(std::ostream&, Move const&); + +// Implementation of range constructor (declared in class above). +template +Position_set::Position_set(FwdIter begin, FwdIter end) +{ + for ( ; begin != end; ++begin) + (*this)[*begin] = true; +} + diff --git a/src/player.cxx b/src/player.cxx new file mode 100644 index 0000000..78e9b40 --- /dev/null +++ b/src/player.cxx @@ -0,0 +1,27 @@ +/*******************************************/ +/*** DO NOT CHANGE ANYTHING IN THIS FILE ***/ +/*******************************************/ + +#include "player.hxx" + +Player other_player(Player player) +{ + switch (player) { + case Player::light: return Player::dark; + case Player::dark: return Player::light; + default: return player; + } +} + +std::ostream& operator<<(std::ostream& os, Player p) +{ + switch (p) { + case Player::light: + return os << "L"; + case Player::dark: + return os << "D"; + default: + return os << "_"; + } +} + diff --git a/src/player.hxx b/src/player.hxx new file mode 100644 index 0000000..7872caf --- /dev/null +++ b/src/player.hxx @@ -0,0 +1,21 @@ +/*******************************************/ +/*** DO NOT CHANGE ANYTHING IN THIS FILE ***/ +/*******************************************/ + +#pragma once + +#include + +// A player or lack thereof. +enum class Player +{ + dark, + light, + neither, +}; + +// Returns the other player. +Player other_player(Player); + +// Prints a player in a manner suitable for debugging. +std::ostream& operator<<(std::ostream&, Player); diff --git a/src/reversi.cxx b/src/reversi.cxx new file mode 100644 index 0000000..8b385fd --- /dev/null +++ b/src/reversi.cxx @@ -0,0 +1,39 @@ +#include "controller.hxx" + +#include +#include + +using namespace std; + +int main(int argc, char* argv[]) + +try { + /// + /// Don't change this command-line processing, as the grader + /// will expect to be able to start your program this way. + /// + int width, height; + switch (argc) { + case 1: + width = height = 4; + break; + case 3: + width = stoi(argv[1]); + height = stoi(argv[2]); + break; + default: + cerr << "Usage: " << argv[0] << " [WIDTH HEIGHT]\n"; + return 1; + } + + // You can change how you start your game class if you want (but + // you probably don't need to): + Controller(width, height).run(); +} + +// This prints out error messages if, say, the command-line argument +// cannot be parsed as `int`s. +catch (exception const& e) { + cerr << argv[0] << ": " << e.what() << "\n"; + return 1; +} diff --git a/src/view.cxx b/src/view.cxx new file mode 100644 index 0000000..192d720 --- /dev/null +++ b/src/view.cxx @@ -0,0 +1,135 @@ +#include "view.hxx" + +// Convenient type aliases: +using Dimensions = ge211::Dimensions; +using Position = ge211::Position; +using Color = ge211::Color; +using Sprite_set = ge211::Sprite_set; + +ge211::Color const black_color = ge211::Color::black(), + white_color = ge211::Color::white(), + red_color = ge211::Color::medium_red(), + green_color = ge211::Color::medium_green(), + dark_grey_color = ge211::Color::from_rgba(128, 128, 128), + very_light_grey_color = ge211::Color::from_rgba(220, 220, 220), + light_grey_color = ge211::Color::from_rgba(211, 211, 211); + + +View::View(Model const& model) + : model_(model) + , grid_offset_(compute_grid_offset(model_)) + , player_black_token_(7, black_color) + , player_white_token_(7, white_color) + , player_grey_token_(7, dark_grey_color) + , position_sprite_(8, red_color) + , background_black_sprite(this->initial_window_dimensions(), black_color) + , background_white_sprite(this->initial_window_dimensions(), + white_color) + , background_grey_sprite(this->initial_window_dimensions(), + light_grey_color) + // You may want to add sprite initialization here +{ } + +const int grid_size_ = 23; + +void View::draw(Sprite_set& set) +{ + Position centre = model_.board().center(); + + for(int i = 0; i < model_.board().dimensions().width; i++) { + for (int j = 0; j < model_.board().dimensions().height; j++) { + Position on_board = {i, j}; + ge211::Position screen_pos = board_to_screen_(on_board); + set.add_sprite(board_sprite_, screen_pos, 1); + const Move* move = model_.find_move(on_board); + + Player player = model_.operator[](on_board); + if(player == Player::dark) { + set.add_sprite(player_black_token_, screen_pos, 3); + } + else if(player == Player::light) { + set.add_sprite(player_white_token_, screen_pos, 3); + } + + if(move != nullptr && player == Player::neither) { + set.add_sprite(position_sprite_, screen_pos, 2); + } + + if(model_.is_game_over()) { + ge211::Rectangle board = model_.board(); + set.add_sprite(background_grey_sprite, {0,0}, 0); + if(model_.winner() == Player::light) { + for (Position each_pos: board) { + ge211::Position final_screen_pos = + board_to_screen_(each_pos); + if(model_.operator[](each_pos) == Player::dark){ + set.add_sprite(player_grey_token_, + final_screen_pos, 3); + + } + } + } + else if(model_.winner() == Player::dark){ + for (Position each_pos: board) { + ge211::Position final_screen_pos = + board_to_screen_(each_pos); + if(model_.operator[](each_pos) == Player::light){ + set.add_sprite(player_grey_token_, + final_screen_pos, 3); + + } + } + } else if (model_.winner() == Player::neither){ + for (Position each_pos: board) { + ge211::Position final_screen_pos = + board_to_screen_(each_pos); + set.add_sprite(player_grey_token_, + final_screen_pos, 3); + + } + } + } + else if(model_.turn() == Player::dark) { + set.add_sprite(background_black_sprite, {0,0}, 0); + } + else if(model_.turn() == Player:: light) { + set.add_sprite(background_white_sprite, {0,0}, 0); + } + } + } + +} + +Dimensions View::initial_window_dimensions() const +{ + // You can change this if you want: + return 50 * model_.board().dimensions(); +} + +std::string View::initial_window_title() const +{ + // You can change this if you want: + return "Reversi"; +} + +ge211::Position View::board_to_screen_(ge211::Position board_pos) const +{ + int x = grid_size_ * board_pos.x; + int y = grid_size_ * (board_pos.y); + return ge211::Position{x, y} + grid_offset_; +} + +ge211::Position View::screen_to_board_(ge211::Position screen_pos) const +{ + screen_pos = screen_pos - grid_offset_; + int col_no = screen_pos.x / grid_size_; + int row_no = screen_pos.y / grid_size_; + return {col_no, row_no}; +} + +ge211::Dimensions View::compute_grid_offset(Model const& model) +{ + ge211::Dimensions model_dims{model_.board().dimensions().width, model_ + .board().dimensions().height}; + return ( 50 * model_.board().dimensions() - grid_size_ * model_dims) / 2; +} \ No newline at end of file diff --git a/src/view.hxx b/src/view.hxx new file mode 100644 index 0000000..296eb44 --- /dev/null +++ b/src/view.hxx @@ -0,0 +1,46 @@ +#pragma once + +#include "model.hxx" +#include + +#include + +extern ge211::Color const green_color; + +class View +{ +public: + explicit View(Model const&); + + // You will probably want to add arguments here so that the + // controller can communicate UI state (such as a mouse or + // cursor position): + void draw(ge211::Sprite_set& set); + + ge211::Position board_to_screen_(ge211::Position board_pos) const; + + ge211::Position screen_to_board_(ge211::Position screen_pos) const; + + ge211::Dimensions initial_window_dimensions() const; + + std::string initial_window_title() const; + +private: + Model const& model_; + ge211::Circle_sprite const player_white_token_; + ge211::Circle_sprite const player_black_token_; + ge211::Circle_sprite const player_grey_token_; + ge211::Rectangle_sprite const background_black_sprite; + ge211::Rectangle_sprite const background_white_sprite; + ge211::Rectangle_sprite const background_grey_sprite; + + ge211::Rectangle_sprite const + board_sprite_ {{16, 16}, green_color}; + + ge211::Circle_sprite const position_sprite_; + + ge211::Dimensions grid_offset_; + + ge211::Dimensions compute_grid_offset(Model const& model); + +}; diff --git a/test/board_test.cxx b/test/board_test.cxx new file mode 100644 index 0000000..6ad6289 --- /dev/null +++ b/test/board_test.cxx @@ -0,0 +1,100 @@ +#include "board.hxx" +#include +#include + +using namespace ge211; + +TEST_CASE("board operations") +{ + Board b({8, 8}); + + b[{2, 1}] = Player::light; + + CHECK( b[{1, 1}] == Player::neither ); + CHECK( b[{2, 1}] == Player::light ); + CHECK( b[{3, 1}] == Player::neither ); + + b[{3, 1}] = Player::dark; + CHECK( b[{1, 1}] == Player::neither ); + CHECK( b[{2, 1}] == Player::light ); + CHECK( b[{3, 1}] == Player::dark ); + + b[{1, 1}] = Player::light; + CHECK( b[{1, 1}] == Player::light ); + CHECK( b[{2, 1}] == Player::light ); + CHECK( b[{3, 1}] == Player::dark ); + + CHECK( b.count_player(Player::light) == 2 ); + CHECK( b.count_player(Player::dark) == 1 ); + + b[{0, 1}] = Player::dark; + b[{1, 1}] = Player::dark; + b[{2, 1}] = Player::dark; + CHECK( b[{0, 1}] == Player::dark ); + CHECK( b[{1, 1}] == Player::dark ); + CHECK( b[{2, 1}] == Player::dark ); + CHECK( b[{3, 1}] == Player::dark ); + + CHECK( b.count_player(Player::light) == 0 ); + CHECK( b.count_player(Player::dark) == 4 ); + + Board b1(b); + + CHECK( b == b ); + CHECK( b1 == b ); + b1[{3, 4}] = Player::light; + CHECK( b1 != b ); + b[{3, 4}] = Player::light; + CHECK( b1 == b ); +} + +TEST_CASE("board mass operations") +{ + Board b({6, 6}); + + CHECK( b.count_player(Player::light) == 0 ); + CHECK( b.count_player(Player::dark) == 0 ); + CHECK( b.count_player(Player::neither) == 36 ); + + b.set_all({{1, 1}, {2, 2}, {3, 3}, {4, 4}}, Player::dark); + CHECK( b.count_player(Player::light) == 0 ); + CHECK( b.count_player(Player::dark) == 4 ); + CHECK( b.count_player(Player::neither) == 32 ); + + b.set_all({{2, 0}, {2, 1}, {2, 2}, {2, 4}}, Player::light); + CHECK( b.count_player(Player::light) == 4 ); + CHECK( b.count_player(Player::dark) == 3 ); + CHECK( b.count_player(Player::neither) == 29 ); +} + +TEST_CASE("board errors") +{ + Board b({4, 4}); + + CHECK( b[{0, 0}] == Player::neither ); + CHECK( b[{3, 3}] == Player::neither ); + + CHECK_THROWS_AS((b[{3, 4}]), Client_logic_error); + CHECK_THROWS_AS((b[{4, 3}]), Client_logic_error); + CHECK_THROWS_AS((b[{4, 4}]), Client_logic_error); + CHECK_THROWS_AS((b[{-1, 2}]), Client_logic_error); + + CHECK_THROWS_AS(Board({1, 3}), Client_logic_error); + CHECK_THROWS_AS(Board({-1, 3}), Client_logic_error); +} + +TEST_CASE("board stream insertion") +{ + Board b({4, 4}); + b[{1, 1}] = Player::light; + b[{2, 2}] = Player::light; + b[{2, 1}] = Player::dark; + b[{1, 2}] = Player::dark; + + std::ostringstream oss; + oss << b; + CHECK( oss.str() == "____\n" + "_LD_\n" + "_DL_\n" + "____\n" ); +} diff --git a/test/model_test.cxx b/test/model_test.cxx new file mode 100644 index 0000000..43256a6 --- /dev/null +++ b/test/model_test.cxx @@ -0,0 +1,317 @@ +#include "model.hxx" +#include + +using namespace ge211; + +struct Test_access +{ + Model& m_ ; + Board& board() + { + return m_.board_ ; + } + Position_set find_flips( Position start, Dimensions dir ) + { + return m_.find_flips_(start, dir); + } + void set_game_over() + { + return m_.set_game_over_(); + } + void really_play_move(Move move) + { + return m_.really_play_move_(move); + } + bool advance_turn() + { + return m_.advance_turn_(); + } + Move_map compute_next_moves() + { + m_.compute_next_moves_(); + return m_.next_moves_; + } + Position_set evaluate_position(Position position) + { + return m_.evaluate_position_(position); + } +}; + +// These pass with the starter code and should continue +// to pass. +TEST_CASE("Passes with starter code") +{ + Model m(8, 8); + CHECK( m.board() == Rectangle{0, 0, 8, 8} ); + CHECK_FALSE( m.is_game_over() ); + CHECK( m.turn() == Player::dark ); + CHECK( m.winner() == Player::neither ); + CHECK( m[{0, 0}] == Player::neither ); + CHECK( m.find_move({0, 0}) == nullptr ); + CHECK_THROWS_AS( m.play_move({0, 0}), Client_logic_error ); +} + +// This fails with the starter code, but should pass. +TEST_CASE("Find move") +{ + Model m(6); + + CHECK( m.find_move({2, 2}) ); +} + +// This fails with the starter code, but should pass. +TEST_CASE("Play move") +{ + Model m(4); + m.play_move({1, 1}); +} + +TEST_CASE("simple flips") +{ + Model model; + Test_access t{model}; + t.board()[{2,2}] = Player::dark; + t.board()[{2,3}] = Player::light; + Position_set f = {}; + CHECK(f.empty()); + f = t.find_flips({2,4}, {0, 1}); + CHECK(f.empty()); + f = t.find_flips({2,4}, {0, -1}); + CHECK(f == Position_set{{2, 3}}); +} + +TEST_CASE("check game over with clear winner") +{ + Model model; + Test_access t{model}; + CHECK(t.m_.winner() == Player :: neither); + t.board()[{2 , 2}] = Player :: dark ; + t.board()[{2 , 3}] = Player :: dark ; + CHECK(t.m_.winner() == Player :: neither); + t.set_game_over(); + CHECK(t.m_.winner() == Player :: dark); +} + +TEST_CASE("check game over with equal") +{ + Model model; + Test_access t{model}; + t.board()[{2 , 2}] = Player :: dark ; + t.board()[{2 , 3}] = Player :: light ; + CHECK(t.m_.winner() == Player :: neither); + t.set_game_over(); + CHECK(t.m_.winner() == Player :: neither); +} + +TEST_CASE("Simple advance_turn") +{ + Model model; + Test_access t{model}; + t.board()[{2,2}] = Player::dark; + t.board()[{2,3}] = Player::light; + Position position = {2, 4}; + Position_set positionSet = {{2,3}}; + Move move = {position, positionSet}; + CHECK(t.m_.turn() == Player::dark); + CHECK(t.advance_turn()); +} + +TEST_CASE("complicated advance_turn") +{ + Model model(6); + Test_access t{model}; + t.board()[{2,2}] = Player::dark; + t.board()[{2,3}] = Player::dark; + t.board()[{3,2}] = Player::dark; + t.board()[{3,3}] = Player::dark; + + Position position = {2, 4}; + Position_set positionSet = {{2,3}}; + Move move = {position, positionSet}; + CHECK(t.m_.turn() == Player::dark); + CHECK_FALSE(t.advance_turn()); +} + + +TEST_CASE("checks if a simple really_play_move works") +{ + Model model; + Test_access t{model}; + t.board()[{2,2}] = Player::dark; + t.board()[{2,3}] = Player::light; + Position position = {2,4}; + Position_set position_set = {{2,3}}; + Move move = {position, position_set}; + CHECK(t.m_.turn() == Player::dark); + t.really_play_move(move); + CHECK(t.m_.operator[]({2,3}) == Player::dark); + CHECK(t.m_.operator[]({2,4}) == Player::dark); +} + +TEST_CASE("checks if a simple compute_move works") +{ + Model model(6); + Test_access t{model}; + Move_map moves = t.compute_next_moves(); + CHECK_FALSE(moves.empty()); + CHECK(moves.size() == 4 ); +} + +TEST_CASE("checks if a complicated compute_move works") +{ + Model model(6); + Test_access t{model}; + Position position = {2,2}; + Position_set position_set = {{2,2}}; + Move move = {position, position_set}; + CHECK(t.m_.turn() == Player::dark); + model.play_move(position); + // t.really_play_move(move); + + position = {2,3}; + position_set = {{2,3}}; + Move move1 = {position, position_set}; + CHECK(t.m_.turn() == Player::light); + t.really_play_move(move1); + + position = {3,2}; + position_set = {{3,2}}; + Move move2 = {position, position_set}; + CHECK(t.m_.turn() == Player::dark); + t.really_play_move(move2); + + position = {3,3}; + position_set = {{3,3}}; + Move move3 = {position, position_set}; + CHECK(t.m_.turn() == Player::light); + t.really_play_move(move3); + + Move_map moves = t.compute_next_moves(); + CHECK_FALSE(moves.empty()); + CHECK(moves.size() == 4 ); +} + +TEST_CASE("Play move and then compute moves") +{ + Model m(4); + m.play_move({1, 1}); + Test_access t{m}; + Move_map moves = t.compute_next_moves(); + CHECK_FALSE(moves.empty()); + CHECK(moves.size() == 3 ); +} + +TEST_CASE("Flip more than one in different directions") +{ + Model model(6); + Test_access t{model}; + t.board()[{2,2}] = Player::dark; + t.board()[{2,3}] = Player::light; + t.board()[{4,2}] = Player::dark; + t.board()[{3,3}] = Player::light; + Position_set f = {}; + CHECK(f.empty()); + f = t.evaluate_position({2,4}); + CHECK_FALSE(f.empty()); + t.really_play_move({{2,4}, f}); + CHECK(t.m_.operator[]({2,3}) == Player::dark); + CHECK(t.m_.operator[]({2,4}) == Player::dark); + CHECK(t.m_.operator[]({3,3}) == Player::dark); +} + + +TEST_CASE("Flip more than one in same direction") +{ + Model model(6); + Test_access t{model}; + t.board()[{2,2}] = Player::dark; + t.board()[{2,3}] = Player::light; + t.board()[{2,4}] = Player::light; + Position_set f = {}; + CHECK(f.empty()); + f = t.evaluate_position({2,5}); + CHECK_FALSE(f.empty()); + t.really_play_move({{2,5}, f}); + CHECK(t.m_.operator[]({2,3}) == Player::dark); + CHECK(t.m_.operator[]({2,4}) == Player::dark); + CHECK(t.m_.operator[]({2,5}) == Player::dark); +} + +TEST_CASE("Player repeat") +{ + Model model(4); + Test_access t{model}; + t.board()[{0,1}] = Player::dark; + t.board()[{0,3}] = Player::dark; + t.board()[{1,1}] = Player::dark; + t.board()[{1,3}] = Player::dark; + t.board()[{2,2}] = Player::dark; + t.board()[{2,3}] = Player::dark; + t.board()[{2,1}] = Player::light; + t.board()[{1,2}] = Player::light; + Position_set f = {}; + CHECK(f.empty()); + f = t.evaluate_position({0,2}); + CHECK_FALSE(f.empty()); + CHECK(t.m_.turn() == Player::dark); + t.really_play_move({{0,2}, f}); + CHECK(t.m_.operator[]({1,2}) == Player::dark); + CHECK(t.m_.turn() == Player::dark); + +} + +TEST_CASE("when 1 of 4 is empty") +{ + Model model(6); + Test_access t{model}; + t.board()[{3,2}] = Player::dark; + t.board()[{2,3}] = Player::dark; + t.board()[{3,3}] = Player::light; + t.board()[{3,1}] = Player::light; + CHECK(model.turn() == Player::dark); + CHECK(t.m_.operator[]({2,2}) == Player::neither); + model.play_move({2,2}); + CHECK(t.m_.operator[]({2,2}) == Player::dark); + CHECK(model.turn() == Player::light); + Move_map moves = t.compute_next_moves(); + CHECK_FALSE(moves.empty()); + CHECK(moves.size() == 2 ); +} + +TEST_CASE("check simple evaluate position") +{ + Model model (6); + Test_access t{model}; + + t.board()[{3,3}] = Player::dark; + t.board()[{2,3}] = Player::light; + t.board()[{2,2}] = Player::dark; + t.board()[{3,2}] = Player::light; + + Position_set ps = t.evaluate_position({5,5}); + CHECK(ps.empty()); + + ps = t.evaluate_position({1,3}); + CHECK(ps == Position_set{{2,3}, {1,3}}); +} + +TEST_CASE("check by playing simple game") +{ + Model m(2); + CHECK(m.board() == Rectangle{0, 0, 2, 2}); + CHECK_FALSE(m.is_game_over()); + CHECK(m.winner() == Player::neither); + CHECK(m.turn() == Player::dark); + m.play_move({0,0}); + CHECK(m.turn() == Player::light); + m.play_move({0,1}); + CHECK(m.turn() == Player::dark); + m.play_move({1,0}); + CHECK(m.turn() == Player::light); + CHECK(m.winner() == Player::neither); + CHECK_FALSE(m.is_game_over()); + CHECK(m.find_move({1,1})); + m.play_move({1,1}); + CHECK(m.is_game_over()); + CHECK(m.winner() == Player::neither); +} \ No newline at end of file diff --git a/test/move_test.cxx b/test/move_test.cxx new file mode 100644 index 0000000..bb6c168 --- /dev/null +++ b/test/move_test.cxx @@ -0,0 +1,56 @@ +#include "move.hxx" + +#include + +#include +#include + +using namespace ge211; + +TEST_CASE("Position_set constructors") +{ + Position_set set1; + CHECK( set1.empty() ); + CHECK( set1.size() == 0 ); + + Position_set set2{{2, 3}, {3, 2}}; + CHECK_FALSE( set2.empty() ); + CHECK( set2.size() == 2 ); + + std::vector v3{{2, 3}, {3, 2}, {4, 1}}; + Position_set set3(v3.begin(), v3.end()); + CHECK( set3.size() == 3 ); +} + +TEST_CASE("moves are equal up to permutation") +{ + Move m1{{2, 3}, {{2, 3}, {2, 4}, {2, 5}, {2, 6}}}; + Move m2{{2, 3}, {{2, 4}, {2, 5}, {2, 6}, {2, 3}}}; + Move m3{{2, 3}, {{2, 4}, {2, 5}, {2, 6}, {2, 7}}}; + + CHECK( m1 == m1 ); + CHECK( m1 == m2 ); + CHECK( m2 == m1 ); + CHECK( m2 == m2 ); + CHECK( m3 == m3 ); + + CHECK( m1 != m3 ); + CHECK( m2 != m3 ); + CHECK( m3 != m1 ); + CHECK( m3 != m2 ); +} + +TEST_CASE("move stream insertion") +{ + std::ostringstream oss; + + Move m1{{2, 3}, {}}; + Move m2{{2, 6}, {{2, 4}, {2, 5}, {2, 6}}}; + + oss << m1; + CHECK( oss.str() == "Move{{2, 3}, {}}" ); + + oss.str(""); + oss << m2; + CHECK( oss.str() == "Move{{2, 6}, {{2, 4}, {2, 5}, {2, 6}}}" ); +} diff --git a/test/player_test.cxx b/test/player_test.cxx new file mode 100644 index 0000000..3bfc190 --- /dev/null +++ b/test/player_test.cxx @@ -0,0 +1,33 @@ +#include "player.hxx" +#include +#include + +TEST_CASE("players equal themselves and not each other") +{ + CHECK(Player::light == Player::light); + CHECK(Player::dark == Player::dark); + CHECK(Player::neither == Player::neither); + CHECK(Player::light != Player::dark); + CHECK(Player::light != Player::neither); + CHECK(Player::dark != Player::light); + CHECK(Player::dark != Player::neither); + CHECK(Player::neither != Player::light); + CHECK(Player::neither != Player::dark); +} + +TEST_CASE("other_player works") +{ + CHECK(other_player(Player::light) == Player::dark); + CHECK(other_player(Player::dark) == Player::light); +} + +TEST_CASE("stream insertion") +{ + std::ostringstream oss; + oss << Player::light; + CHECK(oss.str() == "L"); + oss << Player::neither; + CHECK(oss.str() == "L_"); + oss << Player::dark; + CHECK(oss.str() == "L_D"); +}