From 06f9b35c6c2b45c8636f0a3809c1a3716e68c2d3 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Sun, 24 Apr 2022 15:27:09 +0200 Subject: [PATCH] Let's go! --- .gitignore | 3 + .gitlab-ci.yml | 12 + Makefile | 22 + README.md | 1 + doc/składnia.txt | 54 + lib/expected/README.md | 91 ++ lib/expected/tl/expected.hpp | 2326 +++++++++++++++++++++++++++++++++ lib/ut/LICENSE.md | 23 + lib/ut/README.md | 2027 ++++++++++++++++++++++++++++ lib/ut/boost/ut.hpp | 2391 ++++++++++++++++++++++++++++++++++ src/errors.cc | 6 + src/lexer.cc | 12 + src/main.cc | 26 + src/musique.hh | 77 ++ 14 files changed, 7071 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 Makefile create mode 100644 README.md create mode 100644 doc/składnia.txt create mode 100644 lib/expected/README.md create mode 100644 lib/expected/tl/expected.hpp create mode 100644 lib/ut/LICENSE.md create mode 100644 lib/ut/README.md create mode 100644 lib/ut/boost/ut.hpp create mode 100644 src/errors.cc create mode 100644 src/lexer.cc create mode 100644 src/main.cc create mode 100644 src/musique.hh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f873af --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.ccls-cache +bin/* +compile_commands.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..280cf20 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,12 @@ +image: gcc + +stages: + - build + +build: + stage: build + script: + - make + artifacts: + path: + - main diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..70e2adb --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)" +CXXFLAGS=-std=c++20 -Wall -Wextra -O2 -Werror=switch +CPPFLAGS=-Ilib/expected/ -Ilib/ut/ + +Obj=bin/lexer.o \ + bin/errors.o \ + bin/main.o + +all: bin/musique + +bin/%.o: src/%.cc src/*.hh + g++ $(CXXFLAGS) $(CPPFLAGS) -o $@ $< -c + +bin/musique: $(Obj) src/*.hh + g++ $(CXXFLAGS) $(CPPFLAGS) -o $@ $(Obj) + +clean: + rm -rf bin + +.PHONY: clean + +$(shell mkdir -p bin) diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b3fa87 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Musique interpreter diff --git a/doc/składnia.txt b/doc/składnia.txt new file mode 100644 index 0000000..b82c3c7 --- /dev/null +++ b/doc/składnia.txt @@ -0,0 +1,54 @@ +program = ws*, expression, ws*, { expression, ws* } +block = "[", program, "]" ; + +(* At interpreter runtime it is determined which symbols pass as operators, and which not *) +expression = value, ws*, operator, ws*, expression + | operator, ws*, value + | value ; + +value = note + | chord + | symbol + | block + | number + | ( "(", expression, ")" ) ; + +symbol = ( valid-symbol-start-characters, { valid-symbol-characters } ) - note - chord; +valid-symbol-start-characters = uniletter | "_" | "@" | "$" | "#" ; +valid-symbol-characters = valid-symbol-start-characters | unidigit | "-" ; + +operator = operator-symbols, { operator-symbols } ; +operator-symbols = "+" | "-" | "*" | "/" | "%" | "!" + | "<" | ">" | "v" | "^" | "=" | ":" ; + +(********************************* Literały liczbowe *********************************) +number = floating-point | fraction ; + +(* Dopuszcza następujące zapisy: 1/2, *) +fraction = digits+, ws*, "/", ws*, digits+ ; + +(* Dopuszcza następujące zapisy: -123.456, 123.456, .456; Notacja naukowa nie jest wspierana *) +floating-point = ( ["-"], digits+, [ ".", digits+ ] ) | ( ".", digits+ ); + + +(********************************* Literały muzyczne *********************************) +(* DSL do definiowania muzycznych wartości. Brakuje notacji dla akordów, przewrotów itd *) +note = note-letter, ["#"], [ws*, octave] ; +note-letter = "c" | "C" | "d" | "D" | "e" | "E" | "f" | "F" | "g" | "G" | "a" | "A" | "h" | "H" | "b" | "B" ; +octave = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" ; + +chord = note-letter, ["#"], [ ("1" | "2" | "5" | "7" ), [ "," | "'" ] ]; + +(********************************* Definicje pomocnicze *********************************) + +(* Unicode helpers, based on Go's compiler source code *) +uniletter = ? all characters that are considered letters in unicode ?; +unidigit = ? all characters that are considered digits in unicode ?; + +digits+ = digit, { digits } ; +digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; + +(* Whitespace helpers *) +ws* = { ws } ; +ws+ = ws, { ws } ; +ws = ? all characters that are considered ascii whitespace ? ; diff --git a/lib/expected/README.md b/lib/expected/README.md new file mode 100644 index 0000000..d16836e --- /dev/null +++ b/lib/expected/README.md @@ -0,0 +1,91 @@ +# expected +Single header implementation of `std::expected` with functional-style extensions. + +[![Documentation Status](https://readthedocs.org/projects/tl-docs/badge/?version=latest)](https://tl.tartanllama.xyz/en/latest/?badge=latest) +Clang + GCC: [![Linux Build Status](https://travis-ci.org/TartanLlama/expected.png?branch=master)](https://travis-ci.org/TartanLlama/expected) +MSVC: [![Windows Build Status](https://ci.appveyor.com/api/projects/status/k5x00xa11y3s5wsg?svg=true)](https://ci.appveyor.com/project/TartanLlama/expected) + +Available on [Vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/tl-expected) and [Conan](https://github.com/yipdw/conan-tl-expected). + +[`std::expected`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0323r3.pdf) is proposed as the preferred way to represent object which will either have an expected value, or an unexpected value giving information about why something failed. Unfortunately, chaining together many computations which may fail can be verbose, as error-checking code will be mixed in with the actual programming logic. This implementation provides a number of utilities to make coding with `expected` cleaner. + +For example, instead of writing this code: + +```cpp +std::expected get_cute_cat (const image& img) { + auto cropped = crop_to_cat(img); + if (!cropped) { + return cropped; + } + + auto with_tie = add_bow_tie(*cropped); + if (!with_tie) { + return with_tie; + } + + auto with_sparkles = make_eyes_sparkle(*with_tie); + if (!with_sparkles) { + return with_sparkles; + } + + return add_rainbow(make_smaller(*with_sparkles)); +} +``` + +You can do this: + +```cpp +tl::expected get_cute_cat (const image& img) { + return crop_to_cat(img) + .and_then(add_bow_tie) + .and_then(make_eyes_sparkle) + .map(make_smaller) + .map(add_rainbow); +} +``` + +The interface is the same as `std::expected` as proposed in [p0323r3](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0323r3.pdf), but the following member functions are also defined. Explicit types are for clarity. + +- `map`: carries out some operation on the stored object if there is one. + * `tl::expected s = exp_string.map(&std::string::size);` +- `map_error`: carries out some operation on the unexpected object if there is one. + * `my_error_code translate_error (std::error_code);` + * `tl::expected s = exp_int.map_error(translate_error);` +- `and_then`: like `map`, but for operations which return a `tl::expected`. + * `tl::expected parse (const std::string& s);` + * `tl::expected exp_ast = exp_string.and_then(parse);` +- `or_else`: calls some function if there is no value stored. + * `exp.or_else([] { throw std::runtime_error{"oh no"}; });` + +### Compiler support + +Tested on: + +- Linux + * clang 6.0.1 + * clang 5.0.2 + * clang 4.0.1 + * clang 3.9 + * clang 3.8 + * clang 3.7 + * clang 3.6 + * clang 3.5 + * g++ 8.0.1 + * g++ 7.3 + * g++ 6.4 + * g++ 5.5 + * g++ 4.9 + * g++ 4.8 +- Windows + * MSVC 2015 + * MSVC 2017 + +### Acknowledgements + +Thanks to [Kévin Alexandre Boissonneault](https://github.com/KABoissonneault) and [Björn Fahller](https://github.com/rollbear) for various bug fixes. + +---------- + +[![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png)]("http://creativecommons.org/publicdomain/zero/1.0/") + +To the extent possible under law, [Sy Brand](https://twitter.com/TartanLlama) has waived all copyright and related or neighboring rights to the `expected` library. This work is published from: United Kingdom. diff --git a/lib/expected/tl/expected.hpp b/lib/expected/tl/expected.hpp new file mode 100644 index 0000000..31b130a --- /dev/null +++ b/lib/expected/tl/expected.hpp @@ -0,0 +1,2326 @@ +/// +// expected - An implementation of std::expected with extensions +// Written in 2017 by Simon Brand (simonrbrand@gmail.com, @TartanLlama) +// +// Documentation available at http://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_EXPECTED_HPP +#define TL_EXPECTED_HPP + +#define TL_EXPECTED_VERSION_MAJOR 1 +#define TL_EXPECTED_VERSION_MINOR 0 +#define TL_EXPECTED_VERSION_PATCH 1 + +#include +#include +#include +#include + +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) +#define TL_EXPECTED_EXCEPTIONS_ENABLED +#endif + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_EXPECTED_MSVC2015 +#define TL_EXPECTED_MSVC2015_CONSTEXPR +#else +#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC55 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions + +#define TL_EXPECTED_NO_CONSTRR +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::has_trivial_copy_assign + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector +// for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && \ + !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { + namespace detail { + template + struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; +#ifdef _GLIBCXX_VECTOR + template + struct is_trivially_copy_constructible> + : std::false_type{}; +#endif + } +} +#endif + +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible +#else +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible +#endif + +#if __cplusplus > 201103L +#define TL_EXPECTED_CXX14 +#endif + +#ifdef TL_EXPECTED_GCC49 +#define TL_EXPECTED_GCC49_CONSTEXPR +#else +#define TL_EXPECTED_GCC49_CONSTEXPR constexpr +#endif + +#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ + defined(TL_EXPECTED_GCC49)) +#define TL_EXPECTED_11_CONSTEXPR +#else +#define TL_EXPECTED_11_CONSTEXPR constexpr +#endif + +namespace tl { +template class expected; + +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +class monostate {}; + +struct in_place_t { + explicit in_place_t() = default; +}; +static constexpr in_place_t in_place{}; +#endif + +template class unexpected { +public: + static_assert(!std::is_same::value, "E must not be void"); + + unexpected() = delete; + constexpr explicit unexpected(const E &e) : m_val(e) {} + + constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} + + constexpr const E &value() const & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); } + constexpr const E &&value() const && { return std::move(m_val); } + +private: + E m_val; +}; + +template +constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() == rhs.value(); +} +template +constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() != rhs.value(); +} +template +constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() < rhs.value(); +} +template +constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() <= rhs.value(); +} +template +constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() > rhs.value(); +} +template +constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() >= rhs.value(); +} + +template +unexpected::type> make_unexpected(E &&e) { + return unexpected::type>(std::forward(e)); +} + +struct unexpect_t { + unexpect_t() = default; +}; +static constexpr unexpect_t unexpect{}; + +namespace detail { +template +[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + throw std::forward(e); +#else + #ifdef _MSC_VER + __assume(0); + #else + __builtin_unreachable(); + #endif +#endif +} + +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + +// In C++11 mode, there's an issue in libc++'s std::mem_fn +// which results in a hard-error when using it in a noexcept expression +// in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +template struct is_pointer_to_non_const_member_func : std::false_type {}; +template +struct is_pointer_to_non_const_member_func : std::true_type {}; +template +struct is_pointer_to_non_const_member_func : std::true_type {}; +template +struct is_pointer_to_non_const_member_func : std::true_type {}; +template +struct is_pointer_to_non_const_member_func : std::true_type {}; +template +struct is_pointer_to_non_const_member_func : std::true_type {}; +template +struct is_pointer_to_non_const_member_func : std::true_type {}; + +template struct is_const_or_const_ref : std::false_type {}; +template struct is_const_or_const_ref : std::true_type {}; +template struct is_const_or_const_ref : std::true_type {}; +#endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template ::value + && is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, + int = 0> + constexpr auto invoke(Fn && f, Args && ... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >::value>> + constexpr auto invoke(Fn && f, Args && ... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +// TODO make a version which works with MSVC 2015 +template struct is_swappable : std::true_type {}; + +template struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests { + // if swap ADL finds this then it would call std::swap otherwise (same + // signature) + struct tag {}; + + template tag swap(T&, T&); + template tag swap(T(&a)[N], T(&b)[N]); + + // helper functions to test if an unqualified swap is possible, and if it + // becomes std::swap + template std::false_type can_swap(...) noexcept(false); + template (), std::declval()))> + std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + + template std::false_type uses_std(...); + template + std::is_same(), std::declval())), tag> + uses_std(int); + + template + struct is_std_swap_noexcept + : std::integral_constant::value&& + std::is_nothrow_move_assignable::value> {}; + + template + struct is_std_swap_noexcept : is_std_swap_noexcept {}; + + template + struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; +} // namespace swap_adl_tests + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype( + detail::swap_adl_tests::uses_std(0))::value || + is_swappable::value)> {}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value + && detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> { +}; +#endif +#endif + +// Trait for checking if a type is a tl::expected +template struct is_expected_impl : std::false_type {}; +template +struct is_expected_impl> : std::true_type {}; +template using is_expected = is_expected_impl>; + +template +using expected_enable_forward_value = detail::enable_if_t< + std::is_constructible::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using expected_enable_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + +template +using is_void_or = conditional_t::value, std::true_type, U>; + +template +using is_copy_constructible_or_void = + is_void_or>; + +template +using is_move_constructible_or_void = + is_void_or>; + +template +using is_copy_assignable_or_void = + is_void_or>; + + +template +using is_move_assignable_or_void = + is_void_or>; + + +} // namespace detail + +namespace detail { +struct no_init_t {}; +static constexpr no_init_t no_init{}; + +// Implements the storage of the values, and ensures that the destructor is +// trivial if it can be. +// +// This specialization is for where neither `T` or `E` is trivially +// destructible, so the destructors must be called on destruction of the +// `expected` +template ::value, + bool = std::is_trivially_destructible::value> +struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&... args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&... args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } else { + m_unexpect.~unexpected(); + } + } + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// This specialization is for when both `T` and `E` are trivially-destructible, +// so the destructor of the `expected` can be trivial. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&... args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&... args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// T is trivial, E is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) + : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&... args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&... args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// E is trivial, T is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&... args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&... args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } + } + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is trivially-destructible +template struct expected_storage_base { + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base() : m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + struct dummy {}; + union { + unexpected m_unexpect; + dummy m_val; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is not trivially-destructible +template struct expected_storage_base { + constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + unexpected m_unexpect; + char m_dummy; + }; + bool m_has_val; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct(Args &&... args) noexcept { + new (std::addressof(this->m_val)) T(std::forward(args)...); + this->m_has_val = true; + } + + template void construct_with(Rhs &&rhs) noexcept { + new (std::addressof(this->m_val)) T(std::forward(rhs).get()); + this->m_has_val = true; + } + + template void construct_error(Args &&... args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + + // These assign overloads ensure that the most efficient assignment + // implementation is used while maintaining the strong exception guarantee. + // The problematic case is where rhs has a value, but *this does not. + // + // This overload handles the case where we can just copy-construct `T` + // directly into place without throwing. + template ::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + // This overload handles the case where we can attempt to create a copy of + // `T`, then no-throw move it into place if the copy was successful. + template ::value && + std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + T tmp = rhs.get(); + geterr().~unexpected(); + construct(std::move(tmp)); + } else { + assign_common(rhs); + } + } + + // This overload is the worst-case, where we have to move-construct the + // unexpected value into temporary storage, then try to copy the T into place. + // If the construction succeeds, then everything is fine, but if it throws, + // then we move the old unexpected value back into place before rethrowing the + // exception. + template ::value && + !std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(rhs.get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } +#else + construct(rhs.get()); +#endif + } else { + assign_common(rhs); + } + } + + // These overloads do the same as above, but for rvalues + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(std::move(rhs)); + } + } + + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(std::move(rhs).get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } +#else + construct(std::move(rhs).get()); +#endif + } else { + assign_common(std::move(rhs)); + } + } + + #else + + // If exceptions are disabled then we can just copy-construct + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(rhs); + } + } + + #endif + + // The common part of move/copy assigning + template void assign_common(Rhs &&rhs) { + if (this->m_has_val) { + if (rhs.m_has_val) { + get() = std::forward(rhs).get(); + } else { + destroy_val(); + construct_error(std::forward(rhs).geterr()); + } + } else { + if (!rhs.m_has_val) { + geterr() = std::forward(rhs).geterr(); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + constexpr const T &get() const & { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_val); } +#endif + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { + get().~T(); + } +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct() noexcept { this->m_has_val = true; } + + // This function doesn't use its argument, but needs it so that code in + // levels above this can work independently of whether T is void + template void construct_with(Rhs &&) noexcept { + this->m_has_val = true; + } + + template void construct_error(Args &&... args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + + template void assign(Rhs &&rhs) noexcept { + if (!this->m_has_val) { + if (rhs.m_has_val) { + geterr().~unexpected(); + construct(); + } else { + geterr() = std::forward(rhs).geterr(); + } + } else { + if (!rhs.m_has_val) { + construct_error(std::forward(rhs).geterr()); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { + //no-op + } +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T and E are trivially copy constructible +template :: + value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; +}; + +// This specialization is for when T or E are not trivially copy constructible +template +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; + + expected_copy_base() = default; + expected_copy_base(const expected_copy_base &rhs) + : expected_operations_base(no_init) { + if (rhs.has_value()) { + this->construct_with(rhs); + } else { + this->construct_error(rhs.geterr()); + } + } + + expected_copy_base(expected_copy_base &&rhs) = default; + expected_copy_base &operator=(const expected_copy_base &rhs) = default; + expected_copy_base &operator=(expected_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_EXPECTED_GCC49 +template >::value + &&std::is_trivially_move_constructible::value> +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; +}; +#else +template struct expected_move_base; +#endif +template +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; + + expected_move_base() = default; + expected_move_base(const expected_move_base &rhs) = default; + + expected_move_base(expected_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) + : expected_copy_base(no_init) { + if (rhs.has_value()) { + this->construct_with(std::move(rhs)); + } else { + this->construct_error(std::move(rhs.geterr())); + } + } + expected_move_base &operator=(const expected_move_base &rhs) = default; + expected_move_base &operator=(expected_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template >::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; +}; + +template +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; + + expected_copy_assign_base() = default; + expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; + + expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; + expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + expected_copy_assign_base & + operator=(expected_copy_assign_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_EXPECTED_GCC49 +template , + std::is_trivially_move_constructible, + std::is_trivially_move_assignable>>:: + value &&std::is_trivially_destructible::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> +struct expected_move_assign_base : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; +}; +#else +template struct expected_move_assign_base; +#endif + +template +struct expected_move_assign_base + : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; + + expected_move_assign_base() = default; + expected_move_assign_base(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base(expected_move_assign_base &&rhs) = default; + + expected_move_assign_base & + operator=(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base & + operator=(expected_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } +}; + +// expected_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template ::value && + std::is_copy_constructible::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value)> +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +// expected_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T and E are copy/move constructible + +// assignable +template ::value && + std::is_copy_constructible::value && + is_copy_assignable_or_void::value && + std::is_copy_assignable::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value && + is_move_assignable_or_void::value && + std::is_move_assignable::value)> +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +// This is needed to be able to construct the expected_default_ctor_base which +// follows, while still conditionally deleting the default constructor. +struct default_constructor_tag { + explicit constexpr default_constructor_tag() = default; +}; + +// expected_default_ctor_base will ensure that expected has a deleted default +// consturctor if T is not default constructible. +// This specialization is for when T is default constructible +template ::value || std::is_void::value> +struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = default; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; + +// This specialization is for when T is not default constructible +template struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = delete; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; +} // namespace detail + +template class bad_expected_access : public std::exception { +public: + explicit bad_expected_access(E e) : m_val(std::move(e)) {} + + virtual const char *what() const noexcept override { + return "Bad expected access"; + } + + const E &error() const & { return m_val; } + E &error() & { return m_val; } + const E &&error() const && { return std::move(m_val); } + E &&error() && { return std::move(m_val); } + +private: + E m_val; +}; + +/// An `expected` object is an object that contains the storage for +/// another object and manages the lifetime of this contained object `T`. +/// Alternatively it could contain the storage for another unexpected object +/// `E`. The contained object may not be initialized after the expected object +/// has been initialized, and may not be destroyed before the expected object +/// has been destroyed. The initialization state of the contained object is +/// tracked by the expected object. +template +class expected : private detail::expected_move_assign_base, + private detail::expected_delete_ctor_base, + private detail::expected_delete_assign_base, + private detail::expected_default_ctor_base { + static_assert(!std::is_reference::value, "T must not be a reference"); + static_assert(!std::is_same::type>::value, + "T must not be in_place_t"); + static_assert(!std::is_same::type>::value, + "T must not be unexpect_t"); + static_assert(!std::is_same>::type>::value, + "T must not be unexpected"); + static_assert(!std::is_reference::value, "E must not be a reference"); + + T *valptr() { return std::addressof(this->m_val); } + const T *valptr() const { return std::addressof(this->m_val); } + unexpected *errptr() { return std::addressof(this->m_unexpect); } + const unexpected *errptr() const { return std::addressof(this->m_unexpect); } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &val() { + return this->m_val; + } + TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } + + template ::value> * = nullptr> + constexpr const U &val() const { + return this->m_val; + } + constexpr const unexpected &err() const { return this->m_unexpect; } + + using impl_base = detail::expected_move_assign_base; + using ctor_base = detail::expected_default_ctor_base; + +public: + typedef T value_type; + typedef E error_type; + typedef unexpected unexpected_type; + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { + return and_then_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { + return and_then_impl(std::move(*this), std::forward(f)); + } + template constexpr auto and_then(F &&f) const & { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif + +#else + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) & -> decltype(and_then_impl(std::declval(), std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && -> decltype( + and_then_impl(std::declval(), std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto and_then(F &&f) const & -> decltype( + and_then_impl(std::declval(), std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto and_then(F &&f) const && -> decltype( + and_then_impl(std::declval(), std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template constexpr auto map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + template constexpr auto map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype( + expected_map_impl(std::declval(), std::declval())) + map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype( + expected_map_impl(std::declval(), std::declval())) + map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template constexpr auto transform(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + template constexpr auto transform(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype( + expected_map_impl(std::declval(), std::declval())) + transform(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype( + expected_map_impl(std::declval(), std::declval())) + transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template constexpr auto map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + template constexpr auto map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { + return or_else_impl(*this, std::forward(f)); + } + + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { + return or_else_impl(std::move(*this), std::forward(f)); + } + + template expected constexpr or_else(F &&f) const & { + return or_else_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template expected constexpr or_else(F &&f) const && { + return or_else_impl(std::move(*this), std::forward(f)); + } +#endif + constexpr expected() = default; + constexpr expected(const expected &rhs) = default; + constexpr expected(expected &&rhs) = default; + expected &operator=(const expected &rhs) = default; + expected &operator=(expected &&rhs) = default; + + template ::value> * = + nullptr> + constexpr expected(in_place_t, Args &&... args) + : impl_base(in_place, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected(in_place_t, std::initializer_list il, Args &&... args) + : impl_base(in_place, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit constexpr expected(const unexpected &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected const &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr> + constexpr explicit expected(unexpect_t, Args &&... args) + : impl_base(unexpect, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected(unexpect_t, std::initializer_list il, + Args &&... args) + : impl_base(unexpect, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template < + class U, class G, + detail::enable_if_t::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U, class G, + detail::enable_if_t<(std::is_convertible::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } + + return *this; + } + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + #endif + } + + return *this; + } + + template ::value && + std::is_assignable::value> * = nullptr> + expected &operator=(const unexpected &rhs) { + if (!has_value()) { + err() = rhs; + } else { + this->destroy_val(); + ::new (errptr()) unexpected(rhs); + this->m_has_val = false; + } + + return *this; + } + + template ::value && + std::is_move_assignable::value> * = nullptr> + expected &operator=(unexpected &&rhs) noexcept { + if (!has_value()) { + err() = std::move(rhs); + } else { + this->destroy_val(); + ::new (errptr()) unexpected(std::move(rhs)); + this->m_has_val = false; + } + + return *this; + } + + template ::value> * = nullptr> + void emplace(Args &&... args) { + if (has_value()) { + val() = T(std::forward(args)...); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } + } + + template ::value> * = nullptr> + void emplace(Args &&... args) { + if (has_value()) { + val() = T(std::forward(args)...); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + #endif + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&... args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + err().~unexpected(); + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&... args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + #endif + } + } + +private: + using t_is_void = std::true_type; + using t_is_not_void = std::false_type; + using t_is_nothrow_move_constructible = std::true_type; + using move_constructing_t_can_throw = std::false_type; + using e_is_nothrow_move_constructible = std::true_type; + using move_constructing_e_can_throw = std::false_type; + + void swap_where_both_have_value(expected &/*rhs*/ , t_is_void) noexcept { + // swapping void is a no-op + } + + void swap_where_both_have_value(expected &rhs, t_is_not_void) { + using std::swap; + swap(val(), rhs.val()); + } + + void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( + std::is_nothrow_move_constructible::value) { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { + swap_where_only_one_has_value_and_t_is_not_void( + rhs, typename std::is_nothrow_move_constructible::type{}, + typename std::is_nothrow_move_constructible::type{}); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + e_is_nothrow_move_constructible) noexcept { + auto temp = std::move(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + move_constructing_e_can_throw) { + auto temp = std::move(val()); + val().~T(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + val() = std::move(temp); + throw; + } +#else + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, move_constructing_t_can_throw, + t_is_nothrow_move_constructible) { + auto temp = std::move(rhs.err()); + rhs.err().~unexpected_type(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (rhs.valptr()) T(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + rhs.err() = std::move(temp); + throw; + } +#else + ::new (rhs.valptr()) T(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + +public: + template + detail::enable_if_t::value && + detail::is_swappable::value && + (std::is_nothrow_move_constructible::value || + std::is_nothrow_move_constructible::value)> + swap(expected &rhs) noexcept( + std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value + &&std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + if (has_value() && rhs.has_value()) { + swap_where_both_have_value(rhs, typename std::is_void::type{}); + } else if (!has_value() && rhs.has_value()) { + rhs.swap(*this); + } else if (has_value()) { + swap_where_only_one_has_value(rhs, typename std::is_void::type{}); + } else { + using std::swap; + swap(err(), rhs.err()); + } + } + + constexpr const T *operator->() const { return valptr(); } + TL_EXPECTED_11_CONSTEXPR T *operator->() { return valptr(); } + + template ::value> * = nullptr> + constexpr const U &operator*() const & { + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { + return val(); + } + template ::value> * = nullptr> + constexpr const U &&operator*() const && { + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + return std::move(val()); + } + + constexpr bool has_value() const noexcept { return this->m_has_val; } + constexpr explicit operator bool() const noexcept { return this->m_has_val; } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &value() const & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + + constexpr const E &error() const & { return err().value(); } + TL_EXPECTED_11_CONSTEXPR E &error() & { return err().value(); } + constexpr const E &&error() const && { return std::move(err().value()); } + TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(err().value()); } + + template constexpr T value_or(U &&v) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy-constructible and convertible to from U&&"); + return bool(*this) ? **this : static_cast(std::forward(v)); + } + template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move-constructible and convertible to from U&&"); + return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); + } +}; + +namespace detail { +template using exp_t = typename detail::decay_t::value_type; +template using err_t = typename detail::decay_t::error_type; +template using ret_t = expected>; + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#else +template struct TC; +template (), + *std::declval())), + detail::enable_if_t>::value> * = nullptr> +auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template ())), + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto or_else_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() + ? std::forward(exp) + : detail::invoke(std::forward(f), std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() + ? std::forward(exp) + : (detail::invoke(std::forward(f), std::forward(exp).error()), + std::forward(exp)); +} +#else +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto or_else_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() + ? std::forward(exp) + : detail::invoke(std::forward(f), std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() + ? std::forward(exp) + : (detail::invoke(std::forward(f), std::forward(exp).error()), + std::forward(exp)); +} +#endif +} // namespace detail + +template +constexpr bool operator==(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); +} +template +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? true + : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); +} + +template +constexpr bool operator==(const expected &x, const U &v) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator==(const U &v, const expected &x) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator!=(const expected &x, const U &v) { + return x.has_value() ? *x != v : true; +} +template +constexpr bool operator!=(const U &v, const expected &x) { + return x.has_value() ? *x != v : true; +} + +template +constexpr bool operator==(const expected &x, const unexpected &e) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator==(const unexpected &e, const expected &x) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator!=(const expected &x, const unexpected &e) { + return x.has_value() ? true : x.error() != e.value(); +} +template +constexpr bool operator!=(const unexpected &e, const expected &x) { + return x.has_value() ? true : x.error() != e.value(); +} + +template ::value || + std::is_move_constructible::value) && + detail::is_swappable::value && + std::is_move_constructible::value && + detail::is_swappable::value> * = nullptr> +void swap(expected &lhs, + expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { + lhs.swap(rhs); +} +} // namespace tl + +#endif diff --git a/lib/ut/LICENSE.md b/lib/ut/LICENSE.md new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/lib/ut/LICENSE.md @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +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. diff --git a/lib/ut/README.md b/lib/ut/README.md new file mode 100644 index 0000000..aab2fac --- /dev/null +++ b/lib/ut/README.md @@ -0,0 +1,2027 @@ +![Version](https://badge.fury.io/gh/boost-ext%2Fut.svg) +![Linux](https://github.com/boost-ext/ut/actions/workflows/linux.yml/badge.svg) +![MacOs](https://github.com/boost-ext/ut/actions/workflows/macos.yml/badge.svg) +![Windows](https://github.com/boost-ext/ut/actions/workflows/windows.yml/badge.svg) +![Coveralls](https://codecov.io/gh/boost-ext/ut/branch/master/graph/badge.svg) +![Try it online](https://img.shields.io/badge/try%20it-online-blue.svg) +![AUR Badge](https://img.shields.io/aur/version/ut) + +> "If you liked it then you `"should have put a"_test` on it", Beyonce rule + +# UT / μt + +| [Motivation](#motivation) | [Quick Start](#quick-start) | [Overview](#overview) | [Tutorial](#tutorial) | [Examples](#examples) | [User Guide](#user-guide) | [FAQ](#faq) | [Benchmarks](#benchmarks) | + +
C++ single header/single module, macro-free μ(micro)/Unit Testing Framework +

