From b44da2fb0cc1c4a8e2e24bd056de62f62d17a466 Mon Sep 17 00:00:00 2001 From: tejas bhuwania Date: Mon, 13 Jul 2020 02:37:44 +0800 Subject: [PATCH] All files --- .cs211/README.md | 17 + .cs211/cmake/211commands.cmake | 50 + .cs211/cmake/211helpers.cmake | 85 + .cs211/cmake/211installer.cmake | 27 + .cs211/cmake/CMakeLists.txt | 14 + .cs211/idea/codeStyles/Project.xml | 98 + .cs211/idea/codeStyles/codeStyleConfig.xml | 5 + .cs211/idea/inspectionProfiles/CS_211.xml | 31 + .../inspectionProfiles/Project_Default.xml | 21 + .../inspectionProfiles/profiles_settings.xml | 6 + .cs211/idea/misc.xml | 13 + .cs211/lib/catch/.gitignore | 2 + .cs211/lib/catch/CMakeLists.txt | 53 + .cs211/lib/catch/include/catch.hxx | 13359 ++++++++++++++++ .cs211/lib/catch/src/catch-main.cxx | 2 + .cs211/lib/ge211/.gitignore | 7 + .cs211/lib/ge211/.travis.yml | 44 + .../ge211/3rdparty/utf8-cpp/CMakeLists.txt | 9 + .../ge211/3rdparty/utf8-cpp/include/utf8.h | 34 + .../3rdparty/utf8-cpp/include/utf8/checked.h | 327 + .../3rdparty/utf8-cpp/include/utf8/core.h | 329 + .../utf8-cpp/include/utf8/unchecked.h | 228 + .cs211/lib/ge211/CMakeLists.txt | 67 + .cs211/lib/ge211/Makefile | 19 + .cs211/lib/ge211/README.md | 71 + .../lib/ge211/Resources/LICENSE.sans.ttf.txt | 674 + .cs211/lib/ge211/Resources/pop.ogg | Bin 0 -> 7134 bytes .cs211/lib/ge211/Resources/sans.ttf | Bin 0 -> 422472 bytes .cs211/lib/ge211/TODO.md | 8 + .cs211/lib/ge211/cmake/FindSDL2.cmake | 209 + .cs211/lib/ge211/cmake/FindSDL2_image.cmake | 86 + .cs211/lib/ge211/cmake/FindSDL2_mixer.cmake | 87 + .cs211/lib/ge211/cmake/FindSDL2_ttf.cmake | 85 + .../ge211/cmake/Ge211ClientInstaller.cmake | 122 + .cs211/lib/ge211/cmake/Ge211Installer.cmake | 36 + .cs211/lib/ge211/doc/CMakeLists.txt | 61 + .cs211/lib/ge211/doc/DoxygenLayout.xml | 194 + .cs211/lib/ge211/example/CMakeLists.txt | 23 + .cs211/lib/ge211/example/fireworks.cxx | 326 + .cs211/lib/ge211/include/ge211.hxx | 18 + .cs211/lib/ge211/include/ge211_audio.hxx | 474 + .cs211/lib/ge211/include/ge211_base.hxx | 314 + .cs211/lib/ge211/include/ge211_color.hxx | 318 + .cs211/lib/ge211/include/ge211_engine.hxx | 35 + .cs211/lib/ge211/include/ge211_error.hxx | 252 + .cs211/lib/ge211/include/ge211_event.hxx | 263 + .cs211/lib/ge211/include/ge211_forward.hxx | 122 + .cs211/lib/ge211/include/ge211_geometry.hxx | 811 + .cs211/lib/ge211/include/ge211_noexcept.hxx | 13 + .cs211/lib/ge211/include/ge211_random.hxx | 150 + .cs211/lib/ge211/include/ge211_render.hxx | 102 + .cs211/lib/ge211/include/ge211_resource.hxx | 71 + .cs211/lib/ge211/include/ge211_session.hxx | 62 + .cs211/lib/ge211/include/ge211_sprites.hxx | 405 + .cs211/lib/ge211/include/ge211_time.hxx | 402 + .cs211/lib/ge211/include/ge211_util.hxx | 228 + .cs211/lib/ge211/include/ge211_version.hxx.in | 5 + .cs211/lib/ge211/include/ge211_window.hxx | 80 + .cs211/lib/ge211/scripts/autotools_install.sh | 42 + .cs211/lib/ge211/src/CMakeLists.txt | 64 + .cs211/lib/ge211/src/ge211_audio.cxx | 464 + .cs211/lib/ge211/src/ge211_base.cxx | 92 + .cs211/lib/ge211/src/ge211_color.cxx | 298 + .cs211/lib/ge211/src/ge211_engine.cxx | 184 + .cs211/lib/ge211/src/ge211_error.cxx | 208 + .cs211/lib/ge211/src/ge211_event.cxx | 152 + .cs211/lib/ge211/src/ge211_geometry.cxx | 150 + .cs211/lib/ge211/src/ge211_random.cxx | 27 + .cs211/lib/ge211/src/ge211_render.cxx | 220 + .cs211/lib/ge211/src/ge211_resource.cxx | 68 + .cs211/lib/ge211/src/ge211_session.cxx | 88 + .cs211/lib/ge211/src/ge211_sprites.cxx | 343 + .cs211/lib/ge211/src/ge211_window.cxx | 109 + .gitignore | 11 + .idea/codeStyles/Project.xml | 98 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/inspectionProfiles/CS_211.xml | 31 + .idea/inspectionProfiles/Project_Default.xml | 21 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 16 + .run/helper_test.run.xml | 8 + .run/model_test.run.xml | 8 + .run/reversi.run.xml | 7 + CMakeLists.txt | 41 + src/board.cxx | 209 + src/board.hxx | 209 + src/controller.cxx | 41 + src/controller.hxx | 53 + src/model.cxx | 194 + src/model.hxx | 180 + src/move.cxx | 234 + src/move.hxx | 248 + src/player.cxx | 27 + src/player.hxx | 21 + src/reversi.cxx | 39 + src/view.cxx | 135 + src/view.hxx | 46 + test/board_test.cxx | 100 + test/model_test.cxx | 317 + test/move_test.cxx | 56 + test/player_test.cxx | 33 + 101 files changed, 25857 insertions(+) create mode 100644 .cs211/README.md create mode 100644 .cs211/cmake/211commands.cmake create mode 100644 .cs211/cmake/211helpers.cmake create mode 100644 .cs211/cmake/211installer.cmake create mode 100644 .cs211/cmake/CMakeLists.txt create mode 100644 .cs211/idea/codeStyles/Project.xml create mode 100644 .cs211/idea/codeStyles/codeStyleConfig.xml create mode 100644 .cs211/idea/inspectionProfiles/CS_211.xml create mode 100644 .cs211/idea/inspectionProfiles/Project_Default.xml create mode 100644 .cs211/idea/inspectionProfiles/profiles_settings.xml create mode 100644 .cs211/idea/misc.xml create mode 100644 .cs211/lib/catch/.gitignore create mode 100644 .cs211/lib/catch/CMakeLists.txt create mode 100644 .cs211/lib/catch/include/catch.hxx create mode 100644 .cs211/lib/catch/src/catch-main.cxx create mode 100644 .cs211/lib/ge211/.gitignore create mode 100644 .cs211/lib/ge211/.travis.yml create mode 100644 .cs211/lib/ge211/3rdparty/utf8-cpp/CMakeLists.txt create mode 100644 .cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8.h create mode 100644 .cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/checked.h create mode 100644 .cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/core.h create mode 100644 .cs211/lib/ge211/3rdparty/utf8-cpp/include/utf8/unchecked.h create mode 100644 .cs211/lib/ge211/CMakeLists.txt create mode 100644 .cs211/lib/ge211/Makefile create mode 100644 .cs211/lib/ge211/README.md create mode 100644 .cs211/lib/ge211/Resources/LICENSE.sans.ttf.txt create mode 100644 .cs211/lib/ge211/Resources/pop.ogg create mode 100644 .cs211/lib/ge211/Resources/sans.ttf create mode 100644 .cs211/lib/ge211/TODO.md create mode 100644 .cs211/lib/ge211/cmake/FindSDL2.cmake create mode 100644 .cs211/lib/ge211/cmake/FindSDL2_image.cmake create mode 100644 .cs211/lib/ge211/cmake/FindSDL2_mixer.cmake create mode 100644 .cs211/lib/ge211/cmake/FindSDL2_ttf.cmake create mode 100644 .cs211/lib/ge211/cmake/Ge211ClientInstaller.cmake create mode 100644 .cs211/lib/ge211/cmake/Ge211Installer.cmake create mode 100644 .cs211/lib/ge211/doc/CMakeLists.txt create mode 100644 .cs211/lib/ge211/doc/DoxygenLayout.xml create mode 100644 .cs211/lib/ge211/example/CMakeLists.txt create mode 100644 .cs211/lib/ge211/example/fireworks.cxx create mode 100644 .cs211/lib/ge211/include/ge211.hxx create mode 100644 .cs211/lib/ge211/include/ge211_audio.hxx create mode 100644 .cs211/lib/ge211/include/ge211_base.hxx create mode 100644 .cs211/lib/ge211/include/ge211_color.hxx create mode 100644 .cs211/lib/ge211/include/ge211_engine.hxx create mode 100644 .cs211/lib/ge211/include/ge211_error.hxx create mode 100644 .cs211/lib/ge211/include/ge211_event.hxx create mode 100644 .cs211/lib/ge211/include/ge211_forward.hxx create mode 100644 .cs211/lib/ge211/include/ge211_geometry.hxx create mode 100644 .cs211/lib/ge211/include/ge211_noexcept.hxx create mode 100644 .cs211/lib/ge211/include/ge211_random.hxx create mode 100644 .cs211/lib/ge211/include/ge211_render.hxx create mode 100644 .cs211/lib/ge211/include/ge211_resource.hxx create mode 100644 .cs211/lib/ge211/include/ge211_session.hxx create mode 100644 .cs211/lib/ge211/include/ge211_sprites.hxx create mode 100644 .cs211/lib/ge211/include/ge211_time.hxx create mode 100644 .cs211/lib/ge211/include/ge211_util.hxx create mode 100644 .cs211/lib/ge211/include/ge211_version.hxx.in create mode 100644 .cs211/lib/ge211/include/ge211_window.hxx create mode 100755 .cs211/lib/ge211/scripts/autotools_install.sh create mode 100644 .cs211/lib/ge211/src/CMakeLists.txt create mode 100644 .cs211/lib/ge211/src/ge211_audio.cxx create mode 100644 .cs211/lib/ge211/src/ge211_base.cxx create mode 100644 .cs211/lib/ge211/src/ge211_color.cxx create mode 100644 .cs211/lib/ge211/src/ge211_engine.cxx create mode 100644 .cs211/lib/ge211/src/ge211_error.cxx create mode 100644 .cs211/lib/ge211/src/ge211_event.cxx create mode 100644 .cs211/lib/ge211/src/ge211_geometry.cxx create mode 100644 .cs211/lib/ge211/src/ge211_random.cxx create mode 100644 .cs211/lib/ge211/src/ge211_render.cxx create mode 100644 .cs211/lib/ge211/src/ge211_resource.cxx create mode 100644 .cs211/lib/ge211/src/ge211_session.cxx create mode 100644 .cs211/lib/ge211/src/ge211_sprites.cxx create mode 100644 .cs211/lib/ge211/src/ge211_window.cxx create mode 100644 .gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/inspectionProfiles/CS_211.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .run/helper_test.run.xml create mode 100644 .run/model_test.run.xml create mode 100644 .run/reversi.run.xml create mode 100644 CMakeLists.txt create mode 100644 src/board.cxx create mode 100644 src/board.hxx create mode 100644 src/controller.cxx create mode 100644 src/controller.hxx create mode 100644 src/model.cxx create mode 100644 src/model.hxx create mode 100644 src/move.cxx create mode 100644 src/move.hxx create mode 100644 src/player.cxx create mode 100644 src/player.hxx create mode 100644 src/reversi.cxx create mode 100644 src/view.cxx create mode 100644 src/view.hxx create mode 100644 test/board_test.cxx create mode 100644 test/model_test.cxx create mode 100644 test/move_test.cxx create mode 100644 test/player_test.cxx 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 0000000000000000000000000000000000000000..1ba427df5bea85236a261d591a08603058f7fceb GIT binary patch literal 7134 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%0DczcN{#{+R)f?Z0Qd}xkfhOIgu7qoLoJT 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

0iPE-16pz|FnPM~@M8V*F3mlVM* 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 ~S#vzI=6Gh!HB+u;%EdEmj%U~$&#*b3VRJmg z=6Ht9@eG@zOP!-jo#Pocm*=?hESuw5Hb<8`$FppXXW1OjvN@h*b3DuDc$UraESuw5 zHpjDUj%V2%&$77|t}C8tb3DuDS^}%h!B;_SQ7+#?3uGX%6b2GoWa^16GLYCJ1Bop% zknD5=i7hg1#Xgz52x_6FLMeS1j1WB%9?4?SPuFv$m4xOo_BZoCfWOW-rePScbDhgU7jcSJWufW z`x|!e=XrORrw5Ve-Cdq%_dL(;d7j<#JgetUo~k^E|8P zc~;N!te)ptJvhwHydY(ExPo18pPR~=P=c&{4)aiNZ^gMNX zo;p2Goo;^bZghH{Iz3OFo~KUFQ>W*t)AQ8ndFu4MFFHL>ot~#oFL0d{xK0XO7X_}1 z0@p=>>!QGQQQ*2La9tF*9tvC!1+Iqz*Fk~npulxd;5sO99Td0@3S0*Tu7d*CL4oU_ zz;#gIIw)`*6u1rwTn7cNg96t!84En*y(G z3cR){aD5cGJ_=kP1+I?**F}NrqQG@g;JPSqT@<(u3S0*Tu7d*CL4oU_z;#gIIw)`* z6gd9{&U=CLUf{eJXu}J%;RV|80&RAIHoHLES)lDK&~_GRI}5a(1=`L6ZD)bDvq0Nf zpzSQ=o5r{GpWBY-zC-1XxS#1_+69gAO$Xa6KAshGF zVmy>FO@@bUp2XedMr8GmE*`<#Ef|^e%eb=b#`|t&loprEL#DXL04A%)LMCf8${LNK zAzNt1dq_Z2uQRGwj+^2RS9&Ykrlvu6c^JP;wsX~8?p=89h%kVs%tMO}%R@t!^C1yn ziHstsn6lp99>ZF1OdT19f*Ik9I)?^K9lV)gl93Ar3{NB7cu&9-FB}-P)fV>PJr3Z* zJr|ntVZ7GSZ<+7u>K-u_`+NF34WD4MuSeFD1>4FVa)(9-JMoUj(Bc@uJz2j&+DC_m zI{Vv~q-&8(Et0K8{QYX$^mYypkE_Ac&2|QcF^fZ1UkB8u0rhD>eHu`o2Gpkk^=ZI6 z@PK+Wp#BV~KLhH|fci6_{tT!;1M1I!`ZJ*Z45&W?>d%1sGobzqs6PYh&w%dk<9GoaoKs4oNR%YgbapuP;KCj;uqfO;~Z zehjD|1M0_s`Z1t>45$wS>cfC~FrXd`s0RZ+Hw$coqqG1_`Ji1M0_s`Z1t> z45%Li>c@ckF`#}7s2>CB$AD*xfM<$;XNrJlihw?UK%YOLj~~#-59s3ud~Oxc*AMvI zD&TXgfVVOMZ)F1B$^^WX3Hbae;H^x+TbY2jG68R80^Z65yp;)fD--ZmCg811z+0Js zw=w~5Wdh#H1iY0A_^w01+nIp3GXZaB0^ZI9yqyVnI}`ABCg811z+0Jsw=n^4V*=jB z1iXz2cpDS&)+OMrOTb%~fVVCIZ(Rc3x&*v+33$5_@OCBOZA!q~lz_J<0q-ON-bnXDH8Bc$F4sW(FEi;(&vq`nBb??dkUko!C2{tmgnL+|2B}49;kozX&ZDh#(6LSBAyp0Tbn;7!$Fy!4~$h*UkcZVVG2#cJb zBIl>b`6+UKikzP!=cmZ|DRO>_oS!1+r^xv!a(;@OpCad{NIhDl9xYOj7CCQ4&Rdc5 zR^+@DId4Vk%_8+?k$SVp`7Clii`1L=bGUZD7O6LjoaZ9vvB-HWavqDE$0GGmk$R{| zJyfI~DpC&>sfUWxLq+PLBK1&_dZ@(xP~v!(INl|WYl-7pqTMO+J%|#=uf*{waePX& zGbN5wiQ`n__>?$4C5}&t<5J?dlsFzGjz@{>zQlE3qCF|mj+D3$O56t}j$eu6SK_#p zxc*CA|0UX!68Axg`=G>qP~z==iMRVD+LsddLy7yLM0--AJt^@Pzr=ba)7dr)<^u=BQCw;M7z)4^HUEriI_AA{g_6sTH7k?Kx*t`v{!!%Y71X$}jd3IOP|637qoFeG+iWFa9xb z$}jd5IOP|6nr;<)ij?w;Jq1qr8)FMc&}$}i6d(yeme zfRysfGXmh0U!D;Fr~LAa0668BTVUXnU;K68lwbUH;FMqdcHop>{DgF?JWD`I`Q=#x zaLO;w5`a^F@h5;&e(@)OQ-1L$fKz_)CxBCa@h8%);_oA+{Nncm&l>%Xl=S6U0&voo zX9>VbU!ElZCw+OA0G#yYSpsm<&+CS>?F(NG7f5oped(*=0-KCIJ9;Z*>;(}d{D>V* zw~8G_O8TPjfs?-Id*GxmPm+O?zS!4vtJqhhlwa&DaLO-s0yyVi>@0B3zt~yeoPV(& z=~l59NZGI03*hWm>;`c5EA|vP`xW~Eoc)UZ0M2zK=MmssS7Jwib6trYNwulszX&+#i(dqs^u;d%PWs{(0VjR& zi_)#~%ou69;T+juOKc*$NS+v@l%uanU32v12{OuM^fj%trj^#T(wbIU(@JYvX-zAw zX{9x-w5FBTw9=YZTGL8vT4_xyt!bq-t+b|<*0j=^R$9|aYg%bdtE_32HLbFyRo1l1 znpRoU3Ts+nO)IQvg*C0PrWMw-!kSiC(+X=^VNENnX@xbdu%;E(w8ENJSknq?T47Bq ztZ9Wct+1vQ*0jQ!R#?*tYg%DVE39dSHLb9w71p%EnpRlT3Ts+nO)IQvg*C0PrWMw- zx|&v3)9PwkT}`X2X>~QNtfrOKw6dC3R@2IAT3Jmit7&C5t*fS0)wHIXR#el9YFbfE zE2?QlHLa+o71gw&npRZPifUR>O)IKtMK!IcrWMt+qMBAz(~4?ZQB5nVX+<@ysHPRw zw4$0;RMU!TT2W0as%b?vt*E9I)$#;Bt>rcjsc081ry$^>U9_BrfXn{Yw3?b$Q`2f{ zT1`!>scAJet)`~c)U=wKR#VezYFbTAtEp)#Av8HLa_rb=9=4n$}g*x@uZiP3x*@T{W$%rghb{uA0_W)4FO} zS551xXLHLa?q zHPy7Hn$}d)nrd28O>3%YO*O5lrZv^Hrkd7N)0%2pQ%!5CX+1Tqr>6DPw4R#QQ`35C zT1`!>scAJet)`~c)U=wKR#VezYFbTAtEp)xEB@;@x)hli{J^&JH%QYac&?vK|zE1eI>+>*+VYTOG!4 z9Qs*HECoufWk+gvK2mGhk%nVCFKov<+#PMb(y!PA{FsV0M7IOCwjXKenMY&8cyKk; zGcY8L@@daJ5Q{QB^HeNC6dsL5@HqQ?EXF#`L$V0-%#*ShF*JJm7g`z$&){Mh58@(t zoP8P>VU~j`0uCa?a1g=5L5x`rBFu6SAq|6_!+6zQLr7&O;qzF=iGR7OupsX3}C-k&-7lyaJc0PUnKL z=)w+Bw`a#%;9+Ivm#}B2lnDEmrI{8poJorrMoQMi3{o zY&+~u)0uPvWOQ&`lyAF?YnfhS#CD8zXw*izS9M;9U(Mi;2lSMC8$0_3N0wlHH{uk4 z6LtT{82ogm&-`Tp`4x=;?PTItl8wWo?cI3aAj*)@=FcHBSZPu*?R#bPKh!h4xUW3Y z&CG%~h@Z3%44J#{IkSzO=**1bC@CHmXpI$CMSm_q_PTga##W`t1u^rCE&6Ob4?*SgypDG zkcr93X$cjzsQ`{57UZ~;$;ojEY1UN0?`q=;vJrD~tBDGEQvpAvi7Utm=Hvi{3YG`F zn;BOi4|1=G3bGY*a-z!Q~eES)xa>_!vVZgWZ3?O5t3qZ~;4FlL229UMm zZ0bZg*u?l}axDL*vdJ(xepESYr`Obp0XteI$BsLIYz#z2encW2^I*Oc1%ZqZ8-;7jzTEU*VmSE zj^!YP(r`@g!rF39u^fa@uEz|GNO|uF-m>ZFthe!)j>VE1O_q~m5K_{XV-RpoupEPc zle!#(fQ!A$$uS5xsmn14xE{N(2L_^}ZQ#O&DVSUuxSX7SGC4W_Ak8&QNw;9Z!OJxD zhoEg$-{`PW#zya(p~IkBPXejkqe$)gL~7YaYS~3<6%$g+E>gQCky>_3H+Fte2MD@ob=^(0yyc58wH&7#f<_^`tl3_IO)qX0N|uA&j5gvzPL^J5i{wF z+XS5S#ccvk`rl^`Uv4vilfK+$04IIf!oW#iZZm+BzHDOP zq%XG_z)4>=F>unC+l*|J+-4x^k7qy+ler<<`IK7+ z;OtLs9e|Thxpe?eKIPT{IQf)Y2mE}U<0Q8Zz&TEG>j0ee<@NzM>C5c{aMG9C2jHYH zw-3NcUv3|OlfK+O;AiusFSifCNndUsfRn!5LI5XyxrG2u`f>{aob=@u0yyc*Ed+4V zms<$@T%Yvi76Lfw%bh-O(wAEa;G{3N62M7cZY6+|zTD{pCw;k{08aXHJApqFK>Bh! z0i5*Zb^M}`s$7bX`j>_M=161ZNo5+Jpj9@lI_AxyVMzZi;Y zh@XiII6Edt?Vdzx#{{X}lStX4xS7E1m>{(_4Qc!ymNha6$Pg@$WIQW8rEoKlK~1kQfN zG6JV`Vi|$Qzqk_T7dezoY$I^WCbm)I2Ftv3+|%DPV&Bd-FBuy|-q1F$I}ddZcAM9o z@y7x1I<|c=ydQtr%)lmlq?(NrrOh@SMsV7^NRC(4EDmYkeh|!C=l<~40;1&NkyRQl^ zse(tQl$&3p5Ty6utg!taX{ zKn%ADm^Re=s@Vi>_7cf9t8nqTL-xlBCQX~y@fUXkG9_Is>54Minb96)EMmsU2mtfm zJZ6qr9UaWVoB=ZZnM(GD0Z1(+TiKMDx~3$qip?Rvm@rjMjX`D%MxC`Wqs?R((iX6F z`;cSWEMb-5h^OIUmYCGgGpTK2YM50fqg{&_I%Xgj;|Co8O}~q=W9>jeT$b4Zkg}1e zteiG%Er!@_Q7u4mX4I`26+0)U=1@5!z4Vh3{&b4%q?f&7QJLws++YL5oJIA9ZT+ru zyO?&^c3nnMn7Rf46Q<9ub9EgA(y< zQaidMoMhY-Iv|+}1EACp@8H5f?WoLdIx}o04^1sWGcuM? zw35(+8g$VA|;oAMD#Wee480~sVn*bV=kTafe+IwQmN60-b~FtHZ`f8oW64hEhk7p z#_(hy=?;(#ivh?)Uo_bUK%SXwg6vPms4rS>eQ{^K5k--`&#GI)R4){Zq_QG=&7hb$ zFaYWHMvK7!$dmdq+Q_ofA{A+SA(RmxsR&?zxRFdrFZqHs5QSg>Q3!x!_yEX;10Z4n zkgI2(TSRs_K<;utgLW7UfL3O53@Dy~g^+9H z1T4zO0(Kq@d$xh)&J3pTX*UHxN@|#tY5;^48FMqQMg&k`e{n@B2b7{vdbZUpC((j~ z6vA4x(2OubDV8N(XjUlcDV`@IlJv;UHXMySQY!@}NA{(eC9@vPBuOfBWai4&92%kl zTujx~h17HoDD5Lwk^7+I|{vBPKD7=k9X z8G~$tJ!}iGnWPhJCfgP%XV>J|sgW$(yqC?e9;MlElwm5%AV|liLfY78@%3eNSINWUCk1U1GY zgTbS&sn{MNrU>e-rf5-wn4(2-k?mtiZ5fmxo0-uxhq@YaP>eN&=6F|A2xUpkFdcVe zNK|_{Y!Ea5yet4}52qc- zFb2mI7^Bx~D_YatjVuccvyMqg2ag0$+13!*3OPIK)^9R>NA>a%&|-F>?4U?|JmIuV z3aeTB@oTqI!MCnmz@FnIHPQsW-kikOn_Yavc@dz+_)4^kuR{MGC9fy)j*IW$p6Mjb zmu&&h!B=gQ_@?bSfafPZ2KeKNa{-^17y>+;{F&q8+n!H5i6p-7=(u&y);;Sa>UK3= z;<)$<)lw(XxD4N+a`7Fi<$y15yd3Zqjb8wKWom}w;;T1vodmvggZlU?&82`ZOQAl# zOoKZ3D$SLE@nssoSEa53e0A!d0k2H01pL+1?SOxf`XOL^T?X(SsXG8~N!<_lfz$(l zx2GNi{7~v4zz?S$1^ig*F~EaUPyHV7>k1>PT!qh8>M+2Q)Fi;~RYw8-fcgO752_CXZcg#}4sc!*Zqt*bvQQZi5ty&BCCbb^$chq+P->fjF_+rct z0OPAMfH$knfPbVgzxaL(<`>_Pxf}33YAfI$t8IYqSN8*cKs^9>yV?$Thk6F^v+5Us zpHpuG-ir<3;`=YnP6A(MIT`RN-YI}T;++Z@Ut$3~)x&t=dn{)Fp6*QtJj0s-c&2wY z_DG%GJ!V^({giS(<4$wxOM2QI=kW4SU%zu~8$Pw+v~=U+D9*gz@<>0vt8Usj*n=*< zHD86t#;`(30=uxmeuv(Rzd&EDtKzyzbX5ns6U*h^5vRMnxVzuMs|Xh?bnwc+1;fro z?d^So&S&v9V7s%_+>bh6K-}+q$=s|vD|&mn%FgvRzQM+82k{4xotp=TM;1D_As%)% z4GnfLbnZOAJTi)}!5cY^q=rKi8~QQ|Mj8V**m#VM8*Mz+#wi9q~9DkGJuD zHlASPiO&AcQO@yBvvZ20o$2`A{m1d;`F?yu{xkNg_SfR8^{ernddtV(wJ<%xj){ve z*_&~4-3KN0E+$9az_Ih{Op5Zv97!kcFYTQbQtr-|6tdd-&KIR#1KTC{2tHv@Qa6;e z4(ktmAL1N=|7KK9v9ctbY0ep>0Ih(!7Kc9lsFT2##HS%1fZjCv*z$!`bh8BKv^x~MelRr${GR5_NX@qCwA3urn2|aY@wC)T#HZPI#ugY!>Wp@F?Apgy$qbYL z6%W0bRlv zy24T5^(d#wIl(DmMa*;BoL;Pl&)VA1N^EyU41GR^U^7_y#TbI^kUX>7Sqgnd=u0v5 zzIFTnzHA!uNhULQmM8bi>@Ed6>6t%{*9P7K`~L;n^-x5UtQV`xJR-5Nu;#nAU+ zXrqOoaWS_KL7o1h?w8Kt=8MzLk(h_0u`4|1K<98fC*O}-!6uQj&4iNpO7_+|SRH7m zdS?RG>rCjatL*+5-*~B$Y&_FB$fVel2Vp0sL|-&sVN2d)Qslg+@iNv#YHOPPH6DJG zv`6}m_Mc~aq_+KXiysW{%HRi+{ufvasi_4n>z#K&u6Ki%L$H45VuzjLOmSv83&8at zbjPL8eJh+*&N_Ur^bTj6^AqRi&P&c~&RcGSJHb7~o$Ma(wzwfIp4qdo#VL3LfVRZY zoiTJ*3_(w_-aRpNZwzgXp&!T4eK7>P&bG#z6KdOIXh#g~j3MLcNxg?-=#d!uNen$2 zLyyPM6EXB;4E;2QjISs?KOIBxFPQhNg%Zvftf^bEroLDAeWwoJO#cybwqT7Yd>j2C z=Rkbbe2;SozEil{%t-x8%)|-MAnToHonJviOowK;0vcg6oXGv*cR!Y#kUSzeC;9Q@ z6Zppbr|KTV*TgTXztn1Tv!*6Flhp^Y8n+uSvTmDtKjM4TWW;x>BM@(K#zNN~g0*}U z^mE2(!HkzuzeUM|seebjJ@pFWZK+=)zQ=mE_Z#20ZcFMp$g?%|3&5LFyAa=E)?~G> z)Emg#mihzYds43>-fsNiy1P@qLwr~2HN+e2+V~@&dtk-LEp}HE?EFc_O9sENkaf4F zDyDX7FXHc};HBXUk8dH~o%$2vAEn+z{w;R*Ou*_rRE-6GkMXQi_o#Zn8&w_R?_rfU z8V=PsU_VeE;@edUG3Eg~Pufnjxf4vT+7IzYH6C%b?E%2HslP+tTh;!6H^sfa+m;-N zc(ZyJ;thX6ruW%8haOE*q4xl;(@jdEP^tIERE6{Jr_8S7eO9hDUG-r?0{pw`EThvL2f22-C z&VA|xz?)S8@efrCa<-~G;7#yh?N~>As9DH;P@Rc*yPApk9yJ5;HZ>jbz3L3aThuhf z(Rj=Oyj{(XWwrivVv` zOAz0yE<}8{x&ZNA>U_jo)F|rQr$zv8R>Od|sv*Fe)F%*cP=koyu{YHv$lIxJ2fkf>8}T-^4)HzeCd7BxRdO?+ z4Pq-(uUbEPTk1D9HRXrYJ&P~kM&G}^&VKp!>-g&JJHL5b`@P%U@Nsuw-x*8#=f2<@ zZURTo1Kq>jqg~(C?o7AM?S&7x!d>fba(B4Ty00bb5(g$em^dLZ4L2-bdRfBY>wFz z-;E*2j9Mn|))=}ihQ1d=8)NAEF?73ytpBf`vJ|ju+1n3e=tnWMC5G;dp}S(}ZVO3o zzejIRs6D1n%Nx9bSe8FnO83R>;1!^Z`rcD0ebO_U{&92mk6L--kn@D+nS2=&&uIK7 zyz$i>wbSIN9W^-*+nk4LavrlekJjWoVRO)H)U*1j&3UpW=V_bsvznY|Y|hVXa$dAK zFVy7x+UESKCg(RcXLn7`Z*9)2H93E@Id4>RyrjwT5;Zv!ZO#OnV|+GHjYdlS#FVK= zqB7MGk2J;>wai*MGKP+gp$}U~=I=SvT4D9EezB(PdE5UlZU0jHWt;O-P0qjDoL6dc zerI!DvpI%?*K5lDWXs;H$$8u6yj9KdTm!4?s(g6$rjA!vQ)i6LfxOX7dSh))qs?)h z6X4BlsJj*3+{U`^!<*Y|&w_W=nX}-%_B6Q7o(Ff-Jy`dUIYHQOTi=VXS_Rphs4l(V(8EqIxL1J#n5|W$oOKy%ll&Jh#10}X6fV@ zdVdTZ6+<70p%2E;hhpgH7&<0~ut(VQaWQm!3}N4}v?+$tF_ej+Yz+A^l#8Ke3;nfI zZ0${wxp7H2ze}0h9CKSE_fskOgxCIsyUS*Kcll9!cR3ZlGyd7TORPJ><_1!7{}N>G z!pv=E-Qv!ne&-MyA8!6Cv2#@4;=aW=hdAKF{CnrkVst{rY?#u-YDsZUHS`kier)0| zu`d8k`plvID4%Qd4{$Q@baDUbtaLWvzU6S(*7IOjuW?toH@lnN2i%_~8evx_V-K`c zRyLkgS(lno$>L-*x$>HmuPks1l~v9Ol_#B(DkbL<QN+-W} zFWQ7?v&1=R@2hCD5^YwZ%}TVn8YhFvl?%}G>+l5*Mz6b3;{w#U05vW^jgP}O-LG<) zGZA5OrO!FF^0+gra=&v{-cx_o4MH%f+=$9vY(H zycaa`&PkZzODfl**0rd0Eoyz**7`Ea-pyJ$)EbXkUq-Djqt=&EYYE14vU4(cT?mPW zA<^yNS3Ap*XU1nWN*Ylz4JDUB);H09HQKL6`_*WFy^~CxT6rrqwQ@7Ybd|+60NW1i zwzzB)u)uJ{8-34q7eAE5#nf8eYhgI5At(C1QU3my}!qk+? zU8&P5>(TCw)al5d0V$%M3>D$$O_Xo2J$dM<8$ETWN|n!{r(yJT0eYH`o-RO7pF__jk-F5CS67v#QB(6*BPIe@3OFmoIT(=Hq_G{|5HFPw*KIZT-w~W~| z=HW5Vj`>w%UE|coj>i7R3mb21+|;wT-)Q+|qH^j9Wi$!?@eWJu&X*y1U3zwts z73lY?=2i&LbG7O&FV<&NR?H1N3Ks%d;@H^N@cr>Rp0xDZ*t4OA(eKtU$YOpnNsLw-DBV zgPYLDt!TRu;r7a-?z<`v;r4Mdc6}b>QGiA{p|TY`eh$4FO|S>O?m@46(CZ%bx(8Ce ziB)LEp(^(ltl|lkuVKblV8#u{*E`LXTc8a-1kPUu=bORtJ>d39%;4uRO4oxsJY_-I zw-DA~jBiExMug3kCo$GHU@Rt79B7Lp(d#<&x)!~zL$6PQ(+^=CZ3MR;0=J{!wh=wQ zhMuo-U`|~F}qtZj`v|4@5AighuPg~$MG#l@(d)|g)w{{ zygY>Qdm8-Q41VqdFKa+$7pRzZwHsr(8)Nw@qptjrDgs{1?8#DJZsJ?8aegu@Zg34O1sq3+ujYfY2GCTqq9)S#xI3LD{XCZYP zMm_5s2KzQ??;DW*WbnKiJl}v($b#>+nERcuEeFE_y$`mR=+36lxxeR(`0`~cS7{Q6qCzGLdW>?kuF2An#Iy z%Mg|#EJH9e4Lu2dCt!z6M0yzP;v|I0l>x}ShHHI2r08hfM1s(|X8c^ua$urj^i@ zYar7`$g=?ZZ9nXy37GMTNDqT;p9GyX8MThWsyQ0-l>tRF6M5$Y;OrJ~wi%pl2WNZ1 z**)NFD>&N<&h~(_%fZ=7aJCYhtpsPap39Zs>~V1RI5>M8oIMWC9tUU7gR|Sg*>-Sr zHCECDXq2O%EizaOKGuY+)!RUA6DaKjrAyHA2FttA8gsz=Vc>l_c%KH|Pekj>!TaUt z;Y#qn4!nODy-Wk|Q^EUO_^1u&ZKg9D^yWc7pM!botJHz_6T$n5;Qd7Kej-NoM2zZ* z7}evz`<3ARO7MOqc)t=HTnXN<2Jdsg`#kXO!Jj+_JTAZ})njG80qjw%#OIN7KdjS3 z@LJEoUYU<|y8wGC;hc^z9bQudcFHR#+l8{-&fjB&EigI?zS>0ay&HV*!qd;=Fy?RB zerKSES!i*#lg2LHja|AMyL2~rc^$iS7k23@*rl(4uie<4FQR{=(W+W&FSJ$zv=&aL zp!5_d8E*2>f@h#-_$L^%5@<~U|5KqAW7Q0G>ZZ$#LP zHQ=I0^UQoUX7U`!J|De;2599mlJCd`<>zptn}=s0Bk&aJzrqoC1f{qaLCG8F0rwlY zZ=QhL=0@ijtlD94`8CY^FlPP{%)GIguR}lVfnTu~oWG0_%YpkhFxzI7J!b~eSty^4 zUgkiWx#;O^Xqb86_8jL#_`@G@{vLW}0Y>#>@P1dJKcnq$K&TzN8!^&r(f>^d>ku}e z&s!00L)eILJHpP&9*q2}jV+*QBzP0DJc5xu3_E2KLNoMj$@2M}bGn_)7eQ+;=wu6_7^GpL+I}z^z{^Ee}*&m9OQf&GqxKtJcWLrf(%bN z-$YoA@GXQj827apT{C;@5H_Ixtq2feFF6FgQWMN_9LkMBx>&j{imH7nBiGyF&BNWLfJPEZb0}Z!fJ$XA*_MKYf;~L zL+cPWp#H508xd|tUpNt9wf?O>?4kctUN7eRlh7;c{~!7LpO)rt@ZYT@k#7#K*J}Cy z_EW+Fl+Jo}Cok#}eMH}~YmFBpTJ^VhuusnfVm zomT9FcX|`XTEzCl+K#^+d&cTf<1Lt8#IC@m`~^%j?gH!*Q?rV>RwLK{CA|!N`Ob4u z)s~sEswZeXz^YYDz~7z>Kf-~QJsQ0n2fN$=ZEJ2za<~)l@HUQyUpxgm`ylA-1@LAs zhHrl?-Y11Mcdo(v16ka|T#qNstDJA3#cF8Ek2p8soq`Z=#@vSX8}a{CXNR-XnS!@s zj&P>BA8|hdOY7D>n|uNB>!9*U=M85s-keF{y@LbYLs9R8c=y0}Prw^A+MVXka_8YK znhtl7JLsN|H)$?*m%CrYjn7K=Ywl{C!*6jn;^yaWcbog5`RDuS#5t^ZcsB zn#B6VhQ#fOI}-QeY0SfkClWtT{1Q)OUQN87cq^GmHYUd>4@e%8JUn?+^4Mf1*^)dZ zSxQb#&P>itF2IwT?qoln)?AeQES}hWA^9adwYffd1D@R6jC+Dj$vcxjPVPwl1b15N z>rFgj;-qKcdu@D2HBJ^R{Gy57BTSrZtd_cy>ha!cazXur=;yZL-GL&-)Q5_^-rRoD=nO^!_8r0l8sNbv14LqSN-jXpR4~Z;%9CAU^OQ>$JQ*^ z__%k*?lP0({=w#d*v3!VoIhFE_FwnsQhdtRIl#nqR~z1v+id*@>wg0{VdBIC^>|Aw zv9A8th(!uJhRIp=Pos{ELoGit0>Z-qbvd*Wspp&iUE!y89Aq>kwtOc3JV)|tV_V0y zoXd#qGd?m(e?C^ie{DYpnsH7vSgD1sn}6;^>&WxFSm7Mu5+18jV!&G|#!Y~(y> z$2?bu_Xv|mSU7F+oo5Xv&L^Q^-j5U42k@SP(K5$CdmM*17CwwOL(3 zbVvbb%M)?7JlX1ti}Tg-LOaX;pk@x-+Q&s_VUHHO_0chr3tn&CP3m+lMh%kHn-U%Rikzj0r6 zf9t;CzUjX0?sY5B7j7bvI5_d1#G#495=SJC#Pit?L2n$BI5u%*;wOp6p$VQ$JOy3w zQsR}w?!=zN9};gQ{+Rd^G=c}6(3|W_ek%EC+`Z#Y+`a|sK--+*jJ0opz6(B_gS%@7 zZ#O#55Wki=O7C|ZQ|tVmMiJ!!?w@k zY#kd*EB7}hA66T2-EVCj;rvrJ=V}utx7qv$Eu66M-8Nol<2y`@5qK7{$XiQGB>gM0 zn9ht8Ray3tT}o~2Ja6kbHL>jzqk$eA%3GB#K}l`@!jHrvr_t!6oC=&ExZANuNJ zPR{5orx{wS!)bx$>U9dxSg$xIz?=E4(+WNHhI1Np)tm65-cG>BgQhyzITM=dFlUz0 zR?ckbt7D-d@g_PvoHOz8%>0_C&i;;PEWDk5+?X8-9{>AvkK?>$rV9bT4sh}Izk2{Q z(YMVg{ujhXNB&pz^H=zB-h?)F-4ry`A?CeBc(MDu&4qEh5c>L3=<2JWr>}#KUIYES z0lN7P=wSH*o{r|KGP&!_79|#?oiQGFwO5 z!t+7>KZ_d{+E(Ca@3RdJh`){4{5rw>{>!m;$+dPVW$hB4&Et0pEwED#@k21qMr#>O zbUuRF=@%m`NBAPbH3%yazJ{Z755k+6 zN;8}PDO>gS%KF3qvu*z`6vbkF+gL2Dgx!db!g@FqzR6#F!xH1N9Ak0~#^Y-ki**4zie)}q$(`Vy3 qe?yF4T!DwxCVm^g3. 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"); +}