+ +

+ +

+ + +
Motivation +

+ +Testing is a very important part of the Software Development, however, C++ doesn't provide any good testing facilities out of the box, +which often leads into a poor testing experience for develops and/or lack of tests/coverage in general. + +> One should treat testing code as production code! + +Additionally, well established testing practises such as [Test Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development)/[Behaviour Driven Development (BDD)](https://en.wikipedia.org/wiki/Behavior-driven_development) are often not followed due to the same reasons. + +The following snippet is a common example of testing with projects in C++. + +```cpp +int main() { + // should sum numbers + { + assert(3 == sum(1, 2)); + } +} +``` + +There are quite a few problems with the approach above + +* No names for tests (Hard to follow intentions by further readers) +* No automatic registration of tests (No way to run specific tests) +* Hard to debug (Assertions don't provide any information why it failed) +* Hard to scale (No easy path forward for parameterized tests, multiple suites, parallel execution, etc...) +* Hard to integrate (No easy way to have a custom output such as XML for CI integration) +* Easy to make mistakes (With implicit casting, floating point comparison, pointer comparison for strings, etc...) +* Hard to follow good practises such as `TDD/BDD` (Lack of support for sections and declarative expressions) +* ... + +`UT` is trying to address these issues by simplifying testing experience with a few simple steps: + +* Just get a single [header/module](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) +* Integrate it into your project +* Learn a few simple concepts ([expect, test, suite](#api)) + +And you good to go! + +Okay, great, but why I would use `UT` over other/similar testing frameworks already available in C++? + +* [Boost.Test](https://github.com/boostorg/test) +* [GoogleTest](https://github.com/google/googletest) +* [Catch](https://github.com/catchorg/Catch2) +* [...](https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C++) + +Great question! There are a few unique features which makes `UT` worth trying + +* Firstly, it supports all the basic Unit Testing Framework features (automatic registration of tests, assertions, suites, etc...) +* It's easy to integrate (it's just one [header/module](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp)) +* It's macro free which makes testing experience that much nicer (it uses modern C++ features instead, macros are opt-in rather than being compulsory - [Can I still use macros?](#macros)) +* It's flexible (all parts of the framework such as: [runner, reporter, printer](#examples) can be customized, basically most other Unit Testing Frameworks can be implemented on top of UT primitives) +* It has smaller learning curve (just a few simple concepts ([expect, test, suite](#api))) +* It leverages C++ features to support more complex testing ([parameterized](#examples)) +* It's faster to compile and execute than similar frameworks which makes it suitable for bigger projects without additional hassle ([Benchmarks](#benchmarks)) +* It supports [TDD/BDD](#examples) workflows +* It supports [Gherkin](#examples) specification +* It supports [Spec](#examples) +* ... + +Sounds intriguing/interesting? Learn more at + +* [Tutorial](#tutorial) +* [Examples](#examples) +* [User-Guide](#user-guide) + +

+
+ + +
Quick Start +

+ +> https://bit.ly/ut-quick-start (slides) + +

+
+ + +
Overview +

+ +* No dependencies ([C++20](#cpp-20), Tested Compilers: GCC-9+, Clang-9.0+, Apple Clang-11.0.0+, MSVC-2019+*, Clang-cl-9.0+ +* Single header/module ([boost/ut.hpp](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp)) +* Macro-free ([How does it work?](#how-it-works)) +* Easy to use ([Minimal API](#api) - `test, suite, operators, literals, [expect]`) +* Fast to compile/execute ([Benchmarks](#benchmarks)) +* Features ([Assertions](https://github.com/boost-ext/ut/blob/master/example/expect.cpp), [Suites](https://github.com/boost-ext/ut/blob/master/example/suite.cpp), [Tests](https://github.com/boost-ext/ut/blob/master/example/test.cpp), [Sections](https://github.com/boost-ext/ut/blob/master/example/section.cpp), [Parameterized](https://github.com/boost-ext/ut/blob/master/example/parameterized.cpp), [BDD](https://github.com/boost-ext/ut/blob/master/example/BDD.cpp), [Gherkin](https://github.com/boost-ext/ut/blob/master/example/gherkin.cpp), [Spec](https://github.com/boost-ext/ut/blob/master/example/spec.cpp), [Matchers](https://github.com/boost-ext/ut/blob/master/example/matcher.cpp), [Logging](https://github.com/boost-ext/ut/blob/master/example/log.cpp), [Runners](https://github.com/boost-ext/ut/blob/master/example/cfg/runner.cpp), [Reporters](https://github.com/boost-ext/ut/blob/master/example/cfg/reporter.cpp), [...](https://github.com/boost-ext/ut/blob/master/example)) +* Integrations ([ApprovalTests.cpp](https://github.com/approvals/ApprovalTests.cpp/releases/tag/v.7.0.0)) + +

+
+ + +
Tutorial +

+ +

    Step 0: Get it... +

+ +> Get the latest latest header/module from [here!](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) + +> Include/Import + +```cpp +// #include // single header +// import boost.ut; // single module (C++20) + +int main() { } +``` + +> Compile & Run + +``` +$CXX main.cpp && ./a.out +``` + +``` +All tests passed (0 assert in 0 test) +``` + +> [Optional] Install it + +``` +cmake -Bbuild -H. +cd build && make # run tests +cd build && make install # install +``` + +> [Optional] CMake integration + +This project provides a CMake config and target. +Just load `ut` with `find_package` to import the `Boost::ut` target. +Linking against this target will add the necessary include directory for the single header file. +This is demonstrated in the following example. + +```cmake +find_package(ut REQUIRED) +add_library(my_test my_test.cpp) +target_link_libraries(my_test PRIVATE Boost::ut) +``` + +> [Optional] [Conan](https://conan.io) integration + +The [boost-ext-ut](https://conan.io/center/boost-ext-ut) package is available from [Conan Center](https://conan.io/center/). +Just include it in your project's Conanfile with `boost-ext-ut/1.1.9`. + +

+
+ +
    Step 1: Expect it... +

+ +> Let's write our first assertion, shall we? + +```cpp +int main() { + boost::ut::expect(true); +} +``` + +``` +All tests passed (1 asserts in 0 test) +``` + +> https://godbolt.org/z/vfx-eB + + +> Okay, let's make it fail now? + +```cpp +int main() { + boost::ut::expect(1 == 2); +} +``` + +``` +main.cpp:4:FAILED [false] +=============================================================================== +tests: 0 | 0 failed +asserts: 1 | 0 passed | 1 failed +``` + +> https://godbolt.org/z/7qTePx + +> Notice that expression `1 == 2` hasn't been printed. Instead we got `false`? + +> Let's print it then? + +```cpp +int main() { + using namespace boost::ut; + expect(1_i == 2); +} +``` + +``` +main.cpp:4:FAILED [1 == 2] +=============================================================================== +tests: 0 | 0 failed +asserts: 1 | 0 passed | 1 failed +``` + +> https://godbolt.org/z/7MXVzu + +> Okay, now we have it! `1 == 2` has been printed as expected. +> Notice the User Defined Literal (UDL) `1_i` was used. +> `_i` is a compile-time constant integer value + +* It allows to override comparison operators 👍 +* It disallow comparison of different types 👍 + +See the [User-guide](#user-guide) for more details. + +> Alternatively, a `terse` notation (no expect required) can be used. + +```cpp +int main() { + using namespace boost::ut::literals; + using namespace boost::ut::operators::terse; + + 1_i == 2; // terse notation +} +``` + +``` +main.cpp:7:FAILED [1 == 2] +=============================================================================== +tests: 0 | 0 failed +asserts: 1 | 0 passed | 1 failed +``` + +> https://godbolt.org/z/s77GSm + +> Other expression syntaxes are also available. + +```cpp +expect(1_i == 2); // UDL syntax +expect(1 == 2_i); // UDL syntax +expect(that % 1 == 2); // Matcher syntax +expect(eq(1, 2)); // eq/neq/gt/ge/lt/le +``` + +``` +main.cpp:6:FAILED [1 == 2] +main.cpp:7:FAILED [1 == 2] +main.cpp:8:FAILED [1 == 2] +main.cpp:9:FAILED [1 == 2] +=============================================================================== +tests: 0 | 0 failed +asserts: 4 | 0 passed | 4 failed +``` + +> https://godbolt.org/z/QbgGtc + +> Okay, but what about the case if my assertion is fatal. +> Meaning that the program will crash unless the processing will be terminated. +> Nothing easier, let's just add `>> fatal` after the expected expression to make it fatal. + +```cpp +expect((1 == 2_i) >> fatal); // fatal assertion +expect(1_i == 2); // not executed +``` + +``` +main.cpp:6:FAILED [1 == 2] +=============================================================================== +tests: 1 | 1 failed +asserts: 2 | 0 passed | 2 failed +``` + +> https://godbolt.org/z/WMe8Y1 + +> But my expression is more complex than just simple comparisons. +> Not a problem, logic operators are also supported in the `expect` 👍. + +```cpp +expect(42l == 42_l and 1 == 2_i); // compound expression +``` + +``` +main.cpp:5:FAILED [(42 == 42 and 1 == 2)] +=============================================================================== +tests: 0 | 0 failed +asserts: 1 | 0 passed | 1 failed +``` + +> https://godbolt.org/z/aEhX4t + +> Can I add a custom message though? +> Sure, `expect` calls are streamable! + +```cpp +int main() { + expect(42l == 42_l and 1 == 2_i) << "additional info"; +} +``` + +``` +main.cpp:5:FAILED [(42 == 42 and 1 == 2)] additional info +=============================================================================== +tests: 0 | 0 failed +asserts: 1 | 0 passed | 1 failed +``` + +> https://godbolt.org/z/v2PDuU + +

+
+ +
    Step 2: Group it... +

+ +> Assertions are great, but how to combine them into more cohesive units? +> `Test cases` are the way to go! They allow to group expectations for the same functionality into coherent units. + +```cpp +"hello world"_test = [] { }; +``` + +> Alternatively `test("hello world") = [] {}` can be used. + +``` +All tests passed (0 asserts in 1 tests) +``` + +> https://godbolt.org/z/Bh-EmY + + +> Notice `1 tests` but `0 asserts`. + +> Let's make our first end-2-end test case, shall we? + +```cpp +int main() { + "hello world"_test = [] { + int i = 43; + expect(42_i == i); + }; +} +``` + +``` +Running "hello world"... + main.cpp:8:FAILED [42 == 43] +FAILED +=============================================================================== +tests: 1 | 1 failed +asserts: 1 | 0 passed | 1 failed +``` + +> https://godbolt.org/z/Y43mXz + +> 👍 We are done here! + +> I'd like to nest my tests, though and share setup/tear-down. +> With lambdas used to represents `tests/sections` we can easily achieve that. +> Let's just take a look at the following example. + +```cpp +int main() { + "[vector]"_test = [] { + std::vector v(5); + + expect((5_ul == std::size(v)) >> fatal); + + should("resize bigger") = [v] { // or "resize bigger"_test + mut(v).resize(10); + expect(10_ul == std::size(v)); + }; + + expect((5_ul == std::size(v)) >> fatal); + + should("resize smaller") = [=]() mutable { // or "resize smaller"_test + v.resize(0); + expect(0_ul == std::size(v)); + }; + } +} +``` + +``` +All tests passed (4 asserts in 1 tests) +``` + +> https://godbolt.org/z/XWAdYt + +> Nice! That was easy, but I'm a believer into Behaviour Driven Development (`BDD`). +> Is there a support for that? +> Yes! Same example as above just with the `BDD` syntax. + +```cpp +int main() { + "vector"_test = [] { + given("I have a vector") = [] { + std::vector v(5); + expect((5_ul == std::size(v)) >> fatal); + + when("I resize bigger") = [=] { + mut(v).resize(10); + + then("The size should increase") = [=] { + expect(10_ul == std::size(v)); + }; + }; + }; + }; +} +``` + +``` +All tests passed (2 asserts in 1 tests) +``` + +> https://godbolt.org/z/dnvxsE + +> On top of that, `feature/scenario` aliases can be leveraged. + +```cpp +int main() { + feature("vector") = [] { + scenario("size") = [] { + given("I have a vector") = [] { + std::vector v(5); + expect((5_ul == std::size(v)) >> fatal); + + when("I resize bigger") = [=] { + mut(v).resize(10); + + then("The size should increase") = [=] { + expect(10_ul == std::size(v)); + }; + }; + }; + }; + }; +} +``` + +``` +All tests passed (2 asserts in 1 tests) +``` + +> https://godbolt.org/z/T4cWss + +> Can I use `Gherkin`? +> Yeah, let's rewrite the example using `Gherkin` specification + +```cpp +int main() { + bdd::gherkin::steps steps = [](auto& steps) { + steps.feature("Vector") = [&] { + steps.scenario("*") = [&] { + steps.given("I have a vector") = [&] { + std::vector v(5); + expect((5_ul == std::size(v)) >> fatal); + + steps.when("I resize bigger") = [&] { + v.resize(10); + }; + + steps.then("The size should increase") = [&] { + expect(10_ul == std::size(v)); + }; + }; + }; + }; + }; + + "Vector"_test = steps | + R"( + Feature: Vector + Scenario: Resize + Given I have a vector + When I resize bigger + Then The size should increase + )"; +} +``` + +``` +All tests passed (2 asserts in 1 tests) +``` + +> https://godbolt.org/z/jb1d8P + +> Nice, is `Spec` notation supported as well? + +```cpp +int main() { + describe("vector") = [] { + std::vector v(5); + expect((5_ul == std::size(v)) >> fatal); + + it("should resize bigger") = [v] { + mut(v).resize(10); + expect(10_ul == std::size(v)); + }; + }; +} +``` + +``` +All tests passed (2 asserts in 1 tests) +``` + +> https://godbolt.org/z/6jKKzT + +> That's great, but how can call the same tests with different arguments/types to be DRY (Don't Repeat Yourself)? +> Parameterized tests to the rescue! + +```cpp +int main() { + for (auto i : std::vector{1, 2, 3}) { + test("parameterized " + std::to_string(i)) = [i] { // 3 tests + expect(that % i > 0); // 3 asserts + }; + } +} +``` + +``` +All tests passed (3 asserts in 3 tests) +``` + +> https://godbolt.org/z/Utnd6X + +> That's it 😮! +> Alternatively, a convenient test syntax is also provided 👍 + +```cpp +int main() { + "args"_test = [](const auto& arg) { + expect(arg > 0_i) << "all values greater than 0"; + } | std::vector{1, 2, 3}; +} +``` + +``` +All tests passed (3 asserts in 3 tests) +``` + +> https://godbolt.org/z/6FHtpq + +> Check [Examples](#examples) for further reading. + +

+
+ +
    Step 3: Scale it... +

+ +> Okay, but my project is more complex than that. How can I scale? +> `Test suites` will make that possible. By using `suite` in translation units +> `tests` defined inside will be automatically registered 👍 + +```cpp +suite errors = [] { + "exception"_test = [] { + expect(throws([] { throw 0; })) << "throws any exception"; + }; + + "failure"_test = [] { + expect(aborts([] { assert(false); })); + }; +}; + +int main() { } +``` + +``` +All tests passed (2 asserts in 2 tests) +``` + +> https://godbolt.org/z/_ccGwZ + +--- + +> What's next? +> * [Examples](#examples) +> * [User-Guide](#user-guide) + +

+
+ +

+
+ + +
Examples +

+ +

    Assertions +

+ +```cpp +// operators +expect(0_i == sum()); +expect(2_i != sum(1, 2)); +expect(sum(1) >= 0_i); +expect(sum(1) <= 1_i); +``` + +```cpp +// message +expect(3_i == sum(1, 2)) << "wrong sum"; +``` + +```cpp +// expressions +expect(0_i == sum() and 42_i == sum(40, 2)); +expect(0_i == sum() or 1_i == sum()) << "compound"; +``` + +```cpp +// matchers +expect(that % 0 == sum()); +expect(that % 42 == sum(40, 2) and that % (1 + 2) == sum(1, 2)); +expect(that % 1 != 2 or 2_i > 3); +``` + +```cpp +// eq/neq/gt/ge/lt/le +expect(eq(42, sum(40, 2))); +expect(neq(1, 2)); +expect(eq(sum(1), 1) and neq(sum(1, 2), 2)); +expect(eq(1, 1) and that % 1 == 1 and 1_i == 1); +``` + +```cpp +// floating points +expect(42.1_d == 42.101) << "epsilon=0.1"; +expect(42.10_d == 42.101) << "epsilon=0.01"; +expect(42.10000001 == 42.1_d) << "epsilon=0.1"; +``` + +```cpp +// constant +constexpr auto compile_time_v = 42; +auto run_time_v = 99; +expect(constant<42_i == compile_time_v> and run_time_v == 99_i); +``` + +```cpp +// failure +expect(1_i == 2) << "should fail"; +expect(sum() == 1_i or 2_i == sum()) << "sum?"; +``` + +``` +assertions.cpp:53:FAILED [1 == 2] should fail +assertions.cpp:54:FAILED [(0 == 1 or 2 == 0)] sum? +=============================================================================== +tests: 0 | 0 failed +asserts: 20 | 18 passed | 2 failed +``` + +> https://godbolt.org/z/E1c7G5 + +

+
+ +
    Tests +

+ +

        Run/Skip/Tag +

+ +```cpp +"run UDL"_test = [] { + expect(42_i == 42); +}; + +skip / "don't run UDL"_test = [] { + expect(42_i == 43) << "should not fire!"; +}; +``` + +``` +All tests passed (1 asserts in 1 tests) +1 tests skipped +``` + +```cpp +test("run function") = [] { + expect(42_i == 42); +}; + +skip / test("don't run function") = [] { + expect(42_i == 43) << "should not fire!"; +}; +``` + +``` +All tests passed (1 asserts in 1 tests) +1 tests skipped +``` + +```cpp +tag("nightly") / tag("slow") / +"performance"_test= [] { + expect(42_i == 42); +}; + +tag("slow") / +"run slowly"_test= [] { + expect(42_i == 43) << "should not fire!"; +}; +``` + +``` +cfg = {.tag = {"nightly"}}; +``` + +``` +All tests passed (1 asserts in 1 tests) +1 tests skipped +``` + +> https://godbolt.org/z/X3_kG4 + +

+
+ +
        Sections +

+ +```cpp +"[vector]"_test = [] { + std::vector v(5); + + expect((5_ul == std::size(v)) >> fatal); + + should("resize bigger") = [=] { // or "resize bigger"_test + mut(v).resize(10); + expect(10_ul == std::size(v)); + }; + + expect((5_ul == std::size(v)) >> fatal); + + should("resize smaller") = [=]() mutable { // or "resize smaller"_test + v.resize(0); + expect(0_ul == std::size(v)); + }; +}; +``` + +``` +All tests passed (4 asserts in 1 tests) +``` + +> https://godbolt.org/z/cE91bj + +

+
+ +
        Behavior Driven Development (BDD) +

+ +```cpp +"Scenario"_test = [] { + given("I have...") = [] { + when("I run...") = [] { + then("I expect...") = [] { expect(1_i == 1); }; + then("I expect...") = [] { expect(1 == 1_i); }; + }; + }; +}; +``` + +``` +All tests passed (2 asserts in 1 tests) +``` + +> https://godbolt.org/z/mNBySr + +

+
+ +
        Gherkin +

+ +```cpp +int main() { + bdd::gherkin::steps steps = [](auto& steps) { + steps.feature("*") = [&] { + steps.scenario("*") = [&] { + steps.given("I have a number {value}") = [&](int value) { + auto number = value; + steps.when("I add {value} to it") = [&](int value) { + number += value; + }; + steps.then("I expect number to be {value}") = [&](int value) { + expect(that % number == value); + }; + }; + }; + }; + }; + + "Gherkin"_test = steps | + R"( + Feature: Number + Scenario: Addition + Given I have a number 40 + When I add 2 to it + Then I expect number to be 42 + )"; +} +``` + +``` +All tests passed (1 asserts in 1 tests) +``` + +> https://godbolt.org/z/BP3hyt + +

+
+ +
        Spec +

+ +```cpp +int main() { + describe("equality") = [] { + it("should be equal") = [] { expect(0_i == 0); }; + it("should not be equal") = [] { expect(1_i != 0); }; + }; +} +``` + +``` +All tests passed (2 asserts in 1 tests) +``` + +> https://godbolt.org/z/BXYJ3a + +

+
+ +
        Parameterized +

+ +```cpp +for (auto i : std::vector{1, 2, 3}) { + test("parameterized " + std::to_string(i)) = [i] { + expect(that % i > 0); + }; +} + +"args"_test = + [](auto arg) { + expect(arg >= 1_i); + } + | std::vector{1, 2, 3}; + +"types"_test = + [] { + expect(std::is_integral_v) << "all types are integrals"; + } + | std::tuple{}; + +"args and types"_test = + [](TArg arg) { + expect(std::is_integral_v >> fatal); + expect(42_i == arg or "is true"_b == arg); + expect(type == type or type == type); + } + | std::tuple{true, 42}; +``` + +``` +All tests passed (14 asserts in 10 tests) +``` + +> https://godbolt.org/z/4xGGdo + + +> And whenever I need to know the specific type for which the test failed, +> I can use `reflection::type_name()`, like this: + +```cpp +"types with type name"_test = + []() { + expect(std::is_unsigned_v) << reflection::type_name() << "is unsigned"; + } + | std::tuple{}; +``` + +``` +Running "types with type name"...PASSED +Running "types with type name"... + :10:FAILED [false] float is unsigned +FAILED +``` + +> https://godbolt.org/z/MEnGnbTY4 + +

+
+ + +

+
+ +
    Suites +

+ +```cpp +namespace ut = boost::ut; + +ut::suite errors = [] { + using namespace ut; + + "throws"_test = [] { + expect(throws([] { throw 0; })); + }; + + "doesn't throw"_test = [] { + expect(nothrow([]{})); + }; +}; + +int main() { } +``` + +``` +All tests passed (2 asserts in 2 tests) +``` + +> https://godbolt.org/z/CFbTP9 + +

+
+ +
    Misc +

+ +

        Logging +

+ +```cpp +"logging"_test = [] { + log << "pre"; + expect(42_i == 43) << "message on failure"; + log << "post"; +}; +``` + +``` +Running "logging"... +pre + logging.cpp:8:FAILED [42 == 43] message on failure +post +FAILED + +=============================================================================== + +tests: 1 | 1 failed +asserts: 1 | 0 passed | 1 failed +``` + +> https://godbolt.org/z/26fPSY + +

+
+ +
        Matchers +

+ +```cpp +"matchers"_test = [] { + constexpr auto is_between = [](auto lhs, auto rhs) { + return [=](auto value) { + return that % value >= lhs and that % value <= rhs; + }; + }; + + expect(is_between(1, 100)(42)); + expect(not is_between(1, 100)(0)); +}; +``` + +``` +All tests passed (2 asserts in 1 tests) +``` + +> https://godbolt.org/z/4qwrCi + +

+
+ +
        Exceptions/Aborts +

+ +```cpp +"exceptions/aborts"_test = [] { + expect(throws([] { throw std::runtime_error{""}; })) + << "throws runtime_error"; + expect(throws([] { throw 0; })) << "throws any exception"; + expect(nothrow([]{})) << "doesn't throw"; + expect(aborts([] { assert(false); })); +}; +``` + +``` +All tests passed (4 asserts in 1 tests) +``` + +> https://godbolt.org/z/A2EehK + +

+
+ +

+
+ +
    Config +

+ +

        Runner +

+ +```cpp +namespace ut = boost::ut; + +namespace cfg { + class runner { + public: + template auto on(ut::events::test test) { test(); } + template auto on(ut::events::skip) {} + template + auto on(ut::events::assertion) -> bool { return true; } + auto on(ut::events::fatal_assertion) {} + template auto on(ut::events::log) {} + }; +} // namespace cfg + +template<> auto ut::cfg = cfg::runner{}; +``` + +> https://godbolt.org/z/jdg687 + +

+
+ +
        Reporter +

+ +```cpp +namespace ut = boost::ut; + +namespace cfg { + class reporter { + public: + auto on(ut::events::test_begin) -> void {} + auto on(ut::events::test_run) -> void {} + auto on(ut::events::test_skip) -> void {} + auto on(ut::events::test_end) -> void {} + template auto on(ut::events::log) -> void {} + template + auto on(ut::events::assertion_pass) -> void {} + template + auto on(ut::events::assertion_fail) -> void {} + auto on(ut::events::fatal_assertion) -> void {} + auto on(ut::events::exception) -> void {} + auto on(ut::events::summary) -> void {} + }; +} // namespace cfg + +template <> +auto ut::cfg = ut::runner{}; +``` + +> https://godbolt.org/z/gsAPKg + +

+
+ +
        Printer +

+ +```cpp +namespace ut = boost::ut; + +namespace cfg { +struct printer : ut::printer { + template + auto& operator<<(T&& t) { + std::cerr << std::forward(t); + return *this; + } +}; +} // namespace cfg + +template <> +auto ut::cfg = ut::runner>{}; + +int main() { + using namespace ut; + "printer"_test = [] {}; +} +``` + +> https://godbolt.org/z/XCscF9 + +

+
+ +

+
+ +

+
+ + + + +
User Guide +

+ +

    API +

+ +```cpp +export module boost.ut; /// __cpp_modules + +namespace boost::inline ext::ut::inline v1_1_9 { + /** + * Represents test suite object + */ + struct suite final { + /** + * Creates and executes test suite + * @example suite _ = [] {}; + * @param suite test suite function + */ + constexpr explicit(false) suite(auto suite); + }; + + /** + * Creates a test + * @example "name"_test = [] {}; + * @return test object to be executed + */ + constexpr auto operator""_test; + + /** + * Creates a test + * @example test("name") = [] {}; + * @return test object to be executed + */ + constexpr auto test = [](const auto name); + + /** + * Creates a test + * @example should("name") = [] {}; + * @return test object to be executed + */ + constexpr auto should = [](const auto name); + + /** + * Behaviour Driven Development (BDD) helper functions + * @param name step name + * @return test object to be executed + */ + constexpr auto given = [](const auto name); + constexpr auto when = [](const auto name); + constexpr auto then = [](const auto name); + + /** + * Evaluates an expression + * @example expect(42 == 42_i and 1 != 2_i); + * @param expr expression to be evaluated + * @param source location https://en.cppreference.com/w/cpp/utility/source_location + * @return stream + */ + constexpr OStream& expect( + Expression expr, + const std::source_location& location = std::source_location::current() + ); + + struct { + /** + * @example (that % 42 == 42); + * @param expr expression to be evaluated + */ + [[nodiscard]] constexpr auto operator%(Expression expr) const; + } that{}; + + inline namespace literals { + /** + * User defined literals to represent constant values + * @example 42_i, 0_uc, 1.23_d + */ + constexpr auto operator""_i; /// int + constexpr auto operator""_s; /// short + constexpr auto operator""_c; /// char + constexpr auto operator""_l; /// long + constexpr auto operator""_ll; /// long long + constexpr auto operator""_u; /// unsigned + constexpr auto operator""_uc; /// unsigned char + constexpr auto operator""_us; /// unsigned short + constexpr auto operator""_ul; /// unsigned long + constexpr auto operator""_f; /// float + constexpr auto operator""_d; /// double + constexpr auto operator""_ld; /// long double + + /** + * Represents dynamic values + * @example _i(42), _f(42.) + */ + constexpr auto _b(bool); + constexpr auto _c(char); + constexpr auto _s(short); + constexpr auto _i(int); + constexpr auto _l(long); + constexpr auto _ll(long long); + constexpr auto _u(unsigned); + constexpr auto _uc(unsigned char); + constexpr auto _us(unsigned short); + constexpr auto _ul(unsigned long); + constexpr auto _f(float); + constexpr auto _d(double); + constexpr auto _ld(long double); + + /** + * Logical representation of constant boolean (true) value + * @example "is set"_b : true + * not "is set"_b : false + */ + constexpr auto operator ""_b; + } // namespace literals + + inline namespace operators { + /** + * Comparison functions to be used in expressions + * @example eq(42, 42), neq(1, 2) + */ + constexpr auto eq(Operator lhs, Operator rhs); /// == + constexpr auto neq(Operator lhs, Operator rhs); /// != + constexpr auto gt(Operator lhs, Operator rhs); /// > + constexpr auto ge(Operator lhs, Operator rhs); /// >= + constexpr auto lt(Operator lhs, Operator rhs); /// < + constexpr auto le(Operator lhs, Operator rhs); /// <= + + /** + * Overloaded comparison operators to be used in expressions + * @example (42_i != 0) + */ + constexpr auto operator==; + constexpr auto operator!=; + constexpr auto operator>; + constexpr auto operator>=; + constexpr auto operator<; + constexpr auto operator<=; + + /** + * Overloaded logic operators to be used in expressions + * @example (42_i != 0 and 1 == 2_i) + */ + constexpr auto operator and; + constexpr auto operator or; + constexpr auto operator not; + + /** + * Executes parameterized tests + * @example "parameterized"_test = [](auto arg) {} | std::tuple{1, 2, 3}; + */ + constexpr auto operator|; + + /** + * Creates tags + * @example tag("slow") / tag("nightly") / "perf"_test = []{}; + */ + constexpr auto operator/; + + /** + * Creates a `fatal_assertion` from an expression + * @example (42_i == 0) >> fatal + */ + constexpr auto operator>>; + } // namespace operators + + /** + * Creates skippable test object + * @example skip / "don't run"_test = [] { }; + */ + constexpr auto skip = tag("skip"); + + struct { + /** + * @example log << "message!"; + * @param msg stringable message + */ + auto& operator<<(Msg msg); + } log{}; + + /** + * Makes object mutable + * @example mut(object) + * @param t object to be mutated + */ + template auto mut(const T& t) -> T&; + + /** + * Default execution flow policy + */ + class runner { + public: + /** + * @example cfg = { + .filter = "test.section.*", + .colors = { .none = "" }, + .dry__run = true + }; + * @param options.filter {default: "*"} runs all tests which names + matches test.section.* filter + * @param options.colors {default: { + .none = "\033[0m", + .pass = "\033[32m", + .fail = "\033[31m" + } if specified then overrides default color values + * @param options.dry_run {default: false} if true then print test names to be + executed without running them + */ + auto operator=(options); + + /** + * @example suite _ = [] {}; + * @param suite() executes suite + */ + template + auto on(ut::events::suite); + + /** + * @example "name"_test = [] {}; + * @param test.type ["test", "given", "when", "then"] + * @param test.name "name" + * @param test.arg parameterized argument + * @param test() executes test + */ + template + auto on(ut::events::test); + + /** + * @example skip / "don't run"_test = []{}; + * @param skip.type ["test", "given", "when", "then"] + * @param skip.name "don't run" + * @param skip.arg parameterized argument + */ + template + auto on(ut::events::skip); + + /** + * @example file.cpp:42: expect(42_i == 42); + * @param assertion.expr 42_i == 42 + * @param assertion.location { "file.cpp", 42 } + * @return true if expr passes, false otherwise + */ + template + auto on(ut::events::assertion) -> bool; + + /** + * @example expect((2_i == 1) >> fatal) + * @note triggered by `fatal` + * should std::exit + */ + auto on(ut::events::fatal_assertion); + + /** + * @example log << "message" + * @param log.msg "message" + */ + template + auto on(ut::events::log); + + /** + * Explicitly runs registered test suites + * If not called directly test suites are executed with run's destructor + * @example return run({.report_errors = true}) + * @param run_cfg.report_errors {default: false} if true it prints the summary after runnig + */ + auto run(run_cfg); + + /** + * Runs registered test suites if they haven't been explicilty executed already + */ + ~run(); + }; + + /** + * Default reporter policy + */ + class reporter { + public: + /** + * @example file.cpp:42: "name"_test = [] {}; + * @param test_begin.type ["test", "given", "when", "then"] + * @param test_begin.name "name" + * @param test_begin.location { "file.cpp", 42 } + */ + auto on(ut::events::test_begin) -> void; + + /** + * @example "name"_test = [] {}; + * @param test_run.type ["test", "given", "when", "then"] + * @param test_run.name "name" + */ + auto on(ut::events::test_run) -> void; + + /** + * @example "name"_test = [] {}; + * @param test_skip.type ["test", "given", "when", "then"] + * @param test_skip.name "name" + */ + auto on(ut::events::test_skip) -> void; + + /** + * @example "name"_test = [] {}; + * @param test_end.type ["test", "given", "when", "then"] + * @param test_end.name "name" + */ + auto on(ut::events::test_end) -> void; + + /** + * @example log << "message" + * @param log.msg "message" + */ + template + auto on(ut::events::log) -> void; + + /** + * @example file.cpp:42: expect(42_i == 42); + * @param assertion_pass.expr 42_i == 42 + * @param assertion_pass.location { "file.cpp", 42 } + */ + template + auto on(ut::events::assertion_pass) -> void; + + /** + * @example file.cpp:42: expect(42_i != 42); + * @param assertion_fail.expr 42_i != 42 + * @param assertion_fail.location { "file.cpp", 42 } + */ + template + auto on(ut::events::assertion_fail) -> void; + + /** + * @example expect((2_i == 1) >> fatal) + * @note triggered by `fatal` + * should std::exit + */ + auto on(ut::events::fatal_assertion) -> void; + + /** + * @example "exception"_test = [] { throw std::runtime_error{""}; }; + */ + auto on(ut::events::exception) -> void; + + /** + * @note triggered on destruction of runner + */ + auto on(ut::events::summary) -> void; + }; + + /** + * Used to override default running policy + * @example template <> auto cfg = runner{}; + */ + struct override {}; + + /** + * Default UT execution policy + * Can be overwritten with override + */ + template auto cfg = runner{}; +} +``` + +

+
+ +
    Configuration +

+ +| Option | Description | Example | +|-|-|-| +| `BOOST_UT_VERSION` | Current version | `1'1'9` | + +

+
+ +

+
+ + +
FAQ +

+ + +

    How does it work? +

+ +> `suite` + + ```cpp + /** + * Reperesents suite object + * @example suite _ = []{}; + */ + struct suite final { + /** + * Assigns and executes test suite + */ + [[nodiscard]] constexpr explicit(false) suite(Suite suite) { + suite(); + } + }; + ``` + +> `test` + + ```cpp + /** + * Creates named test object + * @example "hello world"_test + * @return test object + */ + [[nodiscard]] constexpr Test operator ""_test(const char* name, std::size_t size) { + return test{{name, size}}; + } + ``` + + ```cpp + /** + * Represents test object + */ + struct test final { + std::string_view name{}; /// test case name + + /** + * Assigns and executes test function + * @param test function + */ + constexpr auto operator=(const Test& test) { + std::cout << "Running... " << name << '\n'; + test(); + } + }; + ``` + +> `expect` + + ```cpp + /** + * Evaluates an expression + * @example expect(42_i == 42); + * @param expr expression to be evaluated + * @param source location https://en.cppreference.com/w/cpp/utility/source_location + * @return stream + */ + constexpr OStream& expect( + Expression expr, + const std::source_location& location = std::source_location::current() + ) { + if (not static_cast(expr) { + std::cerr << location.file() + << ':' + << location.line() + << ":FAILED: " + << expr + << '\n'; + } + + return std::cerr; + } + ``` + + ```cpp + /** + * Creates constant object for which operators can be overloaded + * @example 42_i + * @return integral constant object + */ + template + [[nodiscard]] constexpr Operator operator""_i() -> integral_constant>; + ``` + + ```cpp + /** + * Overloads comparison if at least one of {lhs, rhs} is an Operator + * @example (42_i == 42) + * @param lhs Left-hand side operator + * @param rhs Right-hand side operator + * @return Comparison object + */ + [[nodiscard]] constexpr auto operator==(Operator lhs, Operator rhs) { + return eq{lhs, rhs}; + } + ``` + + ```cpp + /** + * Comparison Operator + */ + template + struct eq final { + TLhs lhs{}; // Left-hand side operator + TRhs rhs{}; // Right-hand side operator + + /** + * Performs comparison operatation + * @return true if expression is succesful + */ + [[nodiscard]] constexpr explicit operator bool() const { + return lhs == rhs; + } + + /** + * Nicely prints the operation + */ + friend auto operator<<(OStream& os, const eq& op) -> Ostream& { + return (os << op.lhs << " == " << op.rhs); + } + }; + ``` + +> `Sections` + + ```cpp + /** + * Convenient aliases for creating test named object + * @example should("return true") = [] {}; + */ + constexpr auto should = [](const auto name) { return test{name}; }; + ``` + +> `Behaviour Driven Development (BDD)` + + ```cpp + /** + * Convenient aliases for creating BDD tests + * @example feature("Feature") = [] {}; + * @example scenario("Scenario") = [] {}; + * @example given("I have an object") = [] {}; + * @example when("I call it") = [] {}; + * @example then("I should get") = [] {}; + */ + constexpr auto feature = [](const auto name) { return test{name}; }; + constexpr auto scenario = [](const auto name) { return test{name}; }; + constexpr auto given = [](const auto name) { return test{name}; }; + constexpr auto when = [](const auto name) { return test{name}; }; + constexpr auto then = [](const auto name) { return test{name}; }; + ``` + +> https://godbolt.org/z/6Nk5Mi + +> `Spec` + + ```cpp + /** + * Convenient aliases for creating Spec tests + * @example describe("test") = [] {}; + * @example it("should...") = [] {}; + */ + constexpr auto describe = [](const auto name) { return test{name}; }; + constexpr auto it = [](const auto name) { return test{name}; }; + ``` + +> [Example implementation](https://github.com/boost-ext/ut/tree/gh-pages/denver-cpp-2020/example) + +> Try it online + +* Header - https://godbolt.org/z/x96n8b +* Module - https://wandbox.org/permlink/LrV7WwIgghTP1nrs + +

+
+ + +
    Fast compilation times (Benchmarks)? +

+ +> Implementation + +* Leveraging [C++20](#cpp-20) features + +* Avoiding unique types for lambda expressions + +```cpp + template + requires not std::convertible_to> + constexpr auto operator=(Test test); + +vs + + // Compiles 5x faster because it doesn't introduce a new type for each lambda + constexpr auto operator=(void (*test)()); +``` + +* `Type-name` erasure (allows types/function memoization) + +```cpp + eq, int>{ {}, 42 } + +vs + + // Can be memoized - faster to compile + eq{42, 42} +``` + +* Limiting preprocessor work + * Single header/module + * Minimal number of include files + +* Simplified versions of + * `std::function` + * `std::string_view` + +

+
+ + +
    C++20 features? +

+ +* API + + * [Source Location](https://eel.is/c++draft/support.srcloc#source.location.syn) + * Assertions - `expect(false)` - ` __FILE__:__LINE__:FAILED [false]` + + * [Designated initializers](https://eel.is/c++draft/dcl.init#nt:designated-initializer-list) + * Configuration - `cfg = {.filter = "test"}` + + * [Non-Type Template Parameter](https://eel.is/c++draft/temp.arg.nontype) + * Constant matchers - `constant<42_i == 42>` + + * [Template Parameter List for generic lambdas](https://eel.is/c++draft/expr.prim.lambda) + * Parameterized tests - `"types"_test = []() {};` + + * [Concepts](https://eel.is/c++draft/concepts.lang) + * Operators - `Operator @ Operator` + + * [Modules](https://eel.is/c++draft/module) + * `import boost.ut;` + +

+
+ + +
    C++2X integration? +

+ +> Parameterized tests with Expansion statements (https://wg21.link/P1306r1) + +```cpp +template for (auto arg : std::tuple{}) { + test("types " + std::to_string(arg)) = [arg] { + expect(type(arg) == type or type(arg) == type); + }; +} +``` + +``` +All tests passed (2 asserts in 2 tests) +``` + +> https://cppx.godbolt.org/z/dMmqmM + +

+
+ + +
    Is standardization an option? +

+ +> Personally, I believe that C++ standard could benefit from common testing primitives (`expect`, `""_test`) because + +* It lowers the entry-level to the language (no need for third-party libraries) +* It improves the education aspect (one standard way of doing it) +* It makes the language more coherent/stable (consistent design with other features, stable API) +* It makes the testing a first class citizen (shows that the community cares about this aspect of the language) +* It allows to publish tests for the Standard Library (STL) in the standard way (coherency, easier to extend) +* It allows to act as additional documentation as a way to verify whether a particular implementation is conforming (quality, self-verification) +* It helps with establishing standard vocabulary for testing (common across STL and other projects) + +

+
+ + +
    Can I still use macros? +

+ +> Sure, although please notice that there are negatives of using macros such as + +* Error messages might be not clear and/or point to the wrong line +* Global scope will be polluted +* Type safety will be ignored + +```cpp +#define EXPECT(...) ::boost::ut::expect(::boost::ut::that % __VA_ARGS__) +#define SUITE ::boost::ut::suite _ = [] +#define TEST(name) ::boost::ut::detail::test{"test", name} = [=]() mutable + +SUITE { + TEST("suite") { + EXPECT(42 == 42); + }; +}; + +int main() { + TEST("macro") { + EXPECT(1 != 2); + }; + + TEST("vector") { + std::vector v(5); + + EXPECT((5u == std::size(v)) >> fatal) << "fatal"; + + TEST("resize bigger") { + v.resize(10); + EXPECT(10u == std::size(v)); + }; + }; +} +``` + +``` +All tests passed (4 asserts in 3 tests) +``` + +> https://godbolt.org/z/WcEKTr + +

+
+ +
    What about Mocks/Stubs/Fakes? +

+ +> Consider using one of the following frameworks + +* https://github.com/cpp-testing/GUnit/blob/master/docs/GMock.md +* https://github.com/eranpeer/FakeIt +* https://github.com/dascandy/hippomocks + +

+
+ +
    What about Microbenchmarking? +

+ +> [Example benchmark](example/benchmark.cpp) + +> Consider using one of the following frameworks + +* https://github.com/google/benchmark +* https://github.com/DigitalInBlue/Celero +* https://github.com/libnonius/nonius +* https://github.com/martinus/nanobench + +

+
+ +
    Related materials/talks? +

+ +* [[Boost].UT - Unit Testing Framework - Kris Jusiak](https://boost-ext.github.io/ut/denver-cpp-2019) +* [Future of Testing with C++20 - Kris Jusiak](https://boost-ext.github.io/ut/meeting-cpp-2020) +* [Macro-Free Testing with C++20 - Kris Jusiak](https://www.youtube.com/watch?v=irdgFyxOs_Y) +* ["If you liked it then you `"should have put a"_test` on it", Beyonce rule - Kris Jusiak](https://www.youtube.com/watch?v=yCI8MjvOMeE) +* [Principles of Unit Testing With C++ - Dave Steffen and Kris Jusiak](https://www.youtube.com/watch?v=oOcuJdJJ33g) +* [Empirical Unit Testing - Dave Steffen](https://www.twitch.tv/videos/686512433) + +

+
+ + +
    How to contribute? +

+ +> [CONTRIBUTING](.github/CONTRIBUTING.md) + +

+ +

+
+ + +
Benchmarks +

+ +| Framework | Version | Standard | License | Linkage | Test configuration | +|-|-|-|-|-|-| +| [Boost.Test](https://github.com/boostorg/test) | [1.71.0](https://www.boost.org/users/history/version_1_71_0.html) | C++03 | Boost 1.0 | single header/library | `static library` | +| [GoogleTest](https://github.com/google/googletest) | [1.10.0](https://github.com/google/googletest/releases/tag/release-1.10.0) | C++11 | BSD-3 | library | `static library` | +| [Catch](https://github.com/catchorg/Catch2) | [2.10.2](https://github.com/catchorg/Catch2/releases/download/v2.10.2/catch.hpp) | C++11 | Boost 1.0 | single header | `CATCH_CONFIG_FAST_COMPILE` | +| [Doctest](https://github.com/onqtam/doctest) | [2.3.5](https://github.com/onqtam/doctest/blob/master/doctest/doctest.h) | C++11 | MIT | single header | `DOCTEST_CONFIG_SUPER_FAST_ASSERTS` | +| [UT](https://github.com/boost-ext/ut) | [1.1.0](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) | C++20 | Boost 1.0 | single header/module | | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Include / 0 tests, 0 asserts, 1 cpp file +
+ Assert / 1 test, 1'000'000 asserts, 1 cpp file +
+ Test / 1'000 tests, 0 asserts, 1 cpp file +
+ Suite / 10'000 tests, 0 asserts, 100 cpp files +
+ Suite+Assert / 10'000 tests, 40'000 asserts, 100 cpp files +
+ Suite+Assert+STL / 10'000 tests, 20'000 asserts, 100 cpp files +
+ Incremental Build - Suite+Assert+STL / 1 cpp file change (1'000 tests, 20'000 asserts, 100 cpp files) +
+ Suite+Assert+STL / 10'000 tests, 20'000 asserts, 100 cpp files
(Headers vs Precompiled headers vs C++20 Modules)
+
+ +> https://github.com/cpp-testing/ut-benchmark + +

+
+ +

+
+ +--- + +**Disclaimer** `UT` is not an official Boost library. + +

diff --git a/lib/ut/boost/ut.hpp b/lib/ut/boost/ut.hpp new file mode 100644 index 0000000..0e5c2f8 --- /dev/null +++ b/lib/ut/boost/ut.hpp @@ -0,0 +1,2391 @@ +// +// Copyright (c) 2019-2021 Kris Jusiak (kris at jusiak dot net) +// +// 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) +// +#if defined(__cpp_modules) && !defined(BOOST_UT_DISABLE_MODULE) +export module boost.ut; +export import std; +#else +#pragma once +#endif + +#if __has_include() +#include // and, or, not, ... +#endif + +#if not defined(__cpp_rvalue_references) +#error "[Boost::ext].UT requires support for rvalue references"; +#elif not defined(__cpp_decltype) +#error "[Boost::ext].UT requires support for decltype"; +#elif not defined(__cpp_return_type_deduction) +#error "[Boost::ext].UT requires support for return type deduction"; +#elif not defined(__cpp_deduction_guides) +#error "[Boost::ext].UT requires support for return deduction guides"; +#elif not defined(__cpp_generic_lambdas) +#error "[Boost::ext].UT requires support for generic lambdas"; +#elif not defined(__cpp_constexpr) +#error "[Boost::ext].UT requires support for constexpr"; +#elif not defined(__cpp_alias_templates) +#error "[Boost::ext].UT requires support for alias templates"; +#elif not defined(__cpp_variadic_templates) +#error "[Boost::ext].UT requires support for variadic templates"; +#elif not defined(__cpp_fold_expressions) +#error "[Boost::ext].UT requires support for return fold expressions"; +#elif not defined(__cpp_static_assert) +#error "[Boost::ext].UT requires support for static assert"; +#else +#define BOOST_UT_VERSION 1'1'9 + +#if defined(__has_builtin) and defined(__GNUC__) and (__GNUC__ < 10) and \ + not defined(__clang__) +#undef __has_builtin +#endif + +#if not defined(__has_builtin) +#if defined(__GNUC__) and (__GNUC__ >= 9) +#define __has___builtin_FILE 1 +#define __has___builtin_LINE 1 +#endif +#define __has_builtin(...) __has_##__VA_ARGS__ +#endif + +#include +#include +#include +#include +#include +#include +#include +#if __has_include() and __has_include() +#include +#include +#endif +#if defined(__cpp_exceptions) +#include +#endif + +#if defined(__cpp_lib_source_location) +#include +#endif + +#if defined(__cpp_modules) && !defined(BOOST_UT_DISABLE_MODULE) +export +#endif + namespace boost::inline ext::ut::inline v1_1_9 { +namespace utility { +template +class function; +template +class function { + public: + constexpr function() = default; + template + constexpr /*explicit(false)*/ function(T data) + : invoke_{invoke_impl}, + destroy_{destroy_impl}, + data_{new T{static_cast(data)}} {} + constexpr function(function&& other) noexcept + : invoke_{static_cast(other.invoke_)}, + destroy_{static_cast(other.destroy_)}, + data_{static_cast(other.data_)} { + other.data_ = {}; + } + constexpr function(const function&) = delete; + ~function() { destroy_(data_); } + + constexpr function& operator=(const function&) = delete; + constexpr function& operator=(function&&) = delete; + [[nodiscard]] constexpr auto operator()(TArgs... args) -> R { + return invoke_(data_, args...); + } + [[nodiscard]] constexpr auto operator()(TArgs... args) const -> R { + return invoke_(data_, args...); + } + + private: + template + [[nodiscard]] static auto invoke_impl(void* data, TArgs... args) -> R { + return (*static_cast(data))(args...); + } + + template + static auto destroy_impl(void* data) -> void { + delete static_cast(data); + } + + R (*invoke_)(void*, TArgs...){}; + void (*destroy_)(void*){}; + void* data_{}; +}; + +[[nodiscard]] inline auto is_match(std::string_view input, + std::string_view pattern) -> bool { + if (std::empty(pattern)) { + return std::empty(input); + } + + if (std::empty(input)) { + return pattern[0] == '*' ? is_match(input, pattern.substr(1)) : false; + } + + if (pattern[0] != '?' and pattern[0] != '*' and pattern[0] != input[0]) { + return false; + } + + if (pattern[0] == '*') { + for (decltype(std::size(input)) i = 0u; i <= std::size(input); ++i) { + if (is_match(input.substr(i), pattern.substr(1))) { + return true; + } + } + return false; + } + + return is_match(input.substr(1), pattern.substr(1)); +} + +template +[[nodiscard]] constexpr auto match(const TPattern& pattern, const TStr& str) + -> std::vector { + std::vector groups{}; + auto pi = 0u; + auto si = 0u; + + const auto matcher = [&](char b, char e, char c = 0) { + const auto match = si; + while (str[si] and str[si] != b and str[si] != c) { + ++si; + } + groups.emplace_back(str.substr(match, si - match)); + while (pattern[pi] and pattern[pi] != e) { + ++pi; + } + pi++; + }; + + while (pi < std::size(pattern) && si < std::size(str)) { + if (pattern[pi] == '\'' and str[si] == '\'' and pattern[pi + 1] == '{') { + ++si; + matcher('\'', '}'); + } else if (pattern[pi] == '{') { + matcher(' ', '}', ','); + } else if (pattern[pi] != str[si]) { + return {}; + } + ++pi; + ++si; + } + + if (si < str.size() or pi < std::size(pattern)) { + return {}; + } + + return groups; +} + +template +[[nodiscard]] inline auto split(T input, TDelim delim) -> std::vector { + std::vector output{}; + std::size_t first{}; + while (first < std::size(input)) { + const auto second = input.find_first_of(delim, first); + if (first != second) { + output.emplace_back(input.substr(first, second - first)); + } + if (second == T::npos) { + break; + } + first = second + 1; + } + return output; +} +} // namespace utility + +namespace reflection { +#if defined(__cpp_lib_source_location) +using source_location = std::source_location; +#else +class source_location { + public: + [[nodiscard]] static constexpr auto current( +#if (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE)) + const char* file = __builtin_FILE(), int line = __builtin_LINE() +#else + const char* file = "unknown", int line = {} +#endif + ) noexcept { + source_location sl{}; + sl.file_ = file; + sl.line_ = line; + return sl; + } + [[nodiscard]] constexpr auto file_name() const noexcept { return file_; } + [[nodiscard]] constexpr auto line() const noexcept { return line_; } + + private: + const char* file_{"unknown"}; + int line_{}; +}; +#endif + +template +[[nodiscard]] constexpr auto type_name() -> std::string_view { +#if defined(_MSC_VER) and not defined(__clang__) + return {&__FUNCSIG__[120], sizeof(__FUNCSIG__) - 128}; +#elif defined(__clang_analyzer__) + return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; +#elif defined(__clang__) and (__clang_major__ >= 13) and defined(__APPLE__) + return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; +#elif defined(__clang__) and (__clang_major__ >= 12) and not defined(__APPLE__) + return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; +#elif defined(__clang__) + return {&__PRETTY_FUNCTION__[70], sizeof(__PRETTY_FUNCTION__) - 72}; +#elif defined(__GNUC__) + return {&__PRETTY_FUNCTION__[85], sizeof(__PRETTY_FUNCTION__) - 136}; +#endif +} +} // namespace reflection + +namespace math { +template +[[nodiscard]] constexpr auto abs(const T t) -> T { + return t < T{} ? -t : t; +} + +template +[[nodiscard]] constexpr auto min_value(const T& lhs, const T& rhs) -> const T& { + return (rhs < lhs) ? rhs : lhs; +} + +template +[[nodiscard]] constexpr auto pow(const T base, const TExp exp) -> T { + return exp ? T(base * pow(base, exp - TExp(1))) : T(1); +} + +template +[[nodiscard]] constexpr auto num() -> T { + static_assert( + ((Cs == '.' or Cs == '\'' or (Cs >= '0' and Cs <= '9')) and ...)); + T result{}; + for (const char c : {Cs...}) { + if (c == '.') { + break; + } + if (c >= '0' and c <= '9') { + result = result * T(10) + T(c - '0'); + } + } + return result; +} + +template +[[nodiscard]] constexpr auto den() -> T { + constexpr const std::array cs{Cs...}; + T result{}; + auto i = 0u; + while (cs[i++] != '.') { + } + + for (auto j = i; j < sizeof...(Cs); ++j) { + result += pow(T(10), sizeof...(Cs) - j) * T(cs[j] - '0'); + } + return result; +} + +template +[[nodiscard]] constexpr auto den_size() -> T { + constexpr const std::array cs{Cs...}; + T i{}; + while (cs[i++] != '.') { + } + + return T(sizeof...(Cs)) - i + T(1); +} + +template +[[nodiscard]] constexpr auto den_size(TValue value) -> T { + constexpr auto precision = TValue(1e-7); + T result{}; + TValue tmp{}; + do { + value *= 10; + tmp = value - T(value); + ++result; + } while (tmp > precision); + + return result; +} + +} // namespace math + +namespace type_traits { +template +struct list {}; + +template +struct identity { + using type = T; +}; + +template +struct function_traits : function_traits {}; + +template +struct function_traits { + using result_type = R; + using args = list; +}; + +template +struct function_traits { + using result_type = R; + using args = list; +}; + +template +struct function_traits { + using result_type = R; + using args = list; +}; + +template +struct function_traits { + using result_type = R; + using args = list; +}; + +template +T&& declval(); +template +constexpr auto is_valid(TExpr expr) + -> decltype(expr(declval()), bool()) { + return true; +} +template +constexpr auto is_valid(...) -> bool { + return false; +} + +template +static constexpr auto is_container_v = + is_valid([](auto t) -> decltype(t.begin(), t.end(), void()) {}); + +template +static constexpr auto has_user_print = + is_valid([](auto t) -> decltype(void(declval() << t)) {}); + +template +static constexpr auto has_value_v = + is_valid([](auto t) -> decltype(void(t.value)) {}); + +template +static constexpr auto has_epsilon_v = + is_valid([](auto t) -> decltype(void(t.epsilon)) {}); + +template +inline constexpr auto is_floating_point_v = false; +template <> +inline constexpr auto is_floating_point_v = true; +template <> +inline constexpr auto is_floating_point_v = true; +template <> +inline constexpr auto is_floating_point_v = true; + +#if defined(__clang__) or defined(_MSC_VER) +template +static constexpr auto is_convertible_v = __is_convertible_to(From, To); +#else +template +constexpr auto is_convertible(int) -> decltype(bool(To(declval()))) { + return true; +} +template +constexpr auto is_convertible(...) { + return false; +} +template +constexpr auto is_convertible_v = is_convertible(0); +#endif + +template +struct requires_ {}; +template <> +struct requires_ { + using type = int; +}; + +template +using requires_t = typename requires_::type; +} // namespace type_traits + +struct none {}; + +namespace events { +struct test_begin { + std::string_view type{}; + std::string_view name{}; + reflection::source_location location{}; +}; +template +struct test { + std::string_view type{}; + std::string_view name{}; + std::vector tag{}; + reflection::source_location location{}; + TArg arg{}; + Test run{}; + + constexpr auto operator()() { run_impl(static_cast(run), arg); } + constexpr auto operator()() const { run_impl(static_cast(run), arg); } + + private: + static constexpr auto run_impl(Test test, const none&) { test(); } + + template + static constexpr auto run_impl(T test, const TArg& arg) + -> decltype(test(arg), void()) { + test(arg); + } + + template + static constexpr auto run_impl(T test, const TArg&) + -> decltype(test.template operator()(), void()) { + test.template operator()(); + } +}; +template +test(std::string_view, std::string_view, std::string_view, + reflection::source_location, TArg, Test) -> test; +template +struct suite { + TSuite run{}; + constexpr auto operator()() { run(); } + constexpr auto operator()() const { run(); } +}; +template +suite(TSuite) -> suite; +struct test_run { + std::string_view type{}; + std::string_view name{}; +}; +template +struct skip { + std::string_view type{}; + std::string_view name{}; + TArg arg{}; +}; +template +skip(std::string_view, std::string_view, TArg) -> skip; +struct test_skip { + std::string_view type{}; + std::string_view name{}; +}; +template +struct assertion { + TExpr expr{}; + reflection::source_location location{}; +}; +template +assertion(TExpr, reflection::source_location) -> assertion; +template +struct assertion_pass { + TExpr expr{}; + reflection::source_location location{}; +}; +template +assertion_pass(TExpr) -> assertion_pass; +template +struct assertion_fail { + TExpr expr{}; + reflection::source_location location{}; +}; +template +assertion_fail(TExpr) -> assertion_fail; +struct test_end { + std::string_view type{}; + std::string_view name{}; +}; +template +struct log { + TMsg msg{}; +}; +template +log(TMsg) -> log; +struct fatal_assertion {}; +struct exception { + const char* msg{}; + [[nodiscard]] auto what() const -> const char* { return msg; } +}; +struct summary {}; +} // namespace events + +namespace detail { +struct op {}; +struct fatal {}; +struct cfg { + static inline reflection::source_location location{}; + static inline bool wip{}; +}; + +template +[[nodiscard]] constexpr auto get_impl(const T& t, int) -> decltype(t.get()) { + return t.get(); +} +template +[[nodiscard]] constexpr auto get_impl(const T& t, ...) -> decltype(auto) { + return t; +} +template +[[nodiscard]] constexpr auto get(const T& t) { + return get_impl(t, 0); +} + +template +struct type_ : op { + template + [[nodiscard]] constexpr auto operator()(const TOther&) const + -> const type_ { + return {}; + } + [[nodiscard]] constexpr auto operator==(type_) -> bool { return true; } + template + [[nodiscard]] constexpr auto operator==(type_) -> bool { + return false; + } + template + [[nodiscard]] constexpr auto operator==(const TOther&) -> bool { + return std::is_same_v; + } + [[nodiscard]] constexpr auto operator!=(type_) -> bool { return true; } + template + [[nodiscard]] constexpr auto operator!=(type_) -> bool { + return true; + } + template + [[nodiscard]] constexpr auto operator!=(const TOther&) -> bool { + return not std::is_same_v; + } +}; + +template +struct value : op { + using value_type = T; + + constexpr /*explicit(false)*/ value(const T& _value) : value_{_value} {} + [[nodiscard]] constexpr explicit operator T() const { return value_; } + [[nodiscard]] constexpr decltype(auto) get() const { return value_; } + + T value_{}; +}; + +template +struct value>> + : op { + using value_type = T; + static inline auto epsilon = T{}; + + constexpr value(const T& _value, const T precision) : value_{_value} { + epsilon = precision; + } + + constexpr /*explicit(false)*/ value(const T& val) + : value{val, T(1) / math::pow(T(10), + math::den_size(val))} {} + [[nodiscard]] constexpr explicit operator T() const { return value_; } + [[nodiscard]] constexpr decltype(auto) get() const { return value_; } + + T value_{}; +}; + +template +class value_location : public detail::value { + public: + constexpr /*explicit(false)*/ value_location( + const T& t, const reflection::source_location& sl = + reflection::source_location::current()) + : detail::value{t} { + cfg::location = sl; + } + + constexpr value_location(const T& t, const T precision, + const reflection::source_location& sl = + reflection::source_location::current()) + : detail::value{t, precision} { + cfg::location = sl; + } +}; + +template +struct integral_constant : op { + using value_type = decltype(N); + static constexpr auto value = N; + + [[nodiscard]] constexpr auto operator-() const { + return integral_constant<-N>{}; + } + [[nodiscard]] constexpr explicit operator value_type() const { return N; } + [[nodiscard]] constexpr auto get() const { return N; } +}; + +template +struct floating_point_constant : op { + using value_type = T; + + static constexpr auto epsilon = T(1) / math::pow(T(10), Size - 1); + static constexpr auto value = T(P) * (T(N) + (T(D) / math::pow(T(10), Size))); + + [[nodiscard]] constexpr auto operator-() const { + return floating_point_constant{}; + } + [[nodiscard]] constexpr explicit operator value_type() const { return value; } + [[nodiscard]] constexpr auto get() const { return value; } +}; + +template +struct eq_ : op { + constexpr eq_(const TLhs& lhs = {}, const TRhs& rhs = {}) + : lhs_{lhs}, rhs_{rhs}, value_{[&] { + using std::operator==; + using std::operator<; + + if constexpr (type_traits::has_value_v and + type_traits::has_value_v) { + return TLhs::value == TRhs::value; + } else if constexpr (type_traits::has_epsilon_v and + type_traits::has_epsilon_v) { + return math::abs(get(lhs) - get(rhs)) < + math::min_value(TLhs::epsilon, TRhs::epsilon); + } else if constexpr (type_traits::has_epsilon_v) { + return math::abs(get(lhs) - get(rhs)) < TLhs::epsilon; + } else if constexpr (type_traits::has_epsilon_v) { + return math::abs(get(lhs) - get(rhs)) < TRhs::epsilon; + } else { + return get(lhs) == get(rhs); + } + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } + [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } + + const TLhs lhs_{}; + const TRhs rhs_{}; + const bool value_{}; +}; + +template +struct neq_ : op { + constexpr neq_(const TLhs& lhs = {}, const TRhs& rhs = {}) + : lhs_{lhs}, rhs_{rhs}, value_{[&] { + using std::operator==; + using std::operator!=; + using std::operator>; + + if constexpr (type_traits::has_value_v and + type_traits::has_value_v) { + return TLhs::value != TRhs::value; + } else if constexpr (type_traits::has_epsilon_v and + type_traits::has_epsilon_v) { + return math::abs(get(lhs_) - get(rhs_)) > + math::min_value(TLhs::epsilon, TRhs::epsilon); + } else if constexpr (type_traits::has_epsilon_v) { + return math::abs(get(lhs_) - get(rhs_)) > TLhs::epsilon; + } else if constexpr (type_traits::has_epsilon_v) { + return math::abs(get(lhs_) - get(rhs_)) > TRhs::epsilon; + } else { + return get(lhs_) != get(rhs_); + } + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } + [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } + + const TLhs lhs_{}; + const TRhs rhs_{}; + const bool value_{}; +}; + +template +struct gt_ : op { + constexpr gt_(const TLhs& lhs = {}, const TRhs& rhs = {}) + : lhs_{lhs}, rhs_{rhs}, value_{[&] { + using std::operator>; + + if constexpr (type_traits::has_value_v and + type_traits::has_value_v) { + return TLhs::value > TRhs::value; + } else { + return get(lhs_) > get(rhs_); + } + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } + [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } + + const TLhs lhs_{}; + const TRhs rhs_{}; + const bool value_{}; +}; + +template +struct ge_ : op { + constexpr ge_(const TLhs& lhs = {}, const TRhs& rhs = {}) + : lhs_{lhs}, rhs_{rhs}, value_{[&] { + using std::operator>=; + + if constexpr (type_traits::has_value_v and + type_traits::has_value_v) { + return TLhs::value >= TRhs::value; + } else { + return get(lhs_) >= get(rhs_); + } + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } + [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } + + const TLhs lhs_{}; + const TRhs rhs_{}; + const bool value_{}; +}; + +template +struct lt_ : op { + constexpr lt_(const TLhs& lhs = {}, const TRhs& rhs = {}) + : lhs_{lhs}, rhs_{rhs}, value_{[&] { + using std::operator<; + + if constexpr (type_traits::has_value_v and + type_traits::has_value_v) { + return TLhs::value < TRhs::value; + } else { + return get(lhs_) < get(rhs_); + } + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } + [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } + + private: + const TLhs lhs_{}; + const TRhs rhs_{}; + const bool value_{}; +}; + +template +struct le_ : op { + constexpr le_(const TLhs& lhs = {}, const TRhs& rhs = {}) + : lhs_{lhs}, rhs_{rhs}, value_{[&] { + using std::operator<=; + + if constexpr (type_traits::has_value_v and + type_traits::has_value_v) { + return TLhs::value <= TRhs::value; + } else { + return get(lhs_) <= get(rhs_); + } + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } + [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } + + const TLhs lhs_{}; + const TRhs rhs_{}; + const bool value_{}; +}; + +template +struct and_ : op { + constexpr and_(const TLhs& lhs = {}, const TRhs& rhs = {}) + : lhs_{lhs}, + rhs_{rhs}, + value_{static_cast(lhs) and static_cast(rhs)} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } + [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } + + const TLhs lhs_{}; + const TRhs rhs_{}; + const bool value_{}; +}; + +template +struct or_ : op { + constexpr or_(const TLhs& lhs = {}, const TRhs& rhs = {}) + : lhs_{lhs}, + rhs_{rhs}, + value_{static_cast(lhs) or static_cast(rhs)} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } + [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } + + const TLhs lhs_{}; + const TRhs rhs_{}; + const bool value_{}; +}; + +template +struct not_ : op { + explicit constexpr not_(const T& t = {}) + : t_{t}, value_{not static_cast(t)} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + [[nodiscard]] constexpr auto value() const { return get(t_); } + + const T t_{}; + const bool value_{}; +}; + +template +struct fatal_; + +#if defined(__cpp_exceptions) +template +struct throws_ : op { + constexpr explicit throws_(const TExpr& expr) + : value_{[&expr] { + try { + expr(); + } catch (const TException&) { + return true; + } catch (...) { + return false; + } + return false; + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + + const bool value_{}; +}; + +template +struct throws_ : op { + constexpr explicit throws_(const TExpr& expr) + : value_{[&expr] { + try { + expr(); + } catch (...) { + return true; + } + return false; + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + + const bool value_{}; +}; + +template +struct nothrow_ : op { + constexpr explicit nothrow_(const TExpr& expr) + : value_{[&expr] { + try { + expr(); + } catch (...) { + return false; + } + return true; + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + + const bool value_{}; +}; +#endif + +#if __has_include() and __has_include() +template +struct aborts_ : op { + constexpr explicit aborts_(const TExpr& expr) + : value_{[&expr]() -> bool { + if (const auto pid = fork(); not pid) { + expr(); + exit(0); + } + auto exit_status = 0; + wait(&exit_status); + return exit_status; + }()} {} + + [[nodiscard]] constexpr operator bool() const { return value_; } + + const bool value_{}; +}; +#endif +} // namespace detail + +namespace type_traits { +template +inline constexpr auto is_op_v = __is_base_of(detail::op, T); +} // namespace type_traits + +struct colors { + std::string_view none = "\033[0m"; + std::string_view pass = "\033[32m"; + std::string_view fail = "\033[31m"; +}; + +class printer { + [[nodiscard]] inline auto color(const bool cond) { + return cond ? colors_.pass : colors_.fail; + } + + public: + printer() = default; + /*explicit(false)*/ printer(const colors colors) : colors_{colors} {} + + template + auto& operator<<(const T& t) { + out_ << detail::get(t); + return *this; + } + + template and + type_traits::is_container_v> = 0> + auto& operator<<(T&& t) { + *this << '{'; + auto first = true; + for (const auto& arg : t) { + *this << (first ? "" : ", ") << arg; + first = false; + } + *this << '}'; + return *this; + } + + auto& operator<<(std::string_view sv) { + out_ << sv; + return *this; + } + + template + auto& operator<<(const detail::eq_& op) { + return (*this << color(op) << op.lhs() << " == " << op.rhs() + << colors_.none); + } + + template + auto& operator<<(const detail::neq_& op) { + return (*this << color(op) << op.lhs() << " != " << op.rhs() + << colors_.none); + } + + template + auto& operator<<(const detail::gt_& op) { + return (*this << color(op) << op.lhs() << " > " << op.rhs() + << colors_.none); + } + + template + auto& operator<<(const detail::ge_& op) { + return (*this << color(op) << op.lhs() << " >= " << op.rhs() + << colors_.none); + } + + template + auto& operator<<(const detail::lt_& op) { + return (*this << color(op) << op.lhs() << " < " << op.rhs() + << colors_.none); + } + + template + auto& operator<<(const detail::le_& op) { + return (*this << color(op) << op.lhs() << " <= " << op.rhs() + << colors_.none); + } + + template + auto& operator<<(const detail::and_& op) { + return (*this << '(' << op.lhs() << color(op) << " and " << colors_.none + << op.rhs() << ')'); + } + + template + auto& operator<<(const detail::or_& op) { + return (*this << '(' << op.lhs() << color(op) << " or " << colors_.none + << op.rhs() << ')'); + } + + template + auto& operator<<(const detail::not_& op) { + return (*this << color(op) << "not " << op.value() << colors_.none); + } + + template + auto& operator<<(const detail::fatal_& fatal) { + return (*this << fatal.get()); + } + +#if defined(__cpp_exceptions) + template + auto& operator<<(const detail::throws_& op) { + return (*this << color(op) << "throws<" + << reflection::type_name() << ">" + << colors_.none); + } + + template + auto& operator<<(const detail::throws_& op) { + return (*this << color(op) << "throws" << colors_.none); + } + + template + auto& operator<<(const detail::nothrow_& op) { + return (*this << color(op) << "nothrow" << colors_.none); + } +#endif + +#if __has_include() and __has_include() + template + auto& operator<<(const detail::aborts_& op) { + return (*this << color(op) << "aborts" << colors_.none); + } +#endif + + template + auto& operator<<(const detail::type_&) { + return (*this << reflection::type_name()); + } + + auto str() const { return out_.str(); } + const auto& colors() const { return colors_; } + + private: + ut::colors colors_{}; + std::ostringstream out_{}; +}; + +template +class reporter { + public: + constexpr auto operator=(TPrinter printer) { + printer_ = static_cast(printer); + } + + auto on(events::test_begin test_begin) -> void { + printer_ << "Running \"" << test_begin.name << "\"..."; + fails_ = asserts_.fail; + } + + auto on(events::test_run test_run) -> void { + printer_ << "\n \"" << test_run.name << "\"..."; + } + + auto on(events::test_skip test_skip) -> void { + printer_ << test_skip.name << "...SKIPPED\n"; + ++tests_.skip; + } + + auto on(events::test_end) -> void { + if (asserts_.fail > fails_) { + ++tests_.fail; + printer_ << '\n' + << printer_.colors().fail << "FAILED" << printer_.colors().none + << '\n'; + } else { + ++tests_.pass; + printer_ << printer_.colors().pass << "PASSED" << printer_.colors().none + << '\n'; + } + } + + template + auto on(events::log l) -> void { + printer_ << l.msg; + } + + auto on(events::exception exception) -> void { + printer_ << "\n " << printer_.colors().fail + << "Unexpected exception with message:\n" + << exception.what() << printer_.colors().none; + ++asserts_.fail; + } + + template + auto on(events::assertion_pass) -> void { + ++asserts_.pass; + } + + template + auto on(events::assertion_fail assertion) -> void { + constexpr auto short_name = [](std::string_view name) { + return name.rfind('/') != std::string_view::npos + ? name.substr(name.rfind('/') + 1) + : name; + }; + printer_ << "\n " << short_name(assertion.location.file_name()) << ':' + << assertion.location.line() << ':' << printer_.colors().fail + << "FAILED" << printer_.colors().none << " [" << std::boolalpha + << assertion.expr << printer_.colors().none << ']'; + ++asserts_.fail; + } + + auto on(events::fatal_assertion) -> void {} + + auto on(events::summary) -> void { + if (static auto once = true; once) { + once = false; + if (tests_.fail or asserts_.fail) { + printer_ << "\n========================================================" + "=======================\n" + << "tests: " << (tests_.pass + tests_.fail) << " | " + << printer_.colors().fail << tests_.fail << " failed" + << printer_.colors().none << '\n' + << "asserts: " << (asserts_.pass + asserts_.fail) << " | " + << asserts_.pass << " passed" + << " | " << printer_.colors().fail << asserts_.fail + << " failed" << printer_.colors().none << '\n'; + std::cerr << printer_.str() << std::endl; + } else { + std::cout << printer_.colors().pass << "All tests passed" + << printer_.colors().none << " (" << asserts_.pass + << " asserts in " << tests_.pass << " tests)\n"; + + if (tests_.skip) { + std::cout << tests_.skip << " tests skipped\n"; + } + + std::cout.flush(); + } + } + } + + protected: + struct { + std::size_t pass{}; + std::size_t fail{}; + std::size_t skip{}; + } tests_{}; + + struct { + std::size_t pass{}; + std::size_t fail{}; + } asserts_{}; + + std::size_t fails_{}; + + TPrinter printer_{}; +}; + +struct options { + std::string_view filter{}; + std::vector tag{}; + ut::colors colors{}; + bool dry_run{}; +}; + +struct run_cfg { + bool report_errors{false}; +}; + +template , auto MaxPathSize = 16> +class runner { + class filter { + static constexpr auto delim = "."; + + public: + constexpr /*explicit(false)*/ filter(std::string_view _filter = {}) + : path_{utility::split(_filter, delim)} {} + + template + constexpr auto operator()(const std::size_t level, const TPath& path) const + -> bool { + for (auto i = 0u; i < math::min_value(level + 1, std::size(path_)); ++i) { + if (not utility::is_match(path[i], path_[i])) { + return false; + } + } + return true; + } + + private: + std::vector path_{}; + }; + + public: + constexpr runner() = default; + constexpr runner(TReporter reporter, std::size_t suites_size) + : reporter_{std::move(reporter)}, suites_(suites_size) {} + + ~runner() { + const auto should_run = not run_; + + if (should_run) { + static_cast(run()); + } + + if (not dry_run_) { + reporter_.on(events::summary{}); + } + + if (should_run and fails_) { + std::exit(-1); + } + } + + auto operator=(const options& options) { + filter_ = options.filter; + tag_ = options.tag; + dry_run_ = options.dry_run; + reporter_ = {options.colors}; + } + + template + auto on(events::suite suite) { + suites_.push_back(suite.run); + } + + template + auto on(events::test test) { + path_[level_] = test.name; + + auto execute = std::empty(test.tag); + for (const auto& tag_element : test.tag) { + if (utility::is_match(tag_element, "skip")) { + on(events::skip<>{.type = test.type, .name = test.name}); + return; + } + + for (const auto& ftag : tag_) { + if (utility::is_match(tag_element, ftag)) { + execute = true; + break; + } + } + } + + if (not execute) { + on(events::skip<>{.type = test.type, .name = test.name}); + return; + } + + if (filter_(level_, path_)) { + if (not level_++) { + reporter_.on(events::test_begin{ + .type = test.type, .name = test.name, .location = test.location}); + } else { + reporter_.on(events::test_run{.type = test.type, .name = test.name}); + } + + if (dry_run_) { + for (auto i = 0u; i < level_; ++i) { + std::cout << (i ? "." : "") << path_[i]; + } + std::cout << '\n'; + } + +#if defined(__cpp_exceptions) + try { +#endif + test(); +#if defined(__cpp_exceptions) + } catch (const events::fatal_assertion&) { + } catch (const std::exception& exception) { + ++fails_; + reporter_.on(events::exception{exception.what()}); + } catch (...) { + ++fails_; + reporter_.on(events::exception{"Unknown exception"}); + } +#endif + + if (not --level_) { + reporter_.on(events::test_end{.type = test.type, .name = test.name}); + } + } + } + + template + auto on(events::skip test) { + reporter_.on(events::test_skip{.type = test.type, .name = test.name}); + } + + template + [[nodiscard]] auto on(events::assertion assertion) -> bool { + if (dry_run_) { + return true; + } + + if (static_cast(assertion.expr)) { + reporter_.on(events::assertion_pass{ + .expr = assertion.expr, .location = assertion.location}); + return true; + } + + ++fails_; + reporter_.on(events::assertion_fail{.expr = assertion.expr, + .location = assertion.location}); + return false; + } + + auto on(events::fatal_assertion fatal_assertion) { + reporter_.on(fatal_assertion); + +#if defined(__cpp_exceptions) + if (not level_) { + reporter_.on(events::summary{}); + } + throw fatal_assertion; +#else + if (level_) { + reporter_.on(events::test_end{}); + } + reporter_.on(events::summary{}); + std::abort(); +#endif + } + + template + auto on(events::log l) { + reporter_.on(l); + } + + [[nodiscard]] auto run(run_cfg rc = {}) -> bool { + run_ = true; + for (const auto& suite : suites_) { + suite(); + } + suites_.clear(); + + if (rc.report_errors) { + reporter_.on(events::summary{}); + } + + return fails_ > 0; + } + + protected: + TReporter reporter_{}; + std::vector suites_{}; + std::size_t level_{}; + bool run_{}; + std::size_t fails_{}; + std::array path_{}; + filter filter_{}; + std::vector tag_{}; + bool dry_run_{}; +}; + +struct override {}; + +template +[[maybe_unused]] inline auto cfg = runner>{}; + +namespace detail { +struct tag { + std::vector name{}; +}; + +template +[[nodiscard]] constexpr decltype(auto) on(TEvent&& event) { + return ut::cfg::type>.on( + static_cast(event)); +} + +template +struct test_location { + template + constexpr test_location(const T& t, + const reflection::source_location& sl = + reflection::source_location::current()) + : test{t}, location{sl} {} + + Test test{}; + reflection::source_location location{}; +}; + +struct test { + std::string_view type{}; + std::string_view name{}; + std::vector tag{}; + + template + constexpr auto operator=(test_location _test) { + on(events::test{.type = type, + .name = name, + .tag = tag, + .location = _test.location, + .arg = none{}, + .run = _test.test}); + return _test.test; + } + + template > = 0> + constexpr auto operator=(Test _test) -> + typename type_traits::identity::type { + on(events::test{.type = type, + .name = name, + .tag = tag, + .location = {}, + .arg = none{}, + .run = static_cast(_test)}); + return _test; + } + + constexpr auto operator=(void (*_test)(std::string_view)) const { + return _test(name); + } + + template > = 0> + constexpr auto operator=(Test _test) + -> decltype(_test(type_traits::declval())) { + return _test(name); + } +}; + +struct log { + struct next { + template + auto& operator<<(const TMsg& msg) { + on(events::log{' '}); + on(events::log{msg}); + return *this; + } + }; + + template + auto operator<<(const TMsg& msg) -> next { + on(events::log{'\n'}); + on(events::log{msg}); + return next{}; + } +}; + +template +class terse_ { + public: + constexpr explicit terse_(const TExpr& expr) : expr_{expr} { cfg::wip = {}; } + + ~terse_() noexcept(false) { + if (static auto once = true; once and not cfg::wip) { + once = {}; + } else { + return; + } + + cfg::wip = true; + + void(detail::on( + events::assertion{.expr = expr_, .location = cfg::location})); + } + + private: + const TExpr& expr_; +}; + +struct that_ { + template + struct expr { + using type = expr; + + constexpr explicit expr(const T& t) : t_{t} {} + + [[nodiscard]] constexpr auto operator!() const { return not_{*this}; } + + template + [[nodiscard]] constexpr auto operator==(const TRhs& rhs) const { + return eq_{t_, rhs}; + } + + template + [[nodiscard]] constexpr auto operator!=(const TRhs& rhs) const { + return neq_{t_, rhs}; + } + + template + [[nodiscard]] constexpr auto operator>(const TRhs& rhs) const { + return gt_{t_, rhs}; + } + + template + [[nodiscard]] constexpr auto operator>=(const TRhs& rhs) const { + return ge_{t_, rhs}; + } + + template + [[nodiscard]] constexpr auto operator<(const TRhs& rhs) const { + return lt_{t_, rhs}; + } + + template + [[nodiscard]] constexpr auto operator<=(const TRhs& rhs) const { + return le_{t_, rhs}; + } + + [[nodiscard]] constexpr operator bool() const { + return static_cast(t_); + } + + const T t_{}; + }; + + template + [[nodiscard]] constexpr auto operator%(const T& t) const { + return expr{t}; + } +}; + +template +struct fatal_ : op { + using type = fatal_; + + constexpr explicit fatal_(const TExpr& expr) : expr_{expr} {} + + [[nodiscard]] constexpr operator bool() const { + if (static_cast(expr_)) { + } else { + cfg::wip = true; + void(on( + events::assertion{.expr = expr_, .location = cfg::location})); + on(events::fatal_assertion{}); + } + return static_cast(expr_); + } + + [[nodiscard]] constexpr decltype(auto) get() const { return expr_; } + + TExpr expr_{}; +}; + +template +struct expect_ { + constexpr explicit expect_(bool value) : value_{value} { cfg::wip = {}; } + + template + auto& operator<<(const TMsg& msg) { + if (not value_) { + on(events::log{' '}); + on(events::log{msg}); + } + return *this; + } + + [[nodiscard]] constexpr operator bool() const { return value_; } + + bool value_{}; +}; +} // namespace detail + +namespace literals { +[[nodiscard]] inline auto operator""_test(const char* name, + decltype(sizeof("")) size) { + return detail::test{"test", std::string_view{name, size}}; +} + +template +[[nodiscard]] constexpr auto operator""_i() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_s() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_c() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_sc() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_l() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_ll() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_u() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_uc() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_us() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_ul() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_ull() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_i8() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_i16() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_i32() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_i64() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_u8() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_u16() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_u32() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_u64() { + return detail::integral_constant()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_f() { + return detail::floating_point_constant< + float, math::num(), + math::den(), + math::den_size()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_d() { + return detail::floating_point_constant< + double, math::num(), + math::den(), + math::den_size()>{}; +} + +template +[[nodiscard]] constexpr auto operator""_ld() { + return detail::floating_point_constant< + long double, math::num(), + math::den(), + math::den_size()>{}; +} + +constexpr auto operator""_b(const char* name, decltype(sizeof("")) size) { + struct named : std::string_view, detail::op { + using value_type = bool; + [[nodiscard]] constexpr operator value_type() const { return true; } + [[nodiscard]] constexpr auto operator==(const named&) const { return true; } + [[nodiscard]] constexpr auto operator==(const bool other) const { + return other; + } + }; + return named{{name, size}, {}}; +} +} // namespace literals + +namespace operators { +[[nodiscard]] constexpr auto operator==(std::string_view lhs, + std::string_view rhs) { + return detail::eq_{lhs, rhs}; +} + +[[nodiscard]] constexpr auto operator!=(std::string_view lhs, + std::string_view rhs) { + return detail::neq_{lhs, rhs}; +} + +template > = 0> +[[nodiscard]] constexpr auto operator==(T&& lhs, T&& rhs) { + return detail::eq_{static_cast(lhs), static_cast(rhs)}; +} + +template > = 0> +[[nodiscard]] constexpr auto operator!=(T&& lhs, T&& rhs) { + return detail::neq_{static_cast(lhs), static_cast(rhs)}; +} + +template or + type_traits::is_op_v> = 0> +[[nodiscard]] constexpr auto operator==(const TLhs& lhs, const TRhs& rhs) { + return detail::eq_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +[[nodiscard]] constexpr auto operator!=(const TLhs& lhs, const TRhs& rhs) { + return detail::neq_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +[[nodiscard]] constexpr auto operator>(const TLhs& lhs, const TRhs& rhs) { + return detail::gt_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +[[nodiscard]] constexpr auto operator>=(const TLhs& lhs, const TRhs& rhs) { + return detail::ge_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +[[nodiscard]] constexpr auto operator<(const TLhs& lhs, const TRhs& rhs) { + return detail::lt_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +[[nodiscard]] constexpr auto operator<=(const TLhs& lhs, const TRhs& rhs) { + return detail::le_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +[[nodiscard]] constexpr auto operator and(const TLhs& lhs, const TRhs& rhs) { + return detail::and_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +[[nodiscard]] constexpr auto operator or(const TLhs& lhs, const TRhs& rhs) { + return detail::or_{lhs, rhs}; +} + +template > = 0> +[[nodiscard]] constexpr auto operator not(const T& t) { + return detail::not_{t}; +} + +template +[[nodiscard]] inline auto operator>>( + const T& t, const detail::value_location&) { + return detail::fatal_{t}; +} + +template +[[nodiscard]] auto operator/(const detail::tag& tag, Test test) { + for (const auto& name : tag.name) { + test.tag.push_back(name); + } + return test; +} + +[[nodiscard]] inline auto operator/(const detail::tag& lhs, + const detail::tag& rhs) { + std::vector tag{}; + for (const auto& name : lhs.name) { + tag.push_back(name); + } + for (const auto& name : rhs.name) { + tag.push_back(name); + } + return detail::tag{tag}; +} + +template > = 0> +[[nodiscard]] constexpr auto operator|(const F& f, const T& t) { + return [f, t](const auto name) { + for (const auto& arg : t) { + detail::on(events::test{.type = "test", + .name = name, + .tag = {}, + .location = {}, + .arg = arg, + .run = f}); + } + }; +} + +template < + class F, template class T, class... Ts, + type_traits::requires_t>> = 0> +[[nodiscard]] constexpr auto operator|(const F& f, const T& t) { + return [f, t](const auto name) { + apply( + [f, name](const auto&... args) { + (detail::on(events::test{.type = "test", + .name = name, + .tag = {}, + .location = {}, + .arg = args, + .run = f}), + ...); + }, + t); + }; +} + +namespace terse { +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wunused-comparison" +#endif + +[[maybe_unused]] constexpr struct { +} _t; + +template +constexpr auto operator%(const T& t, const decltype(_t)&) { + return detail::value{t}; +} + +template +inline auto operator>>(const T& t, + const detail::value_location&) { + using fatal_t = detail::fatal_; + struct fatal_ : fatal_t, detail::log { + using type [[maybe_unused]] = fatal_t; + using fatal_t::fatal_t; + const detail::terse_ _{*this}; + }; + return fatal_{t}; +} + +template > = 0> +constexpr auto operator==( + const T& lhs, const detail::value_location& rhs) { + using eq_t = detail::eq_>; + struct eq_ : eq_t, detail::log { + using type [[maybe_unused]] = eq_t; + using eq_t::eq_t; + const detail::terse_ _{*this}; + }; + return eq_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator==( + const detail::value_location& lhs, const T& rhs) { + using eq_t = detail::eq_, T>; + struct eq_ : eq_t, detail::log { + using type [[maybe_unused]] = eq_t; + using eq_t::eq_t; + const detail::terse_ _{*this}; + }; + return eq_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator!=( + const T& lhs, const detail::value_location& rhs) { + using neq_t = detail::neq_>; + struct neq_ : neq_t, detail::log { + using type [[maybe_unused]] = neq_t; + using neq_t::neq_t; + const detail::terse_ _{*this}; + }; + return neq_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator!=( + const detail::value_location& lhs, const T& rhs) { + using neq_t = detail::neq_, T>; + struct neq_ : neq_t { + using type [[maybe_unused]] = neq_t; + using neq_t::neq_t; + const detail::terse_ _{*this}; + }; + return neq_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator>( + const T& lhs, const detail::value_location& rhs) { + using gt_t = detail::gt_>; + struct gt_ : gt_t, detail::log { + using type [[maybe_unused]] = gt_t; + using gt_t::gt_t; + const detail::terse_ _{*this}; + }; + return gt_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator>( + const detail::value_location& lhs, const T& rhs) { + using gt_t = detail::gt_, T>; + struct gt_ : gt_t, detail::log { + using type [[maybe_unused]] = gt_t; + using gt_t::gt_t; + const detail::terse_ _{*this}; + }; + return gt_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator>=( + const T& lhs, const detail::value_location& rhs) { + using ge_t = detail::ge_>; + struct ge_ : ge_t, detail::log { + using type [[maybe_unused]] = ge_t; + using ge_t::ge_t; + const detail::terse_ _{*this}; + }; + return ge_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator>=( + const detail::value_location& lhs, const T& rhs) { + using ge_t = detail::ge_, T>; + struct ge_ : ge_t, detail::log { + using type [[maybe_unused]] = ge_t; + using ge_t::ge_t; + const detail::terse_ _{*this}; + }; + return ge_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator<( + const T& lhs, const detail::value_location& rhs) { + using lt_t = detail::lt_>; + struct lt_ : lt_t, detail::log { + using type [[maybe_unused]] = lt_t; + using lt_t::lt_t; + const detail::terse_ _{*this}; + }; + return lt_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator<( + const detail::value_location& lhs, const T& rhs) { + using lt_t = detail::lt_, T>; + struct lt_ : lt_t, detail::log { + using type [[maybe_unused]] = lt_t; + using lt_t::lt_t; + const detail::terse_ _{*this}; + }; + return lt_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator<=( + const T& lhs, const detail::value_location& rhs) { + using le_t = detail::le_>; + struct le_ : le_t, detail::log { + using type [[maybe_unused]] = le_t; + using le_t::le_t; + const detail::terse_ _{*this}; + }; + return le_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator<=( + const detail::value_location& lhs, const T& rhs) { + using le_t = detail::le_, T>; + struct le_ : le_t { + using type [[maybe_unused]] = le_t; + using le_t::le_t; + const detail::terse_ _{*this}; + }; + return le_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +constexpr auto operator and(const TLhs& lhs, const TRhs& rhs) { + using and_t = detail::and_; + struct and_ : and_t, detail::log { + using type [[maybe_unused]] = and_t; + using and_t::and_t; + const detail::terse_ _{*this}; + }; + return and_{lhs, rhs}; +} + +template or + type_traits::is_op_v> = 0> +constexpr auto operator or(const TLhs& lhs, const TRhs& rhs) { + using or_t = detail::or_; + struct or_ : or_t, detail::log { + using type [[maybe_unused]] = or_t; + using or_t::or_t; + const detail::terse_ _{*this}; + }; + return or_{lhs, rhs}; +} + +template > = 0> +constexpr auto operator not(const T& t) { + using not_t = detail::not_; + struct not_ : not_t, detail::log { + using type [[maybe_unused]] = not_t; + using not_t::not_t; + const detail::terse_ _{*this}; + }; + return not_{t}; +} + +} // namespace terse +} // namespace operators + +template or + type_traits::is_convertible_v> = 0> +constexpr auto expect(const TExpr& expr, + const reflection::source_location& sl = + reflection::source_location::current()) { + return detail::expect_{detail::on( + events::assertion{.expr = expr, .location = sl})}; +} + +[[maybe_unused]] constexpr auto fatal = detail::fatal{}; + +#if defined(__cpp_nontype_template_parameter_class) +template +#else +template +#endif +constexpr auto constant = Constant; + +#if defined(__cpp_exceptions) +template +[[nodiscard]] constexpr auto throws(const TExpr& expr) { + return detail::throws_{expr}; +} + +template +[[nodiscard]] constexpr auto throws(const TExpr& expr) { + return detail::throws_{expr}; +} + +template +[[nodiscard]] constexpr auto nothrow(const TExpr& expr) { + return detail::nothrow_{expr}; +} +#endif + +#if __has_include() and __has_include() +template +[[nodiscard]] constexpr auto aborts(const TExpr& expr) { + return detail::aborts_{expr}; +} +#endif + +using _b = detail::value; +using _c = detail::value; +using _sc = detail::value; +using _s = detail::value; +using _i = detail::value; +using _l = detail::value; +using _ll = detail::value; +using _u = detail::value; +using _uc = detail::value; +using _us = detail::value; +using _ul = detail::value; +using _ull = detail::value; +using _i8 = detail::value; +using _i16 = detail::value; +using _i32 = detail::value; +using _i64 = detail::value; +using _u8 = detail::value; +using _u16 = detail::value; +using _u32 = detail::value; +using _u64 = detail::value; +using _f = detail::value; +using _d = detail::value; +using _ld = detail::value; + +template +struct _t : detail::value { + constexpr explicit _t(const T& t) : detail::value{t} {} +}; + +struct suite { + template + constexpr /*explicit(false)*/ suite(TSuite _suite) { + static_assert(1 == sizeof(_suite)); + detail::on( + events::suite{.run = +_suite}); + } +}; + +[[maybe_unused]] inline auto log = detail::log{}; +[[maybe_unused]] inline auto that = detail::that_{}; +[[maybe_unused]] constexpr auto test = [](const auto name) { + return detail::test{"test", name}; +}; +[[maybe_unused]] constexpr auto should = test; +[[maybe_unused]] inline auto tag = [](const auto name) { + return detail::tag{{name}}; +}; +[[maybe_unused]] inline auto skip = tag("skip"); +template +[[maybe_unused]] constexpr auto type = detail::type_(); + +template +[[nodiscard]] constexpr auto eq(const TLhs& lhs, const TRhs& rhs) { + return detail::eq_{lhs, rhs}; +} +template +[[nodiscard]] constexpr auto neq(const TLhs& lhs, const TRhs& rhs) { + return detail::neq_{lhs, rhs}; +} +template +[[nodiscard]] constexpr auto gt(const TLhs& lhs, const TRhs& rhs) { + return detail::gt_{lhs, rhs}; +} +template +[[nodiscard]] constexpr auto ge(const TLhs& lhs, const TRhs& rhs) { + return detail::ge_{lhs, rhs}; +} +template +[[nodiscard]] constexpr auto lt(const TLhs& lhs, const TRhs& rhs) { + return detail::lt_{lhs, rhs}; +} +template +[[nodiscard]] constexpr auto le(const TLhs& lhs, const TRhs& rhs) { + return detail::le_{lhs, rhs}; +} + +template +[[nodiscard]] constexpr auto mut(const T& t) noexcept -> T& { + return const_cast(t); +} + +namespace bdd { +[[maybe_unused]] constexpr auto feature = [](const auto name) { + return detail::test{"feature", name}; +}; +[[maybe_unused]] constexpr auto scenario = [](const auto name) { + return detail::test{"scenario", name}; +}; +[[maybe_unused]] constexpr auto given = [](const auto name) { + return detail::test{"given", name}; +}; +[[maybe_unused]] constexpr auto when = [](const auto name) { + return detail::test{"when", name}; +}; +[[maybe_unused]] constexpr auto then = [](const auto name) { + return detail::test{"then", name}; +}; + +namespace gherkin { +class steps { + using step_t = std::string; + using steps_t = void (*)(steps&); + using gherkin_t = std::vector; + using call_step_t = utility::function; + using call_steps_t = std::vector>; + + class step { + public: + template + step(steps& steps, const TPattern& pattern) + : steps_{steps}, pattern_{pattern} {} + + ~step() { steps_.next(pattern_); } + + template + auto operator=(const TExpr& expr) -> void { + for (const auto& [pattern, _] : steps_.call_steps()) { + if (pattern_ == pattern) { + return; + } + } + + steps_.call_steps().emplace_back( + pattern_, [expr, pattern = pattern_](const auto& _step) { + [=](type_traits::list) { + log << _step; + auto i = 0u; + const auto& ms = utility::match(pattern, _step); + expr(lexical_cast(ms[i++])...); + } + (typename type_traits::function_traits::args{}); + }); + } + + private: + template + static auto lexical_cast(const std::string& str) { + T t{}; + std::istringstream iss{}; + iss.str(str); + if constexpr (std::is_same_v) { + t = iss.str(); + } else { + iss >> t; + } + return t; + } + + steps& steps_; + std::string pattern_{}; + }; + + public: + template + constexpr /*explicit(false)*/ steps(const TSteps& _steps) : steps_{_steps} {} + + template + auto operator|(const TGherkin& gherkin) { + gherkin_ = utility::split(gherkin, '\n'); + for (auto& _step : gherkin_) { + _step.erase(0, _step.find_first_not_of(" \t")); + } + + return [this] { + step_ = {}; + steps_(*this); + }; + } + auto feature(const std::string& pattern) { + return step{*this, "Feature: " + pattern}; + } + auto scenario(const std::string& pattern) { + return step{*this, "Scenario: " + pattern}; + } + auto given(const std::string& pattern) { + return step{*this, "Given " + pattern}; + } + auto when(const std::string& pattern) { + return step{*this, "When " + pattern}; + } + auto then(const std::string& pattern) { + return step{*this, "Then " + pattern}; + } + + private: + template + auto next(const TPattern& pattern) -> void { + const auto is_scenario = [&pattern](const auto& _step) { + constexpr auto scenario = "Scenario"; + return pattern.find(scenario) == std::string::npos and + _step.find(scenario) != std::string::npos; + }; + + const auto call_steps = [this, is_scenario](const auto& _step, + const auto i) { + for (const auto& [name, call] : call_steps_) { + if (is_scenario(_step)) { + break; + } + + if (utility::is_match(_step, name) or + not std::empty(utility::match(name, _step))) { + step_ = i; + call(_step); + } + } + }; + + decltype(step_) i{}; + for (const auto& _step : gherkin_) { + if (i++ == step_) { + call_steps(_step, i); + } + } + } + + auto call_steps() -> call_steps_t& { return call_steps_; } + + steps_t steps_{}; + gherkin_t gherkin_{}; + call_steps_t call_steps_{}; + decltype(sizeof("")) step_{}; +}; +} // namespace gherkin +} // namespace bdd + +namespace spec { +[[maybe_unused]] constexpr auto describe = [](const auto name) { + return detail::test{"describe", name}; +}; +[[maybe_unused]] constexpr auto it = [](const auto name) { + return detail::test{"it", name}; +}; +} // namespace spec + +using literals::operator""_test; + +using literals::operator""_b; +using literals::operator""_i; +using literals::operator""_s; +using literals::operator""_c; +using literals::operator""_sc; +using literals::operator""_l; +using literals::operator""_ll; +using literals::operator""_u; +using literals::operator""_uc; +using literals::operator""_us; +using literals::operator""_ul; +using literals::operator""_i8; +using literals::operator""_i16; +using literals::operator""_i32; +using literals::operator""_i64; +using literals::operator""_u8; +using literals::operator""_u16; +using literals::operator""_u32; +using literals::operator""_u64; +using literals::operator""_f; +using literals::operator""_d; +using literals::operator""_ld; +using literals::operator""_ull; + +using operators::operator==; +using operators::operator!=; +using operators::operator>; +using operators::operator>=; +using operators::operator<; +using operators::operator<=; +using operators::operator and; +using operators::operator or; +using operators::operator not; +using operators::operator|; +using operators::operator/; +using operators::operator>>; +} // namespace boost::inline ext::ut::inline v1_1_9 +#endif diff --git a/src/errors.cc b/src/errors.cc new file mode 100644 index 0000000..4ff2a1b --- /dev/null +++ b/src/errors.cc @@ -0,0 +1,6 @@ +#include "musique.hh" + +std::ostream& operator<<(std::ostream& os, Error const&) +{ + return os << "generic error"; +} diff --git a/src/lexer.cc b/src/lexer.cc new file mode 100644 index 0000000..c4892c1 --- /dev/null +++ b/src/lexer.cc @@ -0,0 +1,12 @@ +#include "musique.hh" + +auto Lexer::next_token() -> Result +{ + return {}; +} + +std::ostream& operator<<(std::ostream& os, Token const&) +{ + os << "Token"; + return os; +} diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..483a36d --- /dev/null +++ b/src/main.cc @@ -0,0 +1,26 @@ +#include +#include "musique.hh" + +std::string_view Source = R"musique( + nums = [ 1 2 3 ] + say ( min nums + max nums ) +)musique"; + +tl::expected Main() +{ + Lexer lexer{Source}; + + for (;;) { + auto token = Try(lexer.next_token()); + std::cout << token << '\n'; + } +} + +int main() +{ + auto result = Main(); + if (not result.has_value()) { + std::cerr << result.error() << std::endl; + return 1; + } +} diff --git a/src/musique.hh b/src/musique.hh new file mode 100644 index 0000000..832248b --- /dev/null +++ b/src/musique.hh @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include + +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; + +using i8 = std::int8_t; +using i16 = std::int16_t; +using i32 = std::int32_t; +using i64 = std::int64_t; + +struct Error +{ + std::string_view message; + Error *child = nullptr; +}; + +template +using Result = tl::expected; + +std::ostream& operator<<(std::ostream& os, Error const& err); + +// NOTE This implementation requires C++ language extension: statement expressions +// It's supported by GCC, other compilers i don't know +#define Try(Value) ({ \ + auto try_value = (Value); \ + if (not try_value.has_value()) return tl::unexpected(try_value.error()); \ + *std::move(try_value); \ + }) + +struct Token +{ + enum class Type + { + // like repeat or choose or chord + Symbol, + + // chord literal, like c125 + Chord, + + // numeric literal (floating point or integer) + Numeric, + + // "|" separaters arguments from block body, and provides variable introduction syntax + Variable_Separator, + + // "[" and "]", delimit anonymous block of code (potentially a function) + Open_Block, + Close_Block, + + // "(" and ")", used in arithmetic or as function invocation sarrounding (like in Haskell) + Open_Paren, + Close_Paren + }; + + std::string_view source; +}; + +std::ostream& operator<<(std::ostream& os, Token const& tok); + +struct Lexer +{ + // Source that is beeing lexed + std::string_view source; + + // Determine location of tokens to produce nice errors + std::string_view source_name = ""; + unsigned column = 1, row = 1; + + auto next_token() -> Result; +};