diff --git a/README.md b/README.md index 172f082..b84d45b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Inja is a template engine for modern C++, loosely inspired by [jinja](http://jin json data; data["name"] = "world"; -inja::render("Hello {{ name }}!", data); // "Hello world!" +inja::render("Hello {{ name }}!", data); // Returns "Hello world!" ``` diff --git a/test/thirdparty/catch/catch.hpp b/test/thirdparty/catch/catch.hpp index 362f869..882699e 100644 --- a/test/thirdparty/catch/catch.hpp +++ b/test/thirdparty/catch/catch.hpp @@ -1,9 +1,9 @@ /* - * Catch v2.0.1 - * Generated: 2017-11-03 11:53:39.642003 + * Catch v2.1.2 + * Generated: 2018-02-09 17:05:21.506253 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2017 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -13,6 +13,10 @@ // start catch.hpp +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 1 +#define CATCH_VERSION_PATCH 2 + #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ @@ -26,9 +30,6 @@ # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC -# pragma clang diagnostic ignored "-Wglobal-constructors" -# pragma clang diagnostic ignored "-Wvariadic-macros" -# pragma clang diagnostic ignored "-Wc99-extensions" # pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" @@ -36,27 +37,33 @@ # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ -# pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wparentheses" - # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif // end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) # define CATCH_CONFIG_EXTERNAL_INTERFACES # if defined(CATCH_CONFIG_DISABLE_MATCHERS) # undef CATCH_CONFIG_DISABLE_MATCHERS # endif +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER #endif +#if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h #ifdef __APPLE__ # include -# if TARGET_OS_MAC == 1 +# if TARGET_OS_OSX == 1 # define CATCH_PLATFORM_MAC # elif TARGET_OS_IPHONE == 1 # define CATCH_PLATFORM_IPHONE @@ -70,6 +77,7 @@ #endif // end catch_platform.h + #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED @@ -77,6 +85,13 @@ # endif #endif +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h // start catch_tag_alias_autoregistrar.h // start catch_common.h @@ -105,6 +120,14 @@ # define CATCH_CPP14_OR_GREATER # endif +# if __cplusplus >= 201703L +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS #endif #ifdef __clang__ @@ -153,6 +176,10 @@ // Visual C++ #ifdef _MSC_VER +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + // Universal Windows platform does not support SEH // Or console colours (or console at all...) # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) @@ -185,6 +212,10 @@ # define CATCH_CONFIG_POSIX_SIGNALS #endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_INTERNAL_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS # define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS @@ -228,7 +259,10 @@ namespace Catch { struct SourceLineInfo { SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} SourceLineInfo( SourceLineInfo const& other ) = default; SourceLineInfo( SourceLineInfo && ) = default; @@ -245,11 +279,6 @@ namespace Catch { std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - // This is just here to avoid compiler warnings with macro constants and boolean literals - bool isTrue( bool value ); - bool alwaysTrue(); - bool alwaysFalse(); - // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as @@ -275,7 +304,10 @@ namespace Catch { } // end namespace Catch -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h @@ -330,10 +362,12 @@ namespace Catch { /// visible - but it does mean (substring) StringRefs should not be shared between /// threads. class StringRef { - friend struct StringRefTestAccess; - + public: using size_type = std::size_t; + private: + friend struct StringRefTestAccess; + char const* m_start; size_type m_size; @@ -341,16 +375,50 @@ namespace Catch { void takeOwnership(); - public: // construction/ assignment - StringRef() noexcept; - StringRef( StringRef const& other ) noexcept; - StringRef( StringRef&& other ) noexcept; - StringRef( char const* rawChars ) noexcept; - StringRef( char const* rawChars, size_type size ) noexcept; - StringRef( std::string const& stdString ) noexcept; - ~StringRef() noexcept; + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } - auto operator = ( StringRef other ) noexcept -> StringRef&; operator std::string() const; void swap( StringRef& other ) noexcept; @@ -362,8 +430,13 @@ namespace Catch { auto operator[] ( size_type index ) const noexcept -> char; public: // named queries - auto empty() const noexcept -> bool; - auto size() const noexcept -> size_type; + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + auto numberOfCharacters() const noexcept -> size_type; auto c_str() const -> char const*; @@ -382,6 +455,10 @@ namespace Catch { auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + } // namespace Catch // end catch_stringref.h @@ -407,7 +484,7 @@ auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { } struct NameAndTags { - NameAndTags( StringRef name_ = "", StringRef tags_ = "" ) noexcept; + NameAndTags( StringRef name_ = StringRef(), StringRef tags_ = StringRef() ) noexcept; StringRef name; StringRef tags; }; @@ -473,15 +550,121 @@ struct AutoReg : NonCopyable { // start catch_assertionhandler.h +// start catch_assertioninfo.h + +// start catch_result_type.h + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +// end catch_result_type.h +namespace Catch { + + struct AssertionInfo + { + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; + }; + +} // end namespace Catch + +// end catch_assertioninfo.h // start catch_decomposer.h // start catch_tostring.h -#include #include #include #include #include +// start catch_stream.h + +#include +#include +#include + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + + class StringRef; + + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; + }; + + auto makeStream( StringRef const &filename ) -> IStream const*; + + class ReusableStringStream { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + auto str() const -> std::string; + + template + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + auto get() -> std::ostream& { return *m_oss; } + + static void cleanup(); + }; +} + +// end catch_stream.h #ifdef __OBJC__ // start catch_objc_arc.hpp @@ -535,7 +718,7 @@ inline id performOptionalSelector( id obj, SEL sel ) { #endif // We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy; +struct Catch_global_namespace_dummy {}; std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); namespace Catch { @@ -566,25 +749,37 @@ namespace Catch { static const bool value = decltype(test(0))::value; }; + template + std::string convertUnknownEnumToString( E e ); + + template + typename std::enable_if::value, std::string>::type convertUnstreamable( T const& ) { + return Detail::unprintableString; + } + template + typename std::enable_if::value, std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + } // namespace Detail // If we decide for C++14, change these to enable_if_ts - template + template struct StringMaker { template static typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type - convert(const Fake& t) { - std::ostringstream sstr; - sstr << t; - return sstr.str(); + convert(const Fake& value) { + ReusableStringStream rss; + rss << value; + return rss.str(); } template static typename std::enable_if::value, std::string>::type - convert(const Fake&) { - return Detail::unprintableString; + convert( const Fake& value ) { + return Detail::convertUnstreamable( value ); } }; @@ -597,6 +792,11 @@ namespace Catch { return ::Catch::StringMaker::type>::type>::convert(e); } + template + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast::type>(e)); + } + } // namespace Detail // Some predefined specializations @@ -729,32 +929,18 @@ namespace Catch { namespace Detail { template std::string rangeToString(InputIterator first, InputIterator last) { - std::ostringstream oss; - oss << "{ "; + ReusableStringStream rss; + rss << "{ "; if (first != last) { - oss << ::Catch::Detail::stringify(*first); + rss << ::Catch::Detail::stringify(*first); for (++first; first != last; ++first) - oss << ", " << ::Catch::Detail::stringify(*first); + rss << ", " << ::Catch::Detail::stringify(*first); } - oss << " }"; - return oss.str(); + rss << " }"; + return rss.str(); } } - template - struct StringMaker > { - static std::string convert( std::vector const& v ) { - return ::Catch::Detail::rangeToString( v.begin(), v.end() ); - } - }; - - template - struct EnumStringMaker { - static std::string convert(const T& t) { - return ::Catch::Detail::stringify(static_cast::type>(t)); - } - }; - #ifdef __OBJC__ template<> struct StringMaker { @@ -798,13 +984,13 @@ namespace Catch { template struct StringMaker > { static std::string convert(const std::pair& pair) { - std::ostringstream oss; - oss << "{ " + ReusableStringStream rss; + rss << "{ " << ::Catch::Detail::stringify(pair.first) << ", " << ::Catch::Detail::stringify(pair.second) << " }"; - return oss.str(); + return rss.str(); } }; } @@ -841,22 +1027,79 @@ namespace Catch { template struct StringMaker> { static std::string convert(const std::tuple& tuple) { - std::ostringstream os; - os << '{'; - Detail::TupleElementPrinter>::print(tuple, os); - os << " }"; - return os.str(); + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); } }; } #endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +namespace Catch { + struct not_this_one {}; // Tag type for detecting which begin/ end are being selected + + // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace + using std::begin; + using std::end; + + not_this_one begin( ... ); + not_this_one end( ... ); + + template + struct is_range { + static const bool value = + !std::is_same())), not_this_one>::value && + !std::is_same())), not_this_one>::value; + }; + + template + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector specially + template + std::string rangeToString( std::vector const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>::type> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template + struct StringMaker { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + +} // namespace Catch + // Separate std::chrono::duration specialization #if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) #include #include #include +namespace Catch { + template struct ratio_string { static std::string symbol(); @@ -864,69 +1107,68 @@ struct ratio_string { template std::string ratio_string::symbol() { - std::ostringstream oss; - oss << '[' << Ratio::num << '/' + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' << Ratio::den << ']'; - return oss.str(); + return rss.str(); } template <> struct ratio_string { - static std::string symbol() { return "a"; } + static std::string symbol(); }; template <> struct ratio_string { - static std::string symbol() { return "f"; } + static std::string symbol(); }; template <> struct ratio_string { - static std::string symbol() { return "p"; } + static std::string symbol(); }; template <> struct ratio_string { - static std::string symbol() { return "n"; } + static std::string symbol(); }; template <> struct ratio_string { - static std::string symbol() { return "u"; } + static std::string symbol(); }; template <> struct ratio_string { - static std::string symbol() { return "m"; } + static std::string symbol(); }; -namespace Catch { //////////// // std::chrono::duration specializations template struct StringMaker> { static std::string convert(std::chrono::duration const& duration) { - std::ostringstream oss; - oss << duration.count() << ' ' << ratio_string::symbol() << 's'; - return oss.str(); + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); } }; template struct StringMaker>> { static std::string convert(std::chrono::duration> const& duration) { - std::ostringstream oss; - oss << duration.count() << " s"; - return oss.str(); + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); } }; template struct StringMaker>> { static std::string convert(std::chrono::duration> const& duration) { - std::ostringstream oss; - oss << duration.count() << " m"; - return oss.str(); + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); } }; template struct StringMaker>> { static std::string convert(std::chrono::duration> const& duration) { - std::ostringstream oss; - oss << duration.count() << " h"; - return oss.str(); + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); } }; @@ -972,7 +1214,7 @@ namespace Catch { #endif // end catch_tostring.h -#include +#include #ifdef _MSC_VER #pragma warning(push) @@ -985,27 +1227,32 @@ namespace Catch { namespace Catch { struct ITransientExpression { - virtual auto isBinaryExpression() const -> bool = 0; - virtual auto getResult() const -> bool = 0; + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } virtual void streamReconstructedExpression( std::ostream &os ) const = 0; - // We don't actually need a virtual destructore, but many static analysers + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + // We don't actually need a virtual destructor, but many static analysers // complain if it's not here :-( virtual ~ITransientExpression(); + + bool m_isBinaryExpression; + bool m_result; + }; void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); template class BinaryExpr : public ITransientExpression { - bool m_result; LhsT m_lhs; StringRef m_op; RhsT m_rhs; - auto isBinaryExpression() const -> bool override { return true; } - auto getResult() const -> bool override { return m_result; } - void streamReconstructedExpression( std::ostream &os ) const override { formatReconstructedExpression ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); @@ -1013,7 +1260,7 @@ namespace Catch { public: BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) - : m_result( comparisonResult ), + : ITransientExpression{ true, comparisonResult }, m_lhs( lhs ), m_op( op ), m_rhs( rhs ) @@ -1024,20 +1271,20 @@ namespace Catch { class UnaryExpr : public ITransientExpression { LhsT m_lhs; - auto isBinaryExpression() const -> bool override { return false; } - auto getResult() const -> bool override { return m_lhs ? true : false; } - void streamReconstructedExpression( std::ostream &os ) const override { os << Catch::Detail::stringify( m_lhs ); } public: - UnaryExpr( LhsT lhs ) : m_lhs( lhs ) {} + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, lhs ? true : false }, + m_lhs( lhs ) + {} }; // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) template - auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return lhs == rhs; }; + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast(lhs == rhs); } template auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } template @@ -1048,7 +1295,7 @@ namespace Catch { auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } template - auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return lhs != rhs; }; + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast(lhs != rhs); } template auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } template @@ -1062,43 +1309,43 @@ namespace Catch { class ExprLhs { LhsT m_lhs; public: - ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} template auto operator == ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( compareEqual( m_lhs, rhs ), m_lhs, "==", rhs ); + return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; } auto operator == ( bool rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs == rhs, m_lhs, "==", rhs ); + return { m_lhs == rhs, m_lhs, "==", rhs }; } template auto operator != ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs ); + return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; } auto operator != ( bool rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs != rhs, m_lhs, "!=", rhs ); + return { m_lhs != rhs, m_lhs, "!=", rhs }; } template auto operator > ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs > rhs, m_lhs, ">", rhs ); + return { static_cast(m_lhs > rhs), m_lhs, ">", rhs }; } template auto operator < ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs < rhs, m_lhs, "<", rhs ); + return { static_cast(m_lhs < rhs), m_lhs, "<", rhs }; } template auto operator >= ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs >= rhs, m_lhs, ">=", rhs ); + return { static_cast(m_lhs >= rhs), m_lhs, ">=", rhs }; } template auto operator <= ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs <= rhs, m_lhs, "<=", rhs ); + return { static_cast(m_lhs <= rhs), m_lhs, "<=", rhs }; } auto makeUnaryExpr() const -> UnaryExpr { - return UnaryExpr( m_lhs ); + return UnaryExpr{ m_lhs }; } }; @@ -1112,10 +1359,11 @@ namespace Catch { struct Decomposer { template auto operator <= ( T const& lhs ) -> ExprLhs { - return ExprLhs( lhs ); + return ExprLhs{ lhs }; } + auto operator <=( bool value ) -> ExprLhs { - return ExprLhs( value ); + return ExprLhs{ value }; } }; @@ -1126,79 +1374,88 @@ namespace Catch { #endif // end catch_decomposer.h -// start catch_assertioninfo.h +// start catch_interfaces_capture.h -// start catch_result_type.h +#include namespace Catch { - // ResultWas::OfType enum - struct ResultWas { enum OfType { - Unknown = -1, - Ok = 0, - Info = 1, - Warning = 2, + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct Counts; + struct BenchmarkInfo; + struct BenchmarkStats; + struct AssertionReaction; - FailureBit = 0x10, + struct ITransientExpression; - ExpressionFailed = FailureBit | 1, - ExplicitFailure = FailureBit | 2, + struct IResultCapture { - Exception = 0x100 | FailureBit, + virtual ~IResultCapture(); - ThrewException = Exception | 1, - DidntThrowException = Exception | 2, + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; - FatalErrorCondition = 0x200 | FailureBit + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; - }; }; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; - bool isOk( ResultWas::OfType resultType ); - bool isJustInfo( int flags ); + virtual void handleFatalErrorCondition( StringRef message ) = 0; - // ResultDisposition::Flags enum - struct ResultDisposition { enum Flags { - Normal = 0x01, + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; - ContinueOnFailure = 0x02, // Failures fail test, but execution continues - FalseTest = 0x04, // Prefix expression with ! - SuppressFail = 0x08 // Failures are reported but do not fail the test - }; }; + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; - ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); - - bool shouldContinueOnFailure( int flags ); - bool isFalseTest( int flags ); - bool shouldSuppressFailure( int flags ); - -} // end namespace Catch - -// end catch_result_type.h -namespace Catch { - - struct AssertionInfo - { - StringRef macroName; - SourceLineInfo lineInfo; - StringRef capturedExpression; - ResultDisposition::Flags resultDisposition; - - // We want to delete this constructor but a compiler bug in 4.8 means - // the struct is then treated as non-aggregate - //AssertionInfo() = delete; + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; }; -} // end namespace Catch + IResultCapture& getResultCapture(); +} -// end catch_assertioninfo.h +// end catch_interfaces_capture.h namespace Catch { struct TestFailureException{}; struct AssertionResultData; + struct IResultCapture; + class RunContext; class LazyExpression { friend class AssertionHandler; friend struct AssertionStats; + friend class RunContext; ITransientExpression const* m_transientExpression = nullptr; bool m_isNegated; @@ -1212,11 +1469,16 @@ namespace Catch { friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; }; + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; + class AssertionHandler { AssertionInfo m_assertionInfo; - bool m_shouldDebugBreak = false; - bool m_shouldThrow = false; - bool m_inExceptionGuard = false; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; public: AssertionHandler @@ -1224,26 +1486,31 @@ namespace Catch { SourceLineInfo const& lineInfo, StringRef capturedExpression, ResultDisposition::Flags resultDisposition ); - ~AssertionHandler(); - - void handle( ITransientExpression const& expr ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } template - void handle( ExprLhs const& expr ) { - handle( expr.makeUnaryExpr() ); + void handleExpr( ExprLhs const& expr ) { + handleExpr( expr.makeUnaryExpr() ); } - void handle( ResultWas::OfType resultType ); - void handle( ResultWas::OfType resultType, StringRef const& message ); - void handle( ResultWas::OfType resultType, ITransientExpression const* expr, bool negated ); - void handle( AssertionResultData const& resultData, ITransientExpression const* expr ); + void handleExpr( ITransientExpression const& expr ); - auto shouldDebugBreak() const -> bool; + void handleMessage(ResultWas::OfType resultType, StringRef const& message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query auto allowThrows() const -> bool; - void reactWithDebugBreak() const; - void reactWithoutDebugBreak() const; - void useActiveException(); - void setExceptionGuard(); - void unsetExceptionGuard(); }; void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); @@ -1254,7 +1521,6 @@ namespace Catch { // start catch_message.h #include -#include namespace Catch { @@ -1283,8 +1549,7 @@ namespace Catch { return *this; } - // !TBD reuse a global/ thread-local stream - std::ostringstream m_stream; + ReusableStringStream m_stream; }; struct MessageBuilder : MessageStream { @@ -1303,7 +1568,7 @@ namespace Catch { class ScopedMessage { public: - ScopedMessage( MessageBuilder const& builder ); + explicit ScopedMessage( MessageBuilder const& builder ); ~ScopedMessage(); MessageInfo m_info; @@ -1312,89 +1577,6 @@ namespace Catch { } // end namespace Catch // end catch_message.h -// start catch_interfaces_capture.h - -#include - -namespace Catch { - - class AssertionResult; - struct AssertionInfo; - struct SectionInfo; - struct SectionEndInfo; - struct MessageInfo; - struct Counts; - struct BenchmarkInfo; - struct BenchmarkStats; - - struct IResultCapture { - - virtual ~IResultCapture(); - - virtual void assertionStarting( AssertionInfo const& info ) = 0; - virtual void assertionEnded( AssertionResult const& result ) = 0; - virtual bool sectionStarted( SectionInfo const& sectionInfo, - Counts& assertions ) = 0; - virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; - virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; - - virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; - virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; - - virtual void pushScopedMessage( MessageInfo const& message ) = 0; - virtual void popScopedMessage( MessageInfo const& message ) = 0; - - virtual std::string getCurrentTestName() const = 0; - virtual const AssertionResult* getLastResult() const = 0; - - virtual void exceptionEarlyReported() = 0; - - virtual void handleFatalErrorCondition( StringRef message ) = 0; - - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; - virtual void assertionRun() = 0; - }; - - IResultCapture& getResultCapture(); -} - -// end catch_interfaces_capture.h -// start catch_debugger.h - -namespace Catch { - bool isDebuggerActive(); -} - -#ifdef CATCH_PLATFORM_MAC - - #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ - -#elif defined(CATCH_PLATFORM_LINUX) - // If we can use inline assembler, do it because this allows us to break - // directly at the location of the failing check instead of breaking inside - // raise() called from it, i.e. one stack frame below. - #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) - #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ - #else // Fall back to the generic way. - #include - - #define CATCH_TRAP() raise(SIGTRAP) - #endif -#elif defined(_MSC_VER) - #define CATCH_TRAP() __debugbreak() -#elif defined(__MINGW32__) - extern "C" __declspec(dllimport) void __stdcall DebugBreak(); - #define CATCH_TRAP() DebugBreak() -#endif - -#ifdef CATCH_TRAP - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } -#else - #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); -#endif - -// end catch_debugger.h #if !defined(CATCH_CONFIG_DISABLE) #if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) @@ -1404,48 +1586,33 @@ namespace Catch { #endif #if defined(CATCH_CONFIG_FAST_COMPILE) -/////////////////////////////////////////////////////////////////////////////// -// We can speedup compilation significantly by breaking into debugger lower in -// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER -// macro in each assertion -#define INTERNAL_CATCH_REACT( handler ) \ - handler.reactWithDebugBreak(); /////////////////////////////////////////////////////////////////////////////// // Another way to speed-up compilation is to omit local try-catch for REQUIRE* // macros. -// This can potentially cause false negative, if the test code catches -// the exception before it propagates back up to the runner. -#define INTERNAL_CATCH_TRY( capturer ) capturer.setExceptionGuard(); -#define INTERNAL_CATCH_CATCH( capturer ) capturer.unsetExceptionGuard(); +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) #else // CATCH_CONFIG_FAST_COMPILE -/////////////////////////////////////////////////////////////////////////////// -// In the event of a failure works out if the debugger needs to be invoked -// and/or an exception thrown and takes appropriate action. -// This needs to be done as a macro so the debugger will stop in the user -// source code rather than in Catch library code -#define INTERNAL_CATCH_REACT( handler ) \ - if( handler.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ - handler.reactWithoutDebugBreak(); - -#define INTERNAL_CATCH_TRY( capturer ) try -#define INTERNAL_CATCH_CATCH( capturer ) catch(...) { capturer.useActiveException(); } +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } #endif +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ do { \ Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - INTERNAL_CATCH_TRY( catchAssertionHandler ) { \ + INTERNAL_CATCH_TRY { \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - catchAssertionHandler.handle( Catch::Decomposer() <= __VA_ARGS__ ); \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::isTrue( false && static_cast( !!(__VA_ARGS__) ) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + } while( (void)0, false && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. /////////////////////////////////////////////////////////////////////////////// @@ -1464,13 +1631,13 @@ namespace Catch { Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ try { \ static_cast(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ } \ catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ + catchAssertionHandler.handleUnexpectedInflightException(); \ } \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) + } while( false ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ @@ -1479,15 +1646,15 @@ namespace Catch { if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ } \ catch( ... ) { \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ } \ else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + catchAssertionHandler.handleThrowingCallSkipped(); \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) + } while( false ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ @@ -1496,30 +1663,30 @@ namespace Catch { if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast(expr); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ } \ catch( exceptionType const& ) { \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ } \ catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ + catchAssertionHandler.handleUnexpectedInflightException(); \ } \ else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + catchAssertionHandler.handleThrowingCallSkipped(); \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) + } while( false ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ do { \ Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ - catchAssertionHandler.handle( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) + } while( false ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_INFO( macroName, log ) \ - Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); /////////////////////////////////////////////////////////////////////////////// // Although this is matcher-based, it can be used with just a string @@ -1529,15 +1696,15 @@ namespace Catch { if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ } \ catch( ... ) { \ - handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ } \ else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + catchAssertionHandler.handleThrowingCallSkipped(); \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) + } while( false ) #endif // CATCH_CONFIG_DISABLE @@ -1617,8 +1784,8 @@ namespace Catch { uint64_t m_nanoseconds = 0; public: void start(); - auto getElapsedNanoseconds() const -> unsigned int; - auto getElapsedMicroseconds() const -> unsigned int; + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; auto getElapsedMilliseconds() const -> unsigned int; auto getElapsedSeconds() const -> double; }; @@ -1812,7 +1979,9 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ static std::string translatorName( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ static std::string translatorName( signature ) #define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) @@ -1820,22 +1989,8 @@ namespace Catch { // end catch_interfaces_exception.h // start catch_approx.h -// start catch_enforce.h - -#include -#include - -#define CATCH_PREPARE_EXCEPTION( type, msg ) \ - type( static_cast( std::ostringstream() << msg ).str() ) -#define CATCH_INTERNAL_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); -#define CATCH_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) -#define CATCH_ENFORCE( condition, msg ) \ - do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) - -// end catch_enforce.h #include +#include namespace Catch { namespace Detail { @@ -1906,9 +2061,12 @@ namespace Detail { template ::value>::type> Approx& epsilon( T const& newEpsilon ) { double epsilonAsDouble = static_cast(newEpsilon); - CATCH_ENFORCE(epsilonAsDouble >= 0 && epsilonAsDouble <= 1.0, - "Invalid Approx::epsilon: " << epsilonAsDouble - << ", Approx::epsilon has to be between 0 and 1"); + if( epsilonAsDouble < 0 || epsilonAsDouble > 1.0 ) { + throw std::domain_error + ( "Invalid Approx::epsilon: " + + Catch::Detail::stringify( epsilonAsDouble ) + + ", Approx::epsilon has to be between 0 and 1" ); + } m_epsilon = epsilonAsDouble; return *this; } @@ -1916,9 +2074,13 @@ namespace Detail { template ::value>::type> Approx& margin( T const& newMargin ) { double marginAsDouble = static_cast(newMargin); - CATCH_ENFORCE(marginAsDouble >= 0, - "Invalid Approx::margin: " << marginAsDouble - << ", Approx::Margin has to be non-negative."); + if( marginAsDouble < 0 ) { + throw std::domain_error + ( "Invalid Approx::margin: " + + Catch::Detail::stringify( marginAsDouble ) + + ", Approx::Margin has to be non-negative." ); + + } m_margin = marginAsDouble; return *this; } @@ -2013,12 +2175,12 @@ namespace Matchers { virtual bool match( PtrT* arg ) const = 0; }; - template - struct MatcherBase : MatcherUntypedBase, MatcherMethod { + template + struct MatcherBase : MatcherUntypedBase, MatcherMethod { - MatchAllOf operator && ( MatcherBase const& other ) const; - MatchAnyOf operator || ( MatcherBase const& other ) const; - MatchNotOf operator ! () const; + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; }; template @@ -2102,17 +2264,17 @@ namespace Matchers { MatcherBase const& m_underlyingMatcher; }; - template - MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { - return MatchAllOf() && *this && other; + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; } - template - MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { - return MatchAnyOf() || *this || other; + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; } - template - MatchNotOf MatcherBase::operator ! () const { - return MatchNotOf( *this ); + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); } } // namespace Impl @@ -2125,6 +2287,49 @@ using Matchers::Impl::MatcherBase; } // namespace Catch // end catch_matchers.h +// start catch_matchers_floating.h + +#include +#include + +namespace Catch { +namespace Matchers { + + namespace Floating { + + enum class FloatingPointKind : uint8_t; + + struct WithinAbsMatcher : MatcherBase { + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + struct WithinUlpsMatcher : MatcherBase { + WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + int m_ulps; + FloatingPointKind m_type; + }; + + } // namespace Floating + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); + Floating::WithinAbsMatcher WithinAbs(double target, double margin); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h // start catch_matchers_string.h #include @@ -2169,6 +2374,16 @@ namespace Matchers { bool match( std::string const& source ) const override; }; + struct RegexMatcher : MatcherBase { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; + }; + } // namespace StdString // The following functions create the actual matcher objects. @@ -2178,6 +2393,7 @@ namespace Matchers { StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); } // namespace Matchers } // namespace Catch @@ -2185,13 +2401,36 @@ namespace Matchers { // end catch_matchers_string.h // start catch_matchers_vector.h +#include + namespace Catch { namespace Matchers { namespace Vector { + namespace Detail { + template + size_t count(InputIterator first, InputIterator last, T const& item) { + size_t cnt = 0; + for (; first != last; ++first) { + if (*first == item) { + ++cnt; + } + } + return cnt; + } + template + bool contains(InputIterator first, InputIterator last, T const& item) { + for (; first != last; ++first) { + if (*first == item) { + return true; + } + } + return false; + } + } template - struct ContainsElementMatcher : MatcherBase, T> { + struct ContainsElementMatcher : MatcherBase> { ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} @@ -2212,7 +2451,7 @@ namespace Matchers { }; template - struct ContainsMatcher : MatcherBase, std::vector > { + struct ContainsMatcher : MatcherBase> { ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} @@ -2242,7 +2481,7 @@ namespace Matchers { }; template - struct EqualsMatcher : MatcherBase, std::vector > { + struct EqualsMatcher : MatcherBase> { EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} @@ -2264,6 +2503,46 @@ namespace Matchers { std::vector const& m_comparator; }; + template + struct UnorderedEqualsMatcher : MatcherBase> { + UnorderedEqualsMatcher(std::vector const& target) : m_target(target) {} + bool match(std::vector const& vec) const override { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include inside the common path + if (m_target.size() != vec.size()) { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst != *rfirst) { + ++lfirst; ++rfirst; + } + if (lfirst == llast) { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { + return false; + } + } + + return true; + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + private: + std::vector const& m_target; + }; + } // namespace Vector // The following functions create the actual matcher objects. @@ -2284,6 +2563,11 @@ namespace Matchers { return Vector::EqualsMatcher( comparator ); } + template + Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const& target) { + return Vector::UnorderedEqualsMatcher(target); + } + } // namespace Matchers } // namespace Catch @@ -2295,18 +2579,14 @@ namespace Catch { ArgT const& m_arg; MatcherT m_matcher; StringRef m_matcherString; - bool m_result; public: MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) - : m_arg( arg ), + : ITransientExpression{ true, matcher.match( arg ) }, + m_arg( arg ), m_matcher( matcher ), - m_matcherString( matcherString ), - m_result( matcher.match( arg ) ) + m_matcherString( matcherString ) {} - auto isBinaryExpression() const -> bool override { return true; } - auto getResult() const -> bool override { return m_result; } - void streamReconstructedExpression( std::ostream &os ) const override { auto matcherAsString = m_matcher.toString(); os << Catch::Detail::stringify( m_arg ) << ' '; @@ -2332,11 +2612,11 @@ namespace Catch { #define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ do { \ Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - INTERNAL_CATCH_TRY( catchAssertionHandler ) { \ - catchAssertionHandler.handle( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) + } while( false ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ @@ -2345,18 +2625,18 @@ namespace Catch { if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast(__VA_ARGS__ ); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ } \ catch( exceptionType const& ex ) { \ - catchAssertionHandler.handle( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ } \ catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ + catchAssertionHandler.handleUnexpectedInflightException(); \ } \ else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + catchAssertionHandler.handleThrowingCallSkipped(); \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) + } while( false ) // end catch_capture_matchers.h #endif @@ -2918,69 +3198,6 @@ namespace Catch { // end catch_interfaces_config.h // Libstdc++ doesn't like incomplete classes for unique_ptr -// start catch_stream.h - -// start catch_streambuf.h - -#include - -namespace Catch { - - class StreamBufBase : public std::streambuf { - public: - virtual ~StreamBufBase(); - }; -} - -// end catch_streambuf.h -#include -#include -#include -#include - -namespace Catch { - - std::ostream& cout(); - std::ostream& cerr(); - std::ostream& clog(); - - struct IStream { - virtual ~IStream(); - virtual std::ostream& stream() const = 0; - }; - - class FileStream : public IStream { - mutable std::ofstream m_ofs; - public: - FileStream( std::string const& filename ); - ~FileStream() override = default; - public: // IStream - std::ostream& stream() const override; - }; - - class CoutStream : public IStream { - mutable std::ostream m_os; - public: - CoutStream(); - ~CoutStream() override = default; - - public: // IStream - std::ostream& stream() const override; - }; - - class DebugOutStream : public IStream { - std::unique_ptr m_streamBuf; - mutable std::ostream m_os; - public: - DebugOutStream(); - ~DebugOutStream() override = default; - - public: // IStream - std::ostream& stream() const override; - }; -} - -// end catch_stream.h #include #include @@ -3115,7 +3332,7 @@ namespace Catch { std::string getExpandedExpression() const; std::string getMessage() const; SourceLineInfo getSourceInfo() const; - std::string getTestMacroName() const; + StringRef getTestMacroName() const; //protected: AssertionInfo m_info; @@ -3408,6 +3625,7 @@ namespace Catch { #include #include #include +#include namespace Catch { void prepareExpandedExpression(AssertionResult& result); @@ -3423,7 +3641,8 @@ namespace Catch { stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; - CATCH_ENFORCE( DerivedT::getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + throw std::domain_error( "Verbosity level not supported by this reporter" ); } ReporterPreferences getPreferences() const override { @@ -3536,7 +3755,8 @@ namespace Catch { stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; - CATCH_ENFORCE( DerivedT::getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + throw std::domain_error( "Verbosity level not supported by this reporter" ); } ~CumulativeReporterBase() override = default; @@ -3681,10 +3901,11 @@ namespace Catch { BrightGreen = Bright | Green, LightGrey = Bright | Grey, BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, // By intention FileName = LightGrey, - Warning = Yellow, + Warning = BrightYellow, ResultError = BrightRed, ResultSuccess = BrightGreen, ResultExpectedFailure = Warning, @@ -3693,7 +3914,7 @@ namespace Catch { Success = Green, OriginalExpression = Cyan, - ReconstructedExpression = Yellow, + ReconstructedExpression = BrightYellow, SecondaryText = LightGrey, Headers = White @@ -3738,7 +3959,7 @@ namespace Catch { public: - ReporterRegistrar( std::string const& name ) { + explicit ReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, std::make_shared() ); } }; @@ -3783,9 +4004,303 @@ namespace Catch { #endif // CATCH_CONFIG_DISABLE // end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + }; + +} // end namespace Catch + +// end catch_reporter_compact.h +// start catch_reporter_console.h + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + // Fwd decls + struct SummaryColumn; + class TablePrinter; + + struct ConsoleReporter : StreamingReporterBase { + std::unique_ptr m_tablePrinter; + + ConsoleReporter(ReporterConfig const& config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats const& stats) override; + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testGroupEnded(TestGroupStats const& _testGroupStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + + private: + + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); + + void printTotals(Totals const& totals); + void printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row); + + void printTotalsDivider(Totals const& totals); + void printSummaryDivider(); + + private: + bool m_headerPrinted = false; + }; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + +#include + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + XmlWriter& writeComment( std::string const& text ); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +// end catch_xmlwriter.h +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter(ReporterConfig const& _config); + + ~JunitReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const& /*spec*/) override; + + void testRunStarting(TestRunInfo const& runInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + bool assertionEnded(AssertionStats const& assertionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEndedCumulative() override; + + void writeGroup(TestGroupNode const& groupNode, double suiteTime); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection(std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode); + + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +// end catch_reporter_junit.h +// start catch_reporter_xml.h + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter(ReporterConfig const& _config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const& sourceInfo); + + public: // StreamingReporterBase + + void noMatchingTestCases(std::string const& s) override; + + void testRunStarting(TestRunInfo const& testInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void sectionStarting(SectionInfo const& sectionInfo) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& assertionStats) override; + + void sectionEnded(SectionStats const& sectionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEnded(TestRunStats const& testRunStats) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +// end catch_reporter_xml.h + // end catch_external_interfaces.h #endif +#endif // ! CATCH_CONFIG_IMPL_ONLY + #ifdef CATCH_IMPL // start catch_impl.hpp @@ -4008,9 +4523,9 @@ namespace Detail { } std::string Approx::toString() const { - std::ostringstream oss; - oss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; - return oss.str(); + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); } bool Approx::equalityComparisonImpl(const double other) const { @@ -4038,6 +4553,7 @@ namespace Catch { struct IResultCapture; struct IRunner; struct IConfig; + struct IMutableContext; using IConfigPtr = std::shared_ptr; @@ -4047,7 +4563,7 @@ namespace Catch { virtual IResultCapture* getResultCapture() = 0; virtual IRunner* getRunner() = 0; - virtual IConfigPtr getConfig() const = 0; + virtual IConfigPtr const& getConfig() const = 0; }; struct IMutableContext : IContext @@ -4056,16 +4572,294 @@ namespace Catch { virtual void setResultCapture( IResultCapture* resultCapture ) = 0; virtual void setRunner( IRunner* runner ) = 0; virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); }; - IContext& getCurrentContext(); - IMutableContext& getCurrentMutableContext(); + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } + void cleanUpContext(); } // end catch_context.h -#include +// start catch_debugger.h +namespace Catch { + bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + namespace Catch { + inline void doNothing() {} + } + #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() +#endif + +// end catch_debugger.h +// start catch_run_context.h + +// start catch_fatal_condition.h + +#include + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h + +# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} + +# else // CATCH_CONFIG_WINDOWS_SEH is defined + +namespace Catch { + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); + + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + +} // namespace Catch + +# endif // CATCH_CONFIG_WINDOWS_SEH + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} + +# else // CATCH_CONFIG_POSIX_SIGNALS is defined + +#include + +namespace Catch { + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions[];// [sizeof(signalDefs) / sizeof(SignalDefs)]; + static stack_t oldSigStack; + static char altStackMem[]; + + static void handleSignal( int sig ); + + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); + }; + +} // namespace Catch + +# endif // CATCH_CONFIG_POSIX_SIGNALS + +#endif // not Windows + +// end catch_fatal_condition.h +#include + +namespace Catch { + + struct IMutableContext; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); + + ~RunContext() override; + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); + + Totals runTest(TestCase const& testCase); + + IConfigPtr config() const; + IStreamingReporter& reporter() const; + + public: // IResultCapture + + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; + + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; + + void sectionEnded( SectionEndInfo const& endInfo ) override; + void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats const& stats ) override; + + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const override; + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); + + void assertionEnded( AssertionResult const& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); + + void populateReaction( AssertionReaction& reaction ); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker; + Option m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + +} // end namespace Catch + +// end catch_run_context.h namespace Catch { auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { @@ -4104,86 +4898,55 @@ namespace Catch { SourceLineInfo const& lineInfo, StringRef capturedExpression, ResultDisposition::Flags resultDisposition ) - : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition } - { - getCurrentContext().getResultCapture()->assertionStarting( m_assertionInfo ); + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); } - AssertionHandler::~AssertionHandler() { - if ( m_inExceptionGuard ) { - handle( ResultWas::ThrewException, "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE" ); - getCurrentContext().getResultCapture()->exceptionEarlyReported(); - } - } - - void AssertionHandler::handle( ITransientExpression const& expr ) { - - bool negated = isFalseTest( m_assertionInfo.resultDisposition ); - bool result = expr.getResult() != negated; - - handle( result ? ResultWas::Ok : ResultWas::ExpressionFailed, &expr, negated ); - } - void AssertionHandler::handle( ResultWas::OfType resultType ) { - handle( resultType, nullptr, false ); - } - void AssertionHandler::handle( ResultWas::OfType resultType, StringRef const& message ) { - AssertionResultData data( resultType, LazyExpression( false ) ); - data.message = message; - handle( data, nullptr ); - } - void AssertionHandler::handle( ResultWas::OfType resultType, ITransientExpression const* expr, bool negated ) { - AssertionResultData data( resultType, LazyExpression( negated ) ); - handle( data, expr ); - } - void AssertionHandler::handle( AssertionResultData const& resultData, ITransientExpression const* expr ) { - - getResultCapture().assertionRun(); - - AssertionResult assertionResult{ m_assertionInfo, resultData }; - assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; - - getResultCapture().assertionEnded( assertionResult ); - - if( !assertionResult.isOk() ) { - m_shouldDebugBreak = getCurrentContext().getConfig()->shouldDebugBreak(); - m_shouldThrow = - getCurrentContext().getRunner()->aborting() || - (m_assertionInfo.resultDisposition & ResultDisposition::Normal); - } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); } auto AssertionHandler::allowThrows() const -> bool { return getCurrentContext().getConfig()->allowThrows(); } - auto AssertionHandler::shouldDebugBreak() const -> bool { - return m_shouldDebugBreak; - } - void AssertionHandler::reactWithDebugBreak() const { - if (m_shouldDebugBreak) { - /////////////////////////////////////////////////////////////////// - // To inspect the state during test, you need to go one level up the callstack - // To go back to the test and change execution, jump over the reactWithoutDebugBreak() call - /////////////////////////////////////////////////////////////////// + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) CATCH_BREAK_INTO_DEBUGGER(); } - reactWithoutDebugBreak(); - } - void AssertionHandler::reactWithoutDebugBreak() const { - if( m_shouldThrow ) + if( m_reaction.shouldThrow ) throw Catch::TestFailureException(); } - - void AssertionHandler::useActiveException() { - handle( ResultWas::ThrewException, Catch::translateActiveException() ); + void AssertionHandler::setCompleted() { + m_completed = true; } - void AssertionHandler::setExceptionGuard() { - assert( m_inExceptionGuard == false ); - m_inExceptionGuard = true; + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); } - void AssertionHandler::unsetExceptionGuard() { - assert( m_inExceptionGuard == true ); - m_inExceptionGuard = false; + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); } // This is the overload that takes a string and infers the Equals matcher from it @@ -4205,10 +4968,9 @@ namespace Catch { if( reconstructedExpression.empty() ) { if( lazyExpression ) { - // !TBD Use stringstream for now, but rework above to pass stream in - std::ostringstream oss; - oss << lazyExpression; - reconstructedExpression = oss.str(); + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); } } return reconstructedExpression; @@ -4243,7 +5005,7 @@ namespace Catch { std::string AssertionResult::getExpression() const { if( isFalseTest( m_info.resultDisposition ) ) - return "!(" + std::string(m_info.capturedExpression) + ")"; + return "!(" + m_info.capturedExpression + ")"; else return m_info.capturedExpression; } @@ -4254,9 +5016,9 @@ namespace Catch { expr = m_info.capturedExpression; else { expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); - expr += m_info.macroName; + expr += m_info.macroName.c_str(); expr += "( "; - expr += m_info.capturedExpression; + expr += m_info.capturedExpression.c_str(); expr += " )"; } return expr; @@ -4280,7 +5042,7 @@ namespace Catch { return m_info.lineInfo; } - std::string AssertionResult::getTestMacroName() const { + StringRef AssertionResult::getTestMacroName() const { return m_info.macroName; } @@ -4324,7 +5086,7 @@ namespace Catch { void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { std::string exceptionMessage = Catch::translateActiveException(); MatchExpr expr( exceptionMessage, matcher, matcherString ); - handler.handle( expr ); + handler.handleExpr( expr ); } } // namespace Catch @@ -4350,8 +5112,14 @@ namespace Catch { #endif // start clara.hpp -// v1.0-develop.2 -// See https://github.com/philsquared/Clara +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.2 #ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH @@ -4714,7 +5482,7 @@ namespace detail { template struct UnaryLambdaTraits { static const bool isValid = true; - using ArgType = typename std::remove_const::type>::type;; + using ArgType = typename std::remove_const::type>::type; using ReturnType = ReturnT; }; @@ -4878,7 +5646,7 @@ namespace detail { return *this; } - ~ResultValueBase() { + ~ResultValueBase() override { if( m_type == Ok ) m_value.~T(); } @@ -4916,7 +5684,7 @@ namespace detail { auto errorMessage() const -> std::string { return m_errorMessage; } protected: - virtual void enforceOk() const { + void enforceOk() const override { // !TBD: If no exceptions, std::terminate here or something switch( m_type ) { case ResultBase::LogicError: @@ -4996,46 +5764,32 @@ namespace detail { return ParserResult::ok( ParseResultType::Matched ); } - struct BoundRefBase { - BoundRefBase() = default; - BoundRefBase( BoundRefBase const & ) = delete; - BoundRefBase( BoundRefBase && ) = delete; - BoundRefBase &operator=( BoundRefBase const & ) = delete; - BoundRefBase &operator=( BoundRefBase && ) = delete; + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; - virtual ~BoundRefBase() = default; - - virtual auto isFlag() const -> bool = 0; + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { virtual auto setFlag( bool flag ) -> ParserResult = 0; - }; - - struct BoundValueRefBase : BoundRefBase { - auto isFlag() const -> bool override { return false; } - - auto setFlag( bool ) -> ParserResult override { - return ParserResult::logicError( "Flags can only be set on boolean fields" ); - } - }; - - struct BoundFlagRefBase : BoundRefBase { - auto isFlag() const -> bool override { return true; } - - auto setValue( std::string const &arg ) -> ParserResult override { - bool flag; - auto result = convertInto( arg, flag ); - if( result ) - setFlag( flag ); - return result; - } + virtual auto isFlag() const -> bool { return true; } }; template - struct BoundRef : BoundValueRefBase { + struct BoundValueRef : BoundValueRefBase { T &m_ref; - explicit BoundRef( T &ref ) : m_ref( ref ) {} + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} auto setValue( std::string const &arg ) -> ParserResult override { return convertInto( arg, m_ref ); @@ -5043,10 +5797,10 @@ namespace detail { }; template - struct BoundRef> : BoundValueRefBase { + struct BoundValueRef> : BoundValueRefBase { std::vector &m_ref; - explicit BoundRef( std::vector &ref ) : m_ref( ref ) {} + explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} auto isContainer() const -> bool override { return true; } @@ -5091,12 +5845,12 @@ namespace detail { template inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { - ArgType temp; + ArgType temp{}; auto result = convertInto( arg, temp ); return !result ? result : LambdaInvoker::ReturnType>::invoke( lambda, temp ); - }; + } template struct BoundLambda : BoundValueRefBase { @@ -5145,6 +5899,9 @@ namespace detail { public: template auto operator|( T const &other ) const -> Parser; + + template + auto operator+( T const &other ) const -> Parser; }; // Common code and state for Args and Opts @@ -5152,16 +5909,16 @@ namespace detail { class ParserRefImpl : public ComposableParserImpl { protected: Optionality m_optionality = Optionality::Optional; - std::shared_ptr m_ref; + std::shared_ptr m_ref; std::string m_hint; std::string m_description; - explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} + explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} public: template ParserRefImpl( T &ref, std::string const &hint ) - : m_ref( std::make_shared>( ref ) ), + : m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} @@ -5202,10 +5959,10 @@ namespace detail { class ExeName : public ComposableParserImpl { std::shared_ptr m_name; - std::shared_ptr m_ref; + std::shared_ptr m_ref; template - static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { return std::make_shared>( lambda) ; } @@ -5213,7 +5970,7 @@ namespace detail { ExeName() : m_name( std::make_shared( "" ) ) {} explicit ExeName( std::string &ref ) : ExeName() { - m_ref = std::make_shared>( ref ); + m_ref = std::make_shared>( ref ); } template @@ -5256,7 +6013,10 @@ namespace detail { if( token.type != TokenType::Argument ) return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); - auto result = m_ref->setValue( remainingTokens->token ); + assert( !m_ref->isFlag() ); + auto valueRef = static_cast( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); if( !result ) return InternalParseResult( result ); else @@ -5330,19 +6090,21 @@ namespace detail { auto const &token = *remainingTokens; if( isMatch(token.token ) ) { if( m_ref->isFlag() ) { - auto result = m_ref->setFlag( true ); + auto flagRef = static_cast( m_ref.get() ); + auto result = flagRef->setFlag( true ); if( !result ) return InternalParseResult( result ); if( result.value() == ParseResultType::ShortCircuitAll ) return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); } else { + auto valueRef = static_cast( m_ref.get() ); ++remainingTokens; if( !remainingTokens ) return InternalParseResult::runtimeError( "Expected argument following " + token.token ); auto const &argToken = *remainingTokens; if( argToken.type != TokenType::Argument ) return InternalParseResult::runtimeError( "Expected argument following " + token.token ); - auto result = m_ref->setValue( argToken.token ); + auto result = valueRef->setValue( argToken.token ); if( !result ) return InternalParseResult( result ); if( result.value() == ParseResultType::ShortCircuitAll ) @@ -5418,6 +6180,12 @@ namespace detail { return Parser( *this ) |= other; } + // Forward deprecated interface with '+' instead of '|' + template + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + auto getHelpColumns() const -> std::vector { std::vector cols; for (auto const &o : m_options) { @@ -5457,6 +6225,8 @@ namespace detail { for( auto const &cols : rows ) optWidth = (std::max)(optWidth, cols.left.size() + 2); + optWidth = (std::min)(optWidth, consoleWidth/2); + for( auto const &cols : rows ) { auto row = TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + @@ -5766,10 +6536,6 @@ namespace Catch { namespace Catch { - SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} bool SourceLineInfo::empty() const noexcept { return file[0] == '\0'; } @@ -5789,10 +6555,6 @@ namespace Catch { return os; } - bool isTrue( bool value ){ return value; } - bool alwaysTrue() { return true; } - bool alwaysFalse() { return false; } - std::string StreamEndStop::operator+() const { return std::string(); } @@ -5804,6 +6566,21 @@ namespace Catch { // end catch_common.cpp // start catch_config.cpp +// start catch_enforce.h + +#include +#include + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( static_cast( Catch::ReusableStringStream().get() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); +#define CATCH_ERROR( msg ) \ + throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h namespace Catch { Config::Config( ConfigData const& data ) @@ -5853,16 +6630,7 @@ namespace Catch { Verbosity Config::verbosity() const { return m_data.verbosity; } IStream const* Config::openStream() { - if( m_data.outputFilename.empty() ) - return new CoutStream(); - else if( m_data.outputFilename[0] == '%' ) { - if( m_data.outputFilename == "%debug" ) - return new DebugOutStream(); - else - CATCH_ERROR( "Unrecognised stream: '" << m_data.outputFilename << "'" ); - } - else - return new FileStream( m_data.outputFilename ); + return Catch::makeStream(m_data.outputFilename); } } // end namespace Catch @@ -5889,36 +6657,8 @@ namespace Catch { } // end catch_errno_guard.h -// start catch_windows_h_proxy.h +#include - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif - -#ifdef __AFXDLL -#include -#else -#include -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h namespace Catch { namespace { @@ -5977,8 +6717,12 @@ namespace { case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); } } @@ -6036,8 +6780,10 @@ namespace { case Colour::BrightRed: return setColour( "[1;31m" ); case Colour::BrightGreen: return setColour( "[1;32m" ); case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); } } static IColourImpl* instance() { @@ -6131,7 +6877,7 @@ namespace Catch { return m_runner; } - virtual IConfigPtr getConfig() const override { + virtual IConfigPtr const& getConfig() const override { return m_config; } @@ -6156,21 +6902,16 @@ namespace Catch { IResultCapture* m_resultCapture = nullptr; }; - namespace { - Context* currentContext = nullptr; - } - IMutableContext& getCurrentMutableContext() { - if( !currentContext ) - currentContext = new Context(); - return *currentContext; - } - IContext& getCurrentContext() { - return getCurrentMutableContext(); + IMutableContext *IMutableContext::currentContext = nullptr; + + void IMutableContext::createContext() + { + currentContext = new Context(); } void cleanUpContext() { - delete currentContext; - currentContext = nullptr; + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; } IContext::~IContext() = default; IMutableContext::~IMutableContext() = default; @@ -6208,13 +6949,15 @@ namespace Catch { #ifdef CATCH_PLATFORM_MAC - #include - #include - #include - #include - #include +# include +# include +# include +# include +# include +# include +# include - namespace Catch { +namespace Catch { // The following function is taken directly from the following technical note: // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html @@ -6376,6 +7119,17 @@ namespace Catch { return Catch::Detail::stringify( [exception description] ); } #else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } return tryTranslators(); #endif } @@ -6406,84 +7160,19 @@ namespace Catch { // end catch_exception_translator_registry.cpp // start catch_fatal_condition.cpp -// start catch_fatal_condition.h +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif -#include - -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// - -# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) - -namespace Catch { - struct FatalConditionHandler { - void reset(); - }; -} - -# else // CATCH_CONFIG_WINDOWS_SEH is defined - -namespace Catch { - - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); - FatalConditionHandler(); - static void reset(); - ~FatalConditionHandler(); - - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - -} // namespace Catch - -# endif // CATCH_CONFIG_WINDOWS_SEH - -#else // Not Windows - assumed to be POSIX compatible ////////////////////////// - -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) - -namespace Catch { - struct FatalConditionHandler { - void reset(); - }; -} - -# else // CATCH_CONFIG_POSIX_SIGNALS is defined - -#include - -namespace Catch { - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions[];// [sizeof(signalDefs) / sizeof(SignalDefs)]; - static stack_t oldSigStack; - static char altStackMem[]; - - static void handleSignal( int sig ); - - FatalConditionHandler(); - ~FatalConditionHandler(); - static void reset(); - }; - -} // namespace Catch - -# endif // CATCH_CONFIG_POSIX_SIGNALS - -#endif // not Windows - -// end catch_fatal_condition.h +#if (defined(CATCH_PLATFORM_WINDOWS) && defined(CATCH_CONFIG_WINDOWS_SEH)) || defined(CATCH_CONFIG_POSIX_SIGNALS) namespace { // Report the error condition void reportFatal( char const * const message ) { Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); } } +#endif #if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// @@ -6635,6 +7324,10 @@ namespace Catch { # endif // CATCH_CONFIG_POSIX_SIGNALS #endif // not Windows + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif // end catch_fatal_condition.cpp // start catch_interfaces_capture.cpp @@ -6845,11 +7538,11 @@ namespace Catch { // end catch_interfaces_testcase.cpp // start catch_leak_detector.cpp -namespace Catch { - #ifdef CATCH_CONFIG_WINDOWS_CRTDBG #include +namespace Catch { + LeakDetector::LeakDetector() { int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); flag |= _CRTDBG_LEAK_CHECK_DF; @@ -6860,14 +7553,13 @@ namespace Catch { // Change this to leaking allocation's number to break there _CrtSetBreakAlloc(-1); } +} #else - LeakDetector::LeakDetector(){} + Catch::LeakDetector::LeakDetector() {} #endif - -} // end catch_leak_detector.cpp // start catch_list.cpp @@ -7000,13 +7692,14 @@ namespace Catch { } for( auto const& tagCount : tagCounts ) { - std::ostringstream oss; - oss << " " << std::setw(2) << tagCount.second.count << " "; + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); auto wrapper = Column( tagCount.second.all() ) .initialIndent( 0 ) - .indent( oss.str().size() ) + .indent( str.size() ) .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); - Catch::cout() << oss.str() << wrapper << '\n'; + Catch::cout() << str << wrapper << '\n'; } Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; return tagCounts.size(); @@ -7071,8 +7764,138 @@ using Matchers::Impl::MatcherBase; } // namespace Catch // end catch_matchers.cpp +// start catch_matchers_floating.cpp + +#include +#include +#include +#include + +namespace Catch { +namespace Matchers { +namespace Floating { +enum class FloatingPointKind : uint8_t { + Float, + Double +}; +} +} +} + +namespace { + +template +struct Converter; + +template <> +struct Converter { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + Converter(float f) { + std::memcpy(&i, &f, sizeof(f)); + } + int32_t i; +}; + +template <> +struct Converter { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + Converter(double d) { + std::memcpy(&i, &d, sizeof(d)); + } + int64_t i; +}; + +template +auto convert(T t) -> Converter { + return Converter(t); +} + +template +bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (std::isnan(lhs) || std::isnan(rhs)) { + return false; + } + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc.i < 0) != (rc.i < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + auto ulpDiff = std::abs(lc.i - rc.i); + return ulpDiff <= maxUlpDiff; +} + +} + +namespace Catch { +namespace Matchers { +namespace Floating { + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + if (m_margin < 0) { + throw std::domain_error("Allowed margin difference has to be >= 0"); + } + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= m_margin); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + if (m_ulps < 0) { + throw std::domain_error("Allowed ulp difference has to be >= 0"); + } + } + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case FloatingPointKind::Float: + return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps(matchee, m_target, m_ulps); + default: + throw std::domain_error("Unknown FloatingPointKind value"); + } + } + + std::string WithinUlpsMatcher::describe() const { + return "is within " + std::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + } + +}// namespace Floating + +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) { + return Floating::WithinAbsMatcher(target, margin); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.cpp // start catch_matchers_string.cpp +#include + namespace Catch { namespace Matchers { @@ -7134,6 +7957,21 @@ namespace Matchers { return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); } + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + } // namespace StdString StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { @@ -7149,11 +7987,22 @@ namespace Matchers { return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); } + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } + } // namespace Matchers } // namespace Catch // end catch_matchers_string.cpp // start catch_message.cpp +// start catch_uncaught_exceptions.h + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h namespace Catch { MessageInfo::MessageInfo( std::string const& _macroName, @@ -7193,11 +8042,10 @@ namespace Catch { } ScopedMessage::~ScopedMessage() { - if ( !std::uncaught_exception() ){ + if ( !uncaught_exceptions() ){ getResultCapture().popScopedMessage(m_info); } } - } // end namespace Catch // end catch_message.cpp // start catch_random_number_generator.cpp @@ -7463,6 +8311,7 @@ namespace Catch { delete getTheRegistryHub(); getTheRegistryHub() = nullptr; cleanUpContext(); + ReusableStringStream::cleanup(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); @@ -7515,172 +8364,66 @@ namespace Catch { } bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } - bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } } // end namespace Catch // end catch_result_type.cpp // start catch_run_context.cpp -// start catch_run_context.h -#include +#include +#include +#include namespace Catch { - struct IMutableContext; - - class StreamRedirect { + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; public: - StreamRedirect(std::ostream& stream, std::string& targetString); + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + ~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + }; - ~StreamRedirect(); - - private: - std::ostream& m_stream; - std::streambuf* m_prevBuf; - std::ostringstream m_oss; - std::string& m_targetString; + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto str() const -> std::string { return m_rss.str(); } }; // StdErr has two constituent streams in C++, std::cerr and std::clog // This means that we need to redirect 2 streams into 1 to keep proper - // order of writes and cannot use StreamRedirect on its own - class StdErrRedirect { + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; public: - StdErrRedirect(std::string& targetString); - ~StdErrRedirect(); - private: - std::streambuf* m_cerrBuf; - std::streambuf* m_clogBuf; - std::ostringstream m_oss; - std::string& m_targetString; + RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto str() const -> std::string { return m_rss.str(); } }; - /////////////////////////////////////////////////////////////////////////// - - class RunContext : public IResultCapture, public IRunner { - - public: - RunContext( RunContext const& ) = delete; - RunContext& operator =( RunContext const& ) = delete; - - explicit RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter); - - virtual ~RunContext(); - - void testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount); - void testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount); - - Totals runTest(TestCase const& testCase); - - IConfigPtr config() const; - IStreamingReporter& reporter() const; - - private: // IResultCapture - - void assertionStarting(AssertionInfo const& info) override; - void assertionEnded(AssertionResult const& result) override; - - bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; - bool testForMissingAssertions(Counts& assertions); - - void sectionEnded(SectionEndInfo const& endInfo) override; - void sectionEndedEarly(SectionEndInfo const& endInfo) override; - - void benchmarkStarting( BenchmarkInfo const& info ) override; - void benchmarkEnded( BenchmarkStats const& stats ) override; - - void pushScopedMessage(MessageInfo const& message) override; - void popScopedMessage(MessageInfo const& message) override; - - std::string getCurrentTestName() const override; - - const AssertionResult* getLastResult() const override; - - void exceptionEarlyReported() override; - - void handleFatalErrorCondition( StringRef message ) override; - - bool lastAssertionPassed() override; - - void assertionPassed() override; - - void assertionRun() override; - - public: - // !TBD We need to do this another way! - bool aborting() const override; - - private: - - void runCurrentTest(std::string& redirectedCout, std::string& redirectedCerr); - void invokeActiveTestCase(); - - private: - - void handleUnfinishedSections(); - - TestRunInfo m_runInfo; - IMutableContext& m_context; - TestCase const* m_activeTestCase = nullptr; - ITracker* m_testCaseTracker; - Option m_lastResult; - - IConfigPtr m_config; - Totals m_totals; - IStreamingReporterPtr m_reporter; - std::vector m_messages; - AssertionInfo m_lastAssertionInfo; - std::vector m_unfinishedSections; - std::vector m_activeSections; - TrackerContext m_trackerContext; - std::size_t m_prevPassed = 0; - bool m_shouldReportUnexpected = true; - }; - - IResultCapture& getResultCapture(); - -} // end namespace Catch - -// end catch_run_context.h - -#include -#include - -namespace Catch { - - StreamRedirect::StreamRedirect(std::ostream& stream, std::string& targetString) - : m_stream(stream), - m_prevBuf(stream.rdbuf()), - m_targetString(targetString) { - stream.rdbuf(m_oss.rdbuf()); - } - - StreamRedirect::~StreamRedirect() { - m_targetString += m_oss.str(); - m_stream.rdbuf(m_prevBuf); - } - - StdErrRedirect::StdErrRedirect(std::string & targetString) - :m_cerrBuf(cerr().rdbuf()), m_clogBuf(clog().rdbuf()), - m_targetString(targetString) { - cerr().rdbuf(m_oss.rdbuf()); - clog().rdbuf(m_oss.rdbuf()); - } - - StdErrRedirect::~StdErrRedirect() { - m_targetString += m_oss.str(); - cerr().rdbuf(m_cerrBuf); - clog().rdbuf(m_clogBuf); - } - RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) : m_runInfo(_config->name()), m_context(getCurrentMutableContext()), m_config(_config), m_reporter(std::move(reporter)), - m_lastAssertionInfo{ "", SourceLineInfo("",0), "", ResultDisposition::Normal } + m_lastAssertionInfo{ "", SourceLineInfo("",0), "", ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() ) { m_context.setRunner(this); m_context.setConfig(m_config); @@ -7748,27 +8491,33 @@ namespace Catch { return *m_reporter; } - void RunContext::assertionStarting(AssertionInfo const& info) { - m_reporter->assertionStarting( info ); - } void RunContext::assertionEnded(AssertionResult const & result) { if (result.getResultType() == ResultWas::Ok) { m_totals.assertions.passed++; + m_lastAssertionPassed = true; } else if (!result.isOk()) { + m_lastAssertionPassed = false; if( m_activeTestCase->getTestCaseInfo().okToFail() ) m_totals.assertions.failedButOk++; else m_totals.assertions.failed++; } + else { + m_lastAssertionPassed = true; + } // We have no use for the return value (whether messages should be cleared), because messages were made scoped // and should be let to clear themselves out. static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); // Reset working state - m_lastAssertionInfo = { "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}", m_lastAssertionInfo.resultDisposition }; + resetAssertionInfo(); m_lastResult = result; } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); @@ -7858,7 +8607,7 @@ namespace Catch { tempResult.message = message; AssertionResult result(m_lastAssertionInfo, tempResult); - getResultCapture().assertionEnded(result); + assertionEnded(result); handleUnfinishedSections(); @@ -7887,17 +8636,13 @@ namespace Catch { } bool RunContext::lastAssertionPassed() { - return m_totals.assertions.passed == (m_prevPassed + 1); + return m_lastAssertionPassed; } void RunContext::assertionPassed() { + m_lastAssertionPassed = true; ++m_totals.assertions.passed; - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"; - m_lastAssertionInfo.macroName = ""; - } - - void RunContext::assertionRun() { - m_prevPassed = m_totals.assertions.passed; + resetAssertionInfo(); } bool RunContext::aborting() const { @@ -7911,18 +8656,22 @@ namespace Catch { Counts prevAssertions = m_totals.assertions; double duration = 0; m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; try { - m_lastAssertionInfo = { "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal }; - - seedRng(*m_config); - - Timer timer; - timer.start(); if (m_reporter->getPreferences().shouldRedirectStdOut) { - StreamRedirect coutRedir(cout(), redirectedCout); - StdErrRedirect errRedir(redirectedCerr); + RedirectedStdOut redirectedStdOut; + RedirectedStdErr redirectedStdErr; + timer.start(); invokeActiveTestCase(); + redirectedCout += redirectedStdOut.str(); + redirectedCerr += redirectedStdErr.str(); + } else { + timer.start(); invokeActiveTestCase(); } duration = timer.getElapsedSeconds(); @@ -7931,20 +8680,18 @@ namespace Catch { } catch (...) { // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions // are reported without translation at the point of origin. - if (m_shouldReportUnexpected) { - AssertionHandler - ( m_lastAssertionInfo.macroName, - m_lastAssertionInfo.lineInfo, - m_lastAssertionInfo.capturedExpression, - m_lastAssertionInfo.resultDisposition ).useActiveException(); + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); } } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + m_testCaseTracker->close(); handleUnfinishedSections(); m_messages.clear(); - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); m_reporter->sectionEnded(testCaseSectionStats); } @@ -7966,6 +8713,112 @@ namespace Catch { m_unfinishedSections.clear(); } + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + IResultCapture& getResultCapture() { if (auto* capture = getCurrentContext().getResultCapture()) return *capture; @@ -7985,22 +8838,15 @@ namespace Catch { m_timer.start(); } -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 -#endif Section::~Section() { if( m_sectionIncluded ) { SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); - if( std::uncaught_exception() ) + if( uncaught_exceptions() ) getResultCapture().sectionEndedEarly( endInfo ); else getResultCapture().sectionEnded( endInfo ); } } -#if defined(_MSC_VER) -#pragma warning(pop) -#endif // This indicates whether the section should be executed or not Section::operator bool() const { @@ -8105,95 +8951,90 @@ namespace Catch { #include #include -namespace { - const int MaxExitCode = 255; - using Catch::IStreamingReporterPtr; - using Catch::IConfigPtr; - using Catch::Config; +namespace Catch { - IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { - auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); - CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + namespace { + const int MaxExitCode = 255; - return reporter; - } + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; + } #ifndef CATCH_CONFIG_DEFAULT_REPORTER #define CATCH_CONFIG_DEFAULT_REPORTER "console" #endif - IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { - auto const& reporterNames = config->getReporterNames(); - if (reporterNames.empty()) - return createReporter(CATCH_CONFIG_DEFAULT_REPORTER, config); + IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { + auto const& reporterNames = config->getReporterNames(); + if (reporterNames.empty()) + return createReporter(CATCH_CONFIG_DEFAULT_REPORTER, config); - IStreamingReporterPtr reporter; - for (auto const& name : reporterNames) - addReporter(reporter, createReporter(name, config)); - return reporter; - } + IStreamingReporterPtr reporter; + for (auto const& name : reporterNames) + addReporter(reporter, createReporter(name, config)); + return reporter; + } #undef CATCH_CONFIG_DEFAULT_REPORTER - void addListeners(IStreamingReporterPtr& reporters, IConfigPtr const& config) { - auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); - for (auto const& listener : listeners) - addReporter(reporters, listener->create(Catch::ReporterConfig(config))); - } - - Catch::Totals runTests(std::shared_ptr const& config) { - using namespace Catch; - IStreamingReporterPtr reporter = makeReporter(config); - addListeners(reporter, config); - - RunContext context(config, std::move(reporter)); - - Totals totals; - - context.testGroupStarting(config->name(), 1, 1); - - TestSpec testSpec = config->testSpec(); - if (!testSpec.hasFilters()) - testSpec = TestSpecParser(ITagAliasRegistry::get()).parse("~[.]").testSpec(); // All not hidden tests - - auto const& allTestCases = getAllTestCasesSorted(*config); - for (auto const& testCase : allTestCases) { - if (!context.aborting() && matchTest(testCase, testSpec, *config)) - totals += context.runTest(testCase); - else - context.reporter().skipTest(testCase); + void addListeners(IStreamingReporterPtr& reporters, IConfigPtr const& config) { + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) + addReporter(reporters, listener->create(Catch::ReporterConfig(config))); } - context.testGroupEnded(config->name(), totals, 1, 1); - return totals; - } + Catch::Totals runTests(std::shared_ptr const& config) { + IStreamingReporterPtr reporter = makeReporter(config); + addListeners(reporter, config); - void applyFilenamesAsTags(Catch::IConfig const& config) { - using namespace Catch; - auto& tests = const_cast&>(getAllTestCasesSorted(config)); - for (auto& testCase : tests) { - auto tags = testCase.tags; + RunContext context(config, std::move(reporter)); - std::string filename = testCase.lineInfo.file; - auto lastSlash = filename.find_last_of("\\/"); - if (lastSlash != std::string::npos) { - filename.erase(0, lastSlash); - filename[0] = '#'; + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + if (!testSpec.hasFilters()) + testSpec = TestSpecParser(ITagAliasRegistry::get()).parse("~[.]").testSpec(); // All not hidden tests + + auto const& allTestCases = getAllTestCasesSorted(*config); + for (auto const& testCase : allTestCases) { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); } - auto lastDot = filename.find_last_of('.'); - if (lastDot != std::string::npos) { - filename.erase(lastDot); - } - - tags.push_back(std::move(filename)); - setTags(testCase, tags); + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; } - } -} + void applyFilenamesAsTags(Catch::IConfig const& config) { + auto& tests = const_cast&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; -namespace Catch { + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } + } + + } // anon namespace Session::Session() { static bool alreadyInstantiated = false; @@ -8346,6 +9187,9 @@ namespace Catch { if( Option listed = list( config() ) ) return static_cast( *listed ); + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed return (std::min)( MaxExitCode, static_cast( runTests( m_config ).assertions.failed ) ); } catch( std::exception& ex ) { @@ -8377,106 +9221,202 @@ namespace Catch { // end catch_startup_exception_registry.cpp // start catch_stream.cpp -#include #include #include +#include +#include +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif namespace Catch { - template - class StreamBufImpl : public StreamBufBase { - char data[bufferSize]; - WriterF m_writer; - - public: - StreamBufImpl() { - setp( data, data + sizeof(data) ); - } - - ~StreamBufImpl() noexcept { - StreamBufImpl::sync(); - } - - private: - int overflow( int c ) override { - sync(); - - if( c != EOF ) { - if( pbase() == epptr() ) - m_writer( std::string( 1, static_cast( c ) ) ); - else - sputc( static_cast( c ) ); - } - return 0; - } - - int sync() override { - if( pbase() != pptr() ) { - m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); - setp( pbase(), epptr() ); - } - return 0; - } - }; - - /////////////////////////////////////////////////////////////////////////// - Catch::IStream::~IStream() = default; - FileStream::FileStream( std::string const& filename ) { - m_ofs.open( filename.c_str() ); - CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + namespace detail { namespace { + template + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( StringRef filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + std::unique_ptr> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + }} // namespace anon::detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( StringRef const &filename ) -> IStream const* { + if( filename.empty() ) + return new detail::CoutStream(); + else if( filename[0] == '%' ) { + if( filename == "%debug" ) + return new detail::DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); + } + else + return new detail::FileStream( filename ); } - std::ostream& FileStream::stream() const { - return m_ofs; - } + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector> m_streams; + std::vector m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + static StringStreams* s_instance; - struct OutputDebugWriter { + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( std::unique_ptr( new std::ostringstream ) ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } - void operator()( std::string const&str ) { - writeToDebugConsole( str ); + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + + // !TBD: put in TLS + static auto instance() -> StringStreams& { + if( !s_instance ) + s_instance = new StringStreams(); + return *s_instance; + } + static void cleanup() { + delete s_instance; + s_instance = nullptr; } }; - DebugOutStream::DebugOutStream() - : m_streamBuf( new StreamBufImpl() ), - m_os( m_streamBuf.get() ) - {} + StringStreams* StringStreams::s_instance = nullptr; - std::ostream& DebugOutStream::stream() const { - return m_os; + void ReusableStringStream::cleanup() { + StringStreams::cleanup(); } - // Store the streambuf from cout up-front because - // cout may get redirected when running tests - CoutStream::CoutStream() - : m_os( Catch::cout().rdbuf() ) + ReusableStringStream::ReusableStringStream() + : m_index( StringStreams::instance().add() ), + m_oss( StringStreams::instance().m_streams[m_index].get() ) {} - std::ostream& CoutStream::stream() const { - return m_os; + ReusableStringStream::~ReusableStringStream() { + static_cast( m_oss )->str(""); + m_oss->clear(); + StringStreams::instance().release( m_index ); } + auto ReusableStringStream::str() const -> std::string { + return static_cast( m_oss )->str(); + } + + /////////////////////////////////////////////////////////////////////////// + #ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions - std::ostream& cout() { - return std::cout; - } - std::ostream& cerr() { - return std::cerr; - } - std::ostream& clog() { - return std::clog; - } + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } #endif } -// end catch_stream.cpp -// start catch_streambuf.cpp -namespace Catch { - StreamBufBase::~StreamBufBase() = default; -} -// end catch_streambuf.cpp +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stream.cpp // start catch_string_manip.cpp #include @@ -8556,62 +9496,20 @@ namespace Catch { #endif #include -#include #include +#include + +namespace { + const uint32_t byte_2_lead = 0xC0; + const uint32_t byte_3_lead = 0xE0; + const uint32_t byte_4_lead = 0xF0; +} namespace Catch { - - auto getEmptyStringRef() -> StringRef { - static StringRef s_emptyStringRef(""); - return s_emptyStringRef; - } - - StringRef::StringRef() noexcept - : StringRef( getEmptyStringRef() ) - {} - - StringRef::StringRef( StringRef const& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ) - {} - - StringRef::StringRef( StringRef&& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ), - m_data( other.m_data ) - { - other.m_data = nullptr; - } - StringRef::StringRef( char const* rawChars ) noexcept - : m_start( rawChars ), - m_size( static_cast( std::strlen( rawChars ) ) ) - { - assert( rawChars != nullptr ); - } - - StringRef::StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - { - size_type rawSize = rawChars == nullptr ? 0 : static_cast( std::strlen( rawChars ) ); - if( rawSize < size ) - m_size = rawSize; - } - - StringRef::StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) + : StringRef( rawChars, static_cast(std::strlen(rawChars) ) ) {} - StringRef::~StringRef() noexcept { - delete[] m_data; - } - - auto StringRef::operator = ( StringRef other ) noexcept -> StringRef& { - swap( other ); - return *this; - } StringRef::operator std::string() const { return std::string( m_start, m_size ); } @@ -8665,25 +9563,17 @@ namespace Catch { return m_start[index]; } - auto StringRef::empty() const noexcept -> bool { - return m_size == 0; - } - - auto StringRef::size() const noexcept -> size_type { - return m_size; - } auto StringRef::numberOfCharacters() const noexcept -> size_type { size_type noChars = m_size; // Make adjustments for uft encodings for( size_type i=0; i < m_size; ++i ) { char c = m_start[i]; - if( ( c & 0b11000000 ) == 0b11000000 ) { - if( ( c & 0b11100000 ) == 0b11000000 ) + if( ( c & byte_2_lead ) == byte_2_lead ) { + noChars--; + if (( c & byte_3_lead ) == byte_3_lead ) + noChars--; + if( ( c & byte_4_lead ) == byte_4_lead ) noChars--; - else if( ( c & 0b11110000 ) == 0b11100000 ) - noChars-=2; - else if( ( c & 0b11111000 ) == 0b11110000 ) - noChars-=3; } } return noChars; @@ -8736,6 +9626,8 @@ namespace Catch { // end catch_tag_alias_autoregistrar.cpp // start catch_tag_alias_registry.cpp +#include + namespace Catch { TagAliasRegistry::~TagAliasRegistry() {} @@ -8784,6 +9676,7 @@ namespace Catch { #include #include #include +#include namespace Catch { @@ -8997,9 +9890,9 @@ namespace Catch { void TestRegistry::registerTest( TestCase const& testCase ) { std::string name = testCase.getTestCaseInfo().name; if( name.empty() ) { - std::ostringstream oss; - oss << "Anonymous test case " << ++m_unnamedCount; - return registerTest( testCase.withName( oss.str() ) ); + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( rss.str() ) ); } m_functions.push_back( testCase ); } @@ -9046,6 +9939,7 @@ namespace Catch { #include #include #include +#include #if defined(__clang__) # pragma clang diagnostic push @@ -9514,11 +10408,11 @@ namespace Catch { void Timer::start() { m_nanoseconds = getCurrentNanosecondsSinceEpoch(); } - auto Timer::getElapsedNanoseconds() const -> unsigned int { - return static_cast(getCurrentNanosecondsSinceEpoch() - m_nanoseconds); + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; } - auto Timer::getElapsedMicroseconds() const -> unsigned int { - return static_cast(getElapsedNanoseconds()/1000); + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; } auto Timer::getElapsedMilliseconds() const -> unsigned int { return static_cast(getElapsedMicroseconds()/1000); @@ -9537,6 +10431,12 @@ namespace Catch { # pragma clang diagnostic ignored "-Wglobal-constructors" #endif +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include #include namespace Catch { @@ -9572,21 +10472,25 @@ namespace Detail { } unsigned char const *bytes = static_cast(object); - std::ostringstream os; - os << "0x" << std::setfill('0') << std::hex; + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; for( ; i != end; i += inc ) - os << std::setw(2) << static_cast(bytes[i]); - return os.str(); + rss << std::setw(2) << static_cast(bytes[i]); + return rss.str(); } } template std::string fpToString( T value, int precision ) { - std::ostringstream oss; - oss << std::setprecision( precision ) + if (std::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision( precision ) << std::fixed << value; - std::string d = oss.str(); + std::string d = rss.str(); std::size_t i = d.find_last_not_of( '0' ); if( i != std::string::npos && i != d.size()-1 ) { if( d[i] == '.' ) @@ -9670,12 +10574,12 @@ std::string StringMaker::convert(long value) { return ::Catch::Detail::stringify(static_cast(value)); } std::string StringMaker::convert(long long value) { - std::ostringstream oss; - oss << value; + ReusableStringStream rss; + rss << value; if (value > Detail::hexThreshold) { - oss << " (0x" << std::hex << value << ')'; + rss << " (0x" << std::hex << value << ')'; } - return oss.str(); + return rss.str(); } std::string StringMaker::convert(unsigned int value) { @@ -9685,12 +10589,12 @@ std::string StringMaker::convert(unsigned long value) { return ::Catch::Detail::stringify(static_cast(value)); } std::string StringMaker::convert(unsigned long long value) { - std::ostringstream oss; - oss << value; + ReusableStringStream rss; + rss << value; if (value > Detail::hexThreshold) { - oss << " (0x" << std::hex << value << ')'; + rss << " (0x" << std::hex << value << ')'; } - return oss.str(); + return rss.str(); } std::string StringMaker::convert(bool b) { @@ -9732,6 +10636,13 @@ std::string StringMaker::convert(double value) { return fpToString(value, 10); } +std::string ratio_string::symbol() { return "a"; } +std::string ratio_string::symbol() { return "f"; } +std::string ratio_string::symbol() { return "p"; } +std::string ratio_string::symbol() { return "n"; } +std::string ratio_string::symbol() { return "u"; } +std::string ratio_string::symbol() { return "m"; } + } // end namespace Catch #if defined(__clang__) @@ -9794,6 +10705,20 @@ namespace Catch { } // end catch_totals.cpp +// start catch_uncaught_exceptions.cpp + +#include + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch +// end catch_uncaught_exceptions.cpp // start catch_version.cpp #include @@ -9826,7 +10751,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 0, 1, "", 0 ); + static Version version( 2, 1, 2, "", 0 ); return version; } @@ -9834,6 +10759,8 @@ namespace Catch { // end catch_version.cpp // start catch_wildcard_pattern.cpp +#include + namespace Catch { WildcardPattern::WildcardPattern( std::string const& pattern, @@ -9873,103 +10800,6 @@ namespace Catch { // end catch_wildcard_pattern.cpp // start catch_xmlwriter.cpp -// start catch_xmlwriter.h - -#include -#include - -namespace Catch { - - class XmlEncode { - public: - enum ForWhat { ForTextNodes, ForAttributes }; - - XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); - - void encodeTo( std::ostream& os ) const; - - friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); - - private: - std::string m_str; - ForWhat m_forWhat; - }; - - class XmlWriter { - public: - - class ScopedElement { - public: - ScopedElement( XmlWriter* writer ); - - ScopedElement( ScopedElement&& other ) noexcept; - ScopedElement& operator=( ScopedElement&& other ) noexcept; - - ~ScopedElement(); - - ScopedElement& writeText( std::string const& text, bool indent = true ); - - template - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } - - private: - mutable XmlWriter* m_writer = nullptr; - }; - - XmlWriter( std::ostream& os = Catch::cout() ); - ~XmlWriter(); - - XmlWriter( XmlWriter const& ) = delete; - XmlWriter& operator=( XmlWriter const& ) = delete; - - XmlWriter& startElement( std::string const& name ); - - ScopedElement scopedElement( std::string const& name ); - - XmlWriter& endElement(); - - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); - - XmlWriter& writeAttribute( std::string const& name, bool attribute ); - - template - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - m_oss.clear(); - m_oss.str(std::string()); - m_oss << attribute; - return writeAttribute( name, m_oss.str() ); - } - - XmlWriter& writeText( std::string const& text, bool indent = true ); - - XmlWriter& writeComment( std::string const& text ); - - void writeStylesheetRef( std::string const& url ); - - XmlWriter& writeBlankLine(); - - void ensureTagClosed(); - - private: - - void writeDeclaration(); - - void newlineIfNecessary(); - - bool m_tagIsOpen = false; - bool m_needsNewline = false; - std::vector m_tags; - std::string m_indent; - std::ostream& m_os; - std::ostringstream m_oss; - }; - -} - -// end catch_xmlwriter.h #include namespace Catch { @@ -10214,33 +11044,235 @@ namespace { return count == 1 ? std::string() : count == 2 ? "both " : "all " ; } -} + +} // anon namespace namespace Catch { +namespace { +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + Colour colour(Colour::ResultError); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : std::string(); + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion") << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } else if (totals.assertions.failed) { + Colour colour(Colour::ResultError); + out << + "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + } else { + Colour colour(Colour::ResultSuccess); + out << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << + " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} - struct CompactReporter : StreamingReporterBase { +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) {} - using StreamingReporterBase::StreamingReporterBase; + void print() { + printSourceInfo(); - ~CompactReporter() override; + itMessage = messages.begin(); - static std::string getDescription() { + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, passedString()); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); + else + printResultType(Colour::Error, failedString()); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, failedString()); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, failedString()); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, failedString()); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, failedString()); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, std::string const& passOrFail) const { + if (!passOrFail.empty()) { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue(std::string const& issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + Colour colour(dimColour()); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + { + Colour colour(dimColour()); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = dimColour()) { + if (itMessage == messages.end()) + return; + + // using messages.end() directly yields (or auto) compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast(std::distance(itMessage, itEnd)); + + { + Colour colourGuard(colour); + stream << " with " << pluralise(N, "message") << ':'; + } + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + Colour colourGuard(dimColour()); + stream << " and"; + } + } + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { return "Reports test results on a single line, suitable for IDEs"; } - ReporterPreferences getPreferences() const override { + ReporterPreferences CompactReporter::getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; } - void noMatchingTestCases( std::string const& spec ) override { + void CompactReporter::noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << '\'' << std::endl; } - void assertionStarting( AssertionInfo const& ) override {} + void CompactReporter::assertionStarting( AssertionInfo const& ) {} - bool assertionEnded( AssertionStats const& _assertionStats ) override { + bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; @@ -10259,231 +11291,19 @@ namespace Catch { return true; } - void sectionEnded(SectionStats const& _sectionStats) override { + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { if (m_config->showDurations() == ShowDurations::Always) { stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; } } - void testRunEnded( TestRunStats const& _testRunStats ) override { - printTotals( _testRunStats.totals ); + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( stream, _testRunStats.totals ); stream << '\n' << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } - private: - class AssertionPrinter { - public: - AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; - AssertionPrinter( AssertionPrinter const& ) = delete; - AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) - : stream( _stream ) - , result( _stats.assertionResult ) - , messages( _stats.infoMessages ) - , itMessage( _stats.infoMessages.begin() ) - , printInfoMessages( _printInfoMessages ) - {} - - void print() { - printSourceInfo(); - - itMessage = messages.begin(); - - switch( result.getResultType() ) { - case ResultWas::Ok: - printResultType( Colour::ResultSuccess, passedString() ); - printOriginalExpression(); - printReconstructedExpression(); - if ( ! result.hasExpression() ) - printRemainingMessages( Colour::None ); - else - printRemainingMessages(); - break; - case ResultWas::ExpressionFailed: - if( result.isOk() ) - printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); - else - printResultType( Colour::Error, failedString() ); - printOriginalExpression(); - printReconstructedExpression(); - printRemainingMessages(); - break; - case ResultWas::ThrewException: - printResultType( Colour::Error, failedString() ); - printIssue( "unexpected exception with message:" ); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::FatalErrorCondition: - printResultType( Colour::Error, failedString() ); - printIssue( "fatal error condition with message:" ); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::DidntThrowException: - printResultType( Colour::Error, failedString() ); - printIssue( "expected exception, got none" ); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::Info: - printResultType( Colour::None, "info" ); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::Warning: - printResultType( Colour::None, "warning" ); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::ExplicitFailure: - printResultType( Colour::Error, failedString() ); - printIssue( "explicitly" ); - printRemainingMessages( Colour::None ); - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - printResultType( Colour::Error, "** internal error **" ); - break; - } - } - - private: - void printSourceInfo() const { - Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ':'; - } - - void printResultType( Colour::Code colour, std::string const& passOrFail ) const { - if( !passOrFail.empty() ) { - { - Colour colourGuard( colour ); - stream << ' ' << passOrFail; - } - stream << ':'; - } - } - - void printIssue( std::string const& issue ) const { - stream << ' ' << issue; - } - - void printExpressionWas() { - if( result.hasExpression() ) { - stream << ';'; - { - Colour colour( dimColour() ); - stream << " expression was:"; - } - printOriginalExpression(); - } - } - - void printOriginalExpression() const { - if( result.hasExpression() ) { - stream << ' ' << result.getExpression(); - } - } - - void printReconstructedExpression() const { - if( result.hasExpandedExpression() ) { - { - Colour colour( dimColour() ); - stream << " for: "; - } - stream << result.getExpandedExpression(); - } - } - - void printMessage() { - if ( itMessage != messages.end() ) { - stream << " '" << itMessage->message << '\''; - ++itMessage; - } - } - - void printRemainingMessages( Colour::Code colour = dimColour() ) { - if ( itMessage == messages.end() ) - return; - - // using messages.end() directly yields (or auto) compilation error: - std::vector::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); - - { - Colour colourGuard( colour ); - stream << " with " << pluralise( N, "message" ) << ':'; - } - - for(; itMessage != itEnd; ) { - // If this assertion is a warning ignore any INFO messages - if( printInfoMessages || itMessage->type != ResultWas::Info ) { - stream << " '" << itMessage->message << '\''; - if ( ++itMessage != itEnd ) { - Colour colourGuard( dimColour() ); - stream << " and"; - } - } - } - } - - private: - std::ostream& stream; - AssertionResult const& result; - std::vector messages; - std::vector::const_iterator itMessage; - bool printInfoMessages; - }; - - // Colour, message variants: - // - white: No tests ran. - // - red: Failed [both/all] N test cases, failed [both/all] M assertions. - // - white: Passed [both/all] N test cases (no assertions). - // - red: Failed N tests cases, failed M assertions. - // - green: Passed [both/all] N tests cases with M assertions. - - void printTotals( const Totals& totals ) const { - if( totals.testCases.total() == 0 ) { - stream << "No tests ran."; - } - else if( totals.testCases.failed == totals.testCases.total() ) { - Colour colour( Colour::ResultError ); - const std::string qualify_assertions_failed = - totals.assertions.failed == totals.assertions.total() ? - bothOrAll( totals.assertions.failed ) : std::string(); - stream << - "Failed " << bothOrAll( totals.testCases.failed ) - << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << qualify_assertions_failed << - pluralise( totals.assertions.failed, "assertion" ) << '.'; - } - else if( totals.assertions.total() == 0 ) { - stream << - "Passed " << bothOrAll( totals.testCases.total() ) - << pluralise( totals.testCases.total(), "test case" ) - << " (no assertions)."; - } - else if( totals.assertions.failed ) { - Colour colour( Colour::ResultError ); - stream << - "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; - } - else { - Colour colour( Colour::ResultSuccess ); - stream << - "Passed " << bothOrAll( totals.testCases.passed ) - << pluralise( totals.testCases.passed, "test case" ) << - " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; - } - } - }; - - CompactReporter::~CompactReporter() {} + CompactReporter::~CompactReporter() {} CATCH_REGISTER_REPORTER( "compact", CompactReporter ) @@ -10497,629 +11317,609 @@ namespace Catch { #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled + // Note that 4062 (not all labels are handled + // and default is missing) is enabled #endif namespace Catch { - namespace { - std::size_t makeRatio( std::size_t number, std::size_t total ) { - std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; - return ( ratio == 0 && number > 0 ) ? 1 : ratio; +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; } + } - std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { - if( i > j && i > k ) - return i; - else if( j > k ) - return j; - else - return k; - } - - struct ColumnInfo { - enum Justification { Left, Right }; - std::string name; - int width; - Justification justification; - }; - struct ColumnBreak {}; - struct RowBreak {}; - - class TablePrinter { - std::ostream& m_os; - std::vector m_columnInfos; - std::ostringstream m_oss; - int m_currentColumn = -1; - bool m_isOpen = false; - - public: - TablePrinter( std::ostream& os, std::vector const& columnInfos ) - : m_os( os ), - m_columnInfos( columnInfos ) - {} - - auto columnInfos() const -> std::vector const& { - return m_columnInfos; - } - - void open() { - if( !m_isOpen ) { - m_isOpen = true; - *this << RowBreak(); - for( auto const& info : m_columnInfos ) - *this << info.name << ColumnBreak(); - *this << RowBreak(); - m_os << Catch::getLineOfChars<'-'>() << "\n"; - } - } - void close() { - if( m_isOpen ) { - *this << RowBreak(); - m_os << std::endl; - m_isOpen = false; - } - } - - template - friend TablePrinter& operator << ( TablePrinter& tp, T const& value ) { - tp.m_oss << value; - return tp; - } - - friend TablePrinter& operator << ( TablePrinter& tp, ColumnBreak ) { - auto colStr = tp.m_oss.str(); - // This takes account of utf8 encodings - auto strSize = Catch::StringRef( colStr ).numberOfCharacters(); - tp.m_oss.str(""); - tp.open(); - if( tp.m_currentColumn == static_cast(tp.m_columnInfos.size()-1) ) { - tp.m_currentColumn = -1; - tp.m_os << "\n"; - } - tp.m_currentColumn++; - - auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; - auto padding = ( strSize+2 < static_cast( colInfo.width ) ) - ? std::string( colInfo.width-(strSize+2), ' ' ) - : std::string(); - if( colInfo.justification == ColumnInfo::Left ) - tp.m_os << colStr << padding << " "; - else - tp.m_os << padding << colStr << " "; - return tp; - } - - friend TablePrinter& operator << ( TablePrinter& tp, RowBreak ) { - if( tp.m_currentColumn > 0 ) { - tp.m_os << "\n"; - tp.m_currentColumn = -1; - } - return tp; - } - }; - - class Duration { - enum class Unit { - Auto, - Nanoseconds, - Microseconds, - Milliseconds, - Seconds, - Minutes - }; - static const uint64_t s_nanosecondsInAMicrosecond = 1000; - static const uint64_t s_nanosecondsInAMillisecond = 1000*s_nanosecondsInAMicrosecond; - static const uint64_t s_nanosecondsInASecond = 1000*s_nanosecondsInAMillisecond; - static const uint64_t s_nanosecondsInAMinute = 60*s_nanosecondsInASecond; - - uint64_t m_inNanoseconds; - Unit m_units; - - public: - Duration( uint64_t inNanoseconds, Unit units = Unit::Auto ) - : m_inNanoseconds( inNanoseconds ), - m_units( units ) - { - if( m_units == Unit::Auto ) { - if( m_inNanoseconds < s_nanosecondsInAMicrosecond ) - m_units = Unit::Nanoseconds; - else if( m_inNanoseconds < s_nanosecondsInAMillisecond ) - m_units = Unit::Microseconds; - else if( m_inNanoseconds < s_nanosecondsInASecond ) - m_units = Unit::Milliseconds; - else if( m_inNanoseconds < s_nanosecondsInAMinute ) - m_units = Unit::Seconds; - else - m_units = Unit::Minutes; - } - - } - - auto value() const -> double { - switch( m_units ) { - case Unit::Microseconds: - return m_inNanoseconds / static_cast( s_nanosecondsInAMicrosecond ); - case Unit::Milliseconds: - return m_inNanoseconds / static_cast( s_nanosecondsInAMillisecond ); - case Unit::Seconds: - return m_inNanoseconds / static_cast( s_nanosecondsInASecond ); - case Unit::Minutes: - return m_inNanoseconds / static_cast( s_nanosecondsInAMinute ); - default: - return static_cast( m_inNanoseconds ); - } - } - auto unitsAsString() const -> std::string { - switch( m_units ) { - case Unit::Nanoseconds: - return "ns"; - case Unit::Microseconds: - return "µs"; - case Unit::Milliseconds: - return "ms"; - case Unit::Seconds: - return "s"; - case Unit::Minutes: - return "m"; - default: - return "** internal error **"; - } - - } - friend auto operator << ( std::ostream& os, Duration const& duration ) -> std::ostream& { - return os << duration.value() << " " << duration.unitsAsString(); - } - }; - } // end anon namespace - - struct ConsoleReporter : StreamingReporterBase { - TablePrinter m_tablePrinter; - - ConsoleReporter( ReporterConfig const& config ) - : StreamingReporterBase( config ), - m_tablePrinter( config.stream(), - { - { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH-32, ColumnInfo::Left }, - { "iters", 8, ColumnInfo::Right }, - { "elapsed ns", 14, ColumnInfo::Right }, - { "average", 14, ColumnInfo::Right } - } ) - {} - ~ConsoleReporter() override; - static std::string getDescription() { - return "Reports test results as plain lines of text"; - } - - void noMatchingTestCases( std::string const& spec ) override { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } - - void assertionStarting( AssertionInfo const& ) override { - } - - bool assertionEnded( AssertionStats const& _assertionStats ) override { - AssertionResult const& result = _assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - // Drop out if result was successful but we're not printing them. - if( !includeResults && result.getResultType() != ResultWas::Warning ) - return false; - - lazyPrint(); - - AssertionPrinter printer( stream, _assertionStats, includeResults ); - printer.print(); - stream << std::endl; - return true; - } - - void sectionStarting( SectionInfo const& _sectionInfo ) override { - m_headerPrinted = false; - StreamingReporterBase::sectionStarting( _sectionInfo ); - } - void sectionEnded( SectionStats const& _sectionStats ) override { - m_tablePrinter.close(); - if( _sectionStats.missingAssertions ) { - lazyPrint(); - Colour colour( Colour::ResultError ); - if( m_sectionStack.size() > 1 ) - stream << "\nNo assertions in section"; - else - stream << "\nNo assertions in test case"; - stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; - } - if( m_config->showDurations() == ShowDurations::Always ) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - if( m_headerPrinted ) { - m_headerPrinted = false; - } - StreamingReporterBase::sectionEnded( _sectionStats ); - } - - void benchmarkStarting( BenchmarkInfo const& info ) override { - lazyPrintWithoutClosingBenchmarkTable(); - - auto nameCol = Column( info.name ).width( m_tablePrinter.columnInfos()[0].width-2 ); - - bool firstLine = true; - for( auto line : nameCol ) { - if( !firstLine ) - m_tablePrinter << ColumnBreak() << ColumnBreak() << ColumnBreak(); - else - firstLine = false; - - m_tablePrinter << line << ColumnBreak(); - } - } - void benchmarkEnded( BenchmarkStats const& stats ) override { - Duration average( stats.elapsedTimeInNanoseconds/stats.iterations ); - m_tablePrinter - << stats.iterations << ColumnBreak() - << stats.elapsedTimeInNanoseconds << ColumnBreak() - << average << ColumnBreak(); - } - - void testCaseEnded( TestCaseStats const& _testCaseStats ) override { - m_tablePrinter.close(); - StreamingReporterBase::testCaseEnded( _testCaseStats ); - m_headerPrinted = false; - } - void testGroupEnded( TestGroupStats const& _testGroupStats ) override { - if( currentGroupInfo.used ) { - printSummaryDivider(); - stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; - printTotals( _testGroupStats.totals ); - stream << '\n' << std::endl; - } - StreamingReporterBase::testGroupEnded( _testGroupStats ); - } - void testRunEnded( TestRunStats const& _testRunStats ) override { - printTotalsDivider( _testRunStats.totals ); - printTotals( _testRunStats.totals ); - stream << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); - } - - private: - - class AssertionPrinter { - public: - AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; - AssertionPrinter( AssertionPrinter const& ) = delete; - AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) - : stream( _stream ), - stats( _stats ), - result( _stats.assertionResult ), - colour( Colour::None ), - message( result.getMessage() ), - messages( _stats.infoMessages ), - printInfoMessages( _printInfoMessages ) - { - switch( result.getResultType() ) { - case ResultWas::Ok: - colour = Colour::Success; - passOrFail = "PASSED"; - //if( result.hasMessage() ) - if( _stats.infoMessages.size() == 1 ) - messageLabel = "with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "with messages"; - break; - case ResultWas::ExpressionFailed: - if( result.isOk() ) { - colour = Colour::Success; - passOrFail = "FAILED - but was ok"; - } - else { - colour = Colour::Error; - passOrFail = "FAILED"; - } - if( _stats.infoMessages.size() == 1 ) - messageLabel = "with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "with messages"; - break; - case ResultWas::ThrewException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to unexpected exception with "; - if (_stats.infoMessages.size() == 1) - messageLabel += "message"; - if (_stats.infoMessages.size() > 1) - messageLabel += "messages"; - break; - case ResultWas::FatalErrorCondition: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to a fatal error condition"; - break; - case ResultWas::DidntThrowException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "because no exception was thrown where one was expected"; - break; - case ResultWas::Info: - messageLabel = "info"; - break; - case ResultWas::Warning: - messageLabel = "warning"; - break; - case ResultWas::ExplicitFailure: - passOrFail = "FAILED"; - colour = Colour::Error; - if( _stats.infoMessages.size() == 1 ) - messageLabel = "explicitly with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "explicitly with messages"; - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - passOrFail = "** internal error **"; - colour = Colour::Error; - break; - } - } - - void print() const { - printSourceInfo(); - if( stats.totals.assertions.total() > 0 ) { - if( result.isOk() ) - stream << '\n'; - printResultType(); - printOriginalExpression(); - printReconstructedExpression(); - } - else { - stream << '\n'; - } - printMessage(); - } - - private: - void printResultType() const { - if( !passOrFail.empty() ) { - Colour colourGuard( colour ); - stream << passOrFail << ":\n"; - } - } - void printOriginalExpression() const { - if( result.hasExpression() ) { - Colour colourGuard( Colour::OriginalExpression ); - stream << " "; - stream << result.getExpressionInMacro(); - stream << '\n'; - } - } - void printReconstructedExpression() const { - if( result.hasExpandedExpression() ) { - stream << "with expansion:\n"; - Colour colourGuard( Colour::ReconstructedExpression ); - stream << Column( result.getExpandedExpression() ).indent(2) << '\n'; - } - } - void printMessage() const { - if( !messageLabel.empty() ) - stream << messageLabel << ':' << '\n'; - for( auto const& msg : messages ) { - // If this assertion is a warning ignore any INFO messages - if( printInfoMessages || msg.type != ResultWas::Info ) - stream << Column( msg.message ).indent(2) << '\n'; - } - } - void printSourceInfo() const { - Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ": "; - } - - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - Colour::Code colour; - std::string passOrFail; - std::string messageLabel; - std::string message; - std::vector messages; - bool printInfoMessages; - }; - - void lazyPrint() { - - m_tablePrinter.close(); - lazyPrintWithoutClosingBenchmarkTable(); - } - - void lazyPrintWithoutClosingBenchmarkTable() { - - if( !currentTestRunInfo.used ) - lazyPrintRunInfo(); - if( !currentGroupInfo.used ) - lazyPrintGroupInfo(); - - if( !m_headerPrinted ) { - printTestCaseAndSectionHeader(); - m_headerPrinted = true; - } - } - void lazyPrintRunInfo() { - stream << '\n' << getLineOfChars<'~'>() << '\n'; - Colour colour( Colour::SecondaryText ); - stream << currentTestRunInfo->name - << " is a Catch v" << libraryVersion() << " host application.\n" - << "Run with -? for options\n\n"; - - if( m_config->rngSeed() != 0 ) - stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; - - currentTestRunInfo.used = true; - } - void lazyPrintGroupInfo() { - if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { - printClosedHeader( "Group: " + currentGroupInfo->name ); - currentGroupInfo.used = true; - } - } - void printTestCaseAndSectionHeader() { - assert( !m_sectionStack.empty() ); - printOpenHeader( currentTestCaseInfo->name ); - - if( m_sectionStack.size() > 1 ) { - Colour colourGuard( Colour::Headers ); - - auto - it = m_sectionStack.begin()+1, // Skip first section (test case) - itEnd = m_sectionStack.end(); - for( ; it != itEnd; ++it ) - printHeaderString( it->name, 2 ); - } - - SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; - - if( !lineInfo.empty() ){ - stream << getLineOfChars<'-'>() << '\n'; - Colour colourGuard( Colour::FileName ); - stream << lineInfo << '\n'; - } - stream << getLineOfChars<'.'>() << '\n' << std::endl; - } - - void printClosedHeader( std::string const& _name ) { - printOpenHeader( _name ); - stream << getLineOfChars<'.'>() << '\n'; - } - void printOpenHeader( std::string const& _name ) { - stream << getLineOfChars<'-'>() << '\n'; - { - Colour colourGuard( Colour::Headers ); - printHeaderString( _name ); - } - } - - // if string has a : in first line will set indent to follow it on - // subsequent lines - void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { - std::size_t i = _string.find( ": " ); - if( i != std::string::npos ) - i+=2; - else - i = 0; - stream << Column( _string ).indent( indent+i ).initialIndent( indent ) << '\n'; - } - - struct SummaryColumn { - - SummaryColumn( std::string const& _label, Colour::Code _colour ) - : label( _label ), - colour( _colour ) - {} - SummaryColumn addRow( std::size_t count ) { - std::ostringstream oss; - oss << count; - std::string row = oss.str(); - for( auto& oldRow : rows ) { - while( oldRow.size() < row.size() ) - oldRow = ' ' + oldRow; - while( oldRow.size() > row.size() ) - row = ' ' + row; - } - rows.push_back( row ); - return *this; - } - - std::string label; - Colour::Code colour; - std::vector rows; - - }; - - void printTotals( Totals const& totals ) { - if( totals.testCases.total() == 0 ) { - stream << Colour( Colour::Warning ) << "No tests ran\n"; - } - else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { - stream << Colour( Colour::ResultSuccess ) << "All tests passed"; - stream << " (" - << pluralise( totals.assertions.passed, "assertion" ) << " in " - << pluralise( totals.testCases.passed, "test case" ) << ')' - << '\n'; - } - else { - - std::vector columns; - columns.push_back( SummaryColumn( "", Colour::None ) - .addRow( totals.testCases.total() ) - .addRow( totals.assertions.total() ) ); - columns.push_back( SummaryColumn( "passed", Colour::Success ) - .addRow( totals.testCases.passed ) - .addRow( totals.assertions.passed ) ); - columns.push_back( SummaryColumn( "failed", Colour::ResultError ) - .addRow( totals.testCases.failed ) - .addRow( totals.assertions.failed ) ); - columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) - .addRow( totals.testCases.failedButOk ) - .addRow( totals.assertions.failedButOk ) ); - - printSummaryRow( "test cases", columns, 0 ); - printSummaryRow( "assertions", columns, 1 ); - } - } - void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { - for( auto col : cols ) { - std::string value = col.rows[row]; - if( col.label.empty() ) { - stream << label << ": "; - if( value != "0" ) - stream << value; - else - stream << Colour( Colour::Warning ) << "- none -"; - } - else if( value != "0" ) { - stream << Colour( Colour::LightGrey ) << " | "; - stream << Colour( col.colour ) - << value << ' ' << col.label; - } - } + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + if (result.isOk()) + stream << '\n'; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { stream << '\n'; } + printMessage(); + } - void printTotalsDivider( Totals const& totals ) { - if( totals.testCases.total() > 0 ) { - std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); - std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); - std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); - while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) - findMax( failedRatio, failedButOkRatio, passedRatio )++; - while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) - findMax( failedRatio, failedButOkRatio, passedRatio )--; - - stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); - stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); - if( totals.testCases.allPassed() ) - stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); - else - stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); - } - else { - stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); - } +private: + void printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); stream << '\n'; } - void printSummaryDivider() { - stream << getLineOfChars<'-'>() << '\n'; + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } - private: - bool m_headerPrinted = false; + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; +}; + +std::size_t makeRatio(std::size_t number, std::size_t total) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : ratio; +} + +std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} + +struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; + +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; - CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + uint64_t m_inNanoseconds; + Unit m_units; - ConsoleReporter::~ConsoleReporter() {} +public: + explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); + default: + return static_cast(m_inNanoseconds); + } + } + auto unitsAsString() const -> std::string { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "µs"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << " " << duration.unitsAsString(); + } +}; +} // end anon namespace + +class TablePrinter { + std::ostream& m_os; + std::vector m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector columnInfos ) + : m_os( os ), + m_columnInfos( std::move( columnInfos ) ) {} + + auto columnInfos() const -> std::vector const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + for (auto const& info : m_columnInfos) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } + + template + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 2 < static_cast(colInfo.width)) + ? std::string(colInfo.width - (strSize + 2), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig const& config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + { + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, + { "iters", 8, ColumnInfo::Right }, + { "elapsed ns", 14, ColumnInfo::Right }, + { "average", 14, ColumnInfo::Right } + })) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases(std::string const& spec) { + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; + + lazyPrint(); + + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + Colour colour(Colour::ResultError); + if (m_sectionStack.size() > 1) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column( info.name ).width( static_cast( m_tablePrinter->columnInfos()[0].width - 2 ) ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { + Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + (*m_tablePrinter) + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { + if (currentGroupInfo.used) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals(_testGroupStats.totals); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded(_testGroupStats); +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); + + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour(Colour::SecondaryText); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if (m_config->rngSeed() != 0) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; +} +void ConsoleReporter::lazyPrintGroupInfo() { + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + Colour colourGuard(Colour::Headers); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if (!lineInfo.empty()) { + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard(Colour::Headers); + printHeaderString(_name); + } +} + +// if string has a : in first line will set indent to follow it on +// subsequent lines +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} + +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( std::move( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::size_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + +}; + +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << Colour(Colour::ResultSuccess) << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion") << " in " + << pluralise(totals.testCases.passed, "test case") << ')' + << '\n'; + } else { + + std::vector columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases", columns, 0); + printSummaryRow("assertions", columns, 1); + } +} +void ConsoleReporter::printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row) { + for (auto col : cols) { + std::string value = col.rows[row]; + if (col.label.empty()) { + stream << label << ": "; + if (value != "0") + stream << value; + else + stream << Colour(Colour::Warning) << "- none -"; + } else if (value != "0") { + stream << Colour(Colour::LightGrey) << " | "; + stream << Colour(col.colour) + << value << ' ' << col.label; + } + } + stream << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + stream << Colour(Colour::Error) << std::string(failedRatio, '='); + stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) + stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); + else + stream << Colour(Colour::Success) << std::string(passedRatio, '='); + } else { + stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; +} + +CATCH_REGISTER_REPORTER("console", ConsoleReporter) } // end namespace Catch @@ -11130,7 +11930,7 @@ namespace Catch { // start catch_reporter_junit.cpp #include - +#include #include #include @@ -11171,202 +11971,193 @@ namespace Catch { return it->substr(1); return std::string(); } - } + } // anonymous namespace - class JunitReporter : public CumulativeReporterBase { - public: - JunitReporter( ReporterConfig const& _config ) + JunitReporter::JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), xml( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = true; } - ~JunitReporter() override; - - static std::string getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; - } - - void noMatchingTestCases( std::string const& /*spec*/ ) override {} - - void testRunStarting( TestRunInfo const& runInfo ) override { - CumulativeReporterBase::testRunStarting( runInfo ); - xml.startElement( "testsuites" ); - } - - void testGroupStarting( GroupInfo const& groupInfo ) override { - suiteTimer.start(); - stdOutForSuite.str(""); - stdErrForSuite.str(""); - unexpectedExceptions = 0; - CumulativeReporterBase::testGroupStarting( groupInfo ); - } - - void testCaseStarting( TestCaseInfo const& testCaseInfo ) override { - m_okToFail = testCaseInfo.okToFail(); - } - bool assertionEnded( AssertionStats const& assertionStats ) override { - if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) - unexpectedExceptions++; - return CumulativeReporterBase::assertionEnded( assertionStats ); - } - - void testCaseEnded( TestCaseStats const& testCaseStats ) override { - stdOutForSuite << testCaseStats.stdOut; - stdErrForSuite << testCaseStats.stdErr; - CumulativeReporterBase::testCaseEnded( testCaseStats ); - } - - void testGroupEnded( TestGroupStats const& testGroupStats ) override { - double suiteTime = suiteTimer.getElapsedSeconds(); - CumulativeReporterBase::testGroupEnded( testGroupStats ); - writeGroup( *m_testGroups.back(), suiteTime ); - } - - void testRunEndedCumulative() override { - xml.endElement(); - } - - void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); - TestGroupStats const& stats = groupNode.value; - xml.writeAttribute( "name", stats.groupInfo.name ); - xml.writeAttribute( "errors", unexpectedExceptions ); - xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); - xml.writeAttribute( "tests", stats.totals.assertions.total() ); - xml.writeAttribute( "hostname", "tbd" ); // !TBD - if( m_config->showDurations() == ShowDurations::Never ) - xml.writeAttribute( "time", "" ); - else - xml.writeAttribute( "time", suiteTime ); - xml.writeAttribute( "timestamp", getCurrentTimestamp() ); - - // Write test cases - for( auto const& child : groupNode.children ) - writeTestCase( *child ); - - xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); - xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); - } - - void writeTestCase( TestCaseNode const& testCaseNode ) { - TestCaseStats const& stats = testCaseNode.value; - - // All test cases have exactly one section - which represents the - // test case itself. That section may have 0-n nested sections - assert( testCaseNode.children.size() == 1 ); - SectionNode const& rootSection = *testCaseNode.children.front(); - - std::string className = stats.testInfo.className; - - if( className.empty() ) { - className = fileNameTag(stats.testInfo.tags); - if ( className.empty() ) - className = "global"; - } - - if ( !m_config->name().empty() ) - className = m_config->name() + "." + className; - - writeSection( className, "", rootSection ); - } - - void writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { - std::string name = trim( sectionNode.stats.sectionInfo.name ); - if( !rootName.empty() ) - name = rootName + '/' + name; - - if( !sectionNode.assertions.empty() || - !sectionNode.stdOut.empty() || - !sectionNode.stdErr.empty() ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); - if( className.empty() ) { - xml.writeAttribute( "classname", name ); - xml.writeAttribute( "name", "root" ); - } - else { - xml.writeAttribute( "classname", className ); - xml.writeAttribute( "name", name ); - } - xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); - - writeAssertions( sectionNode ); - - if( !sectionNode.stdOut.empty() ) - xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); - if( !sectionNode.stdErr.empty() ) - xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); - } - for( auto const& childNode : sectionNode.childSections ) - if( className.empty() ) - writeSection( name, "", *childNode ); - else - writeSection( className, name, *childNode ); - } - - void writeAssertions( SectionNode const& sectionNode ) { - for( auto const& assertion : sectionNode.assertions ) - writeAssertion( assertion ); - } - void writeAssertion( AssertionStats const& stats ) { - AssertionResult const& result = stats.assertionResult; - if( !result.isOk() ) { - std::string elementName; - switch( result.getResultType() ) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - elementName = "failure"; - break; - case ResultWas::ExpressionFailed: - elementName = "failure"; - break; - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } - - XmlWriter::ScopedElement e = xml.scopedElement( elementName ); - - xml.writeAttribute( "message", result.getExpandedExpression() ); - xml.writeAttribute( "type", result.getTestMacroName() ); - - std::ostringstream oss; - if( !result.getMessage().empty() ) - oss << result.getMessage() << '\n'; - for( auto const& msg : stats.infoMessages ) - if( msg.type == ResultWas::Info ) - oss << msg.message << '\n'; - - oss << "at " << result.getSourceInfo(); - xml.writeText( oss.str(), false ); - } - } - - XmlWriter xml; - Timer suiteTimer; - std::ostringstream stdOutForSuite; - std::ostringstream stdErrForSuite; - unsigned int unexpectedExceptions = 0; - bool m_okToFail = false; - }; - JunitReporter::~JunitReporter() {} + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + void JunitReporter::testRunEndedCumulative() { + xml.endElement(); + } + + void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; + } + + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; + + writeSection( className, "", rootSection ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode ); + else + writeSection( className, name, *childNode ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + ReusableStringStream rss; + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), false ); + } + } + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) } // end namespace Catch @@ -11475,211 +12266,200 @@ namespace Catch { #endif namespace Catch { - class XmlReporter : public StreamingReporterBase { - public: - XmlReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_xml(_config.stream()) - { - m_reporterPrefs.shouldRedirectStdOut = true; - } + XmlReporter::XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } - ~XmlReporter() override; + XmlReporter::~XmlReporter() = default; - static std::string getDescription() { - return "Reports test results as an XML document"; - } + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } - virtual std::string getStylesheetRef() const { - return std::string(); - } + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } - void writeSourceInfo( SourceLineInfo const& sourceInfo ) { - m_xml - .writeAttribute( "filename", sourceInfo.file ) - .writeAttribute( "line", sourceInfo.line ); - } + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } - public: // StreamingReporterBase + void XmlReporter::noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } - void noMatchingTestCases( std::string const& s ) override { - StreamingReporterBase::noMatchingTestCases( s ); - } + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } - void testRunStarting( TestRunInfo const& testInfo ) override { - StreamingReporterBase::testRunStarting( testInfo ); - std::string stylesheetRef = getStylesheetRef(); - if( !stylesheetRef.empty() ) - m_xml.writeStylesheetRef( stylesheetRef ); - m_xml.startElement( "Catch" ); - if( !m_config->name().empty() ) - m_xml.writeAttribute( "name", m_config->name() ); - } + void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } - void testGroupStarting( GroupInfo const& groupInfo ) override { - StreamingReporterBase::testGroupStarting( groupInfo ); - m_xml.startElement( "Group" ) - .writeAttribute( "name", groupInfo.name ); - } + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); - void testCaseStarting( TestCaseInfo const& testInfo ) override { - StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement( "TestCase" ) - .writeAttribute( "name", trim( testInfo.name ) ) - .writeAttribute( "description", testInfo.description ) - .writeAttribute( "tags", testInfo.tagsAsString() ); + writeSourceInfo( testInfo.lineInfo ); - writeSourceInfo( testInfo.lineInfo ); + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } - if ( m_config->showDurations() == ShowDurations::Always ) - m_testCaseTimer.start(); + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + writeSourceInfo( sectionInfo.lineInfo ); m_xml.ensureTagClosed(); } + } - void sectionStarting( SectionInfo const& sectionInfo ) override { - StreamingReporterBase::sectionStarting( sectionInfo ); - if( m_sectionDepth++ > 0 ) { - m_xml.startElement( "Section" ) - .writeAttribute( "name", trim( sectionInfo.name ) ) - .writeAttribute( "description", sectionInfo.description ); - writeSourceInfo( sectionInfo.lineInfo ); - m_xml.ensureTagClosed(); - } - } + void XmlReporter::assertionStarting( AssertionInfo const& ) { } - void assertionStarting( AssertionInfo const& ) override { } + bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { - bool assertionEnded( AssertionStats const& assertionStats ) override { + AssertionResult const& result = assertionStats.assertionResult; - AssertionResult const& result = assertionStats.assertionResult; + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - if( includeResults ) { - // Print any info messages in tags. - for( auto const& msg : assertionStats.infoMessages ) { - if( msg.type == ResultWas::Info ) { - m_xml.scopedElement( "Info" ) - .writeText( msg.message ); - } else if ( msg.type == ResultWas::Warning ) { - m_xml.scopedElement( "Warning" ) - .writeText( msg.message ); - } + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); } } + } - // Drop out if result was successful but we're not printing them. - if( !includeResults && result.getResultType() != ResultWas::Warning ) - return true; - - // Print the expression if there is one. - if( result.hasExpression() ) { - m_xml.startElement( "Expression" ) - .writeAttribute( "success", result.succeeded() ) - .writeAttribute( "type", result.getTestMacroName() ); - - writeSourceInfo( result.getSourceInfo() ); - - m_xml.scopedElement( "Original" ) - .writeText( result.getExpression() ); - m_xml.scopedElement( "Expanded" ) - .writeText( result.getExpandedExpression() ); - } - - // And... Print a result applicable to each result type. - switch( result.getResultType() ) { - case ResultWas::ThrewException: - m_xml.startElement( "Exception" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - case ResultWas::FatalErrorCondition: - m_xml.startElement( "FatalErrorCondition" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - case ResultWas::Info: - m_xml.scopedElement( "Info" ) - .writeText( result.getMessage() ); - break; - case ResultWas::Warning: - // Warning will already have been written - break; - case ResultWas::ExplicitFailure: - m_xml.startElement( "Failure" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - default: - break; - } - - if( result.hasExpression() ) - m_xml.endElement(); - + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); } - void sectionEnded( SectionStats const& sectionStats ) override { - StreamingReporterBase::sectionEnded( sectionStats ); - if( --m_sectionDepth > 0 ) { - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); - e.writeAttribute( "successes", sectionStats.assertions.passed ); - e.writeAttribute( "failures", sectionStats.assertions.failed ); - e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); - + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); m_xml.endElement(); - } + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; } - void testCaseEnded( TestCaseStats const& testCaseStats ) override { - StreamingReporterBase::testCaseEnded( testCaseStats ); - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); - e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); - - if( !testCaseStats.stdOut.empty() ) - m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); - if( !testCaseStats.stdErr.empty() ) - m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); m_xml.endElement(); } + } - void testGroupEnded( TestGroupStats const& testGroupStats ) override { - StreamingReporterBase::testGroupEnded( testGroupStats ); - // TODO: Check testGroupStats.aborting and act accordingly. - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) - .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); - m_xml.endElement(); - } + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); - void testRunEnded( TestRunStats const& testRunStats ) override { - StreamingReporterBase::testRunEnded( testRunStats ); - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testRunStats.totals.assertions.passed ) - .writeAttribute( "failures", testRunStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); - m_xml.endElement(); - } + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); - private: - Timer m_testCaseTimer; - XmlWriter m_xml; - int m_sectionDepth = 0; - }; + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } - XmlReporter::~XmlReporter() {} CATCH_REGISTER_REPORTER( "xml", XmlReporter ) } // end namespace Catch @@ -11739,6 +12519,8 @@ int main (int argc, char * const argv[]) { // end catch_default_main.hpp #endif +#if !defined(CATCH_CONFIG_IMPL_ONLY) + #ifdef CLARA_CONFIG_MAIN_NOT_DEFINED # undef CLARA_CONFIG_MAIN #endif @@ -11993,6 +12775,8 @@ using Catch::Detail::Approx; #endif +#endif // ! CATCH_CONFIG_IMPL_ONLY + // start catch_reenable_warnings.h diff --git a/test/thirdparty/nlohmann/json.hpp b/test/thirdparty/nlohmann/json.hpp index a704d86..3dcb834 100644 --- a/test/thirdparty/nlohmann/json.hpp +++ b/test/thirdparty/nlohmann/json.hpp @@ -1,11 +1,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.1 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29,39 +29,100 @@ SOFTWARE. #ifndef NLOHMANN_JSON_HPP #define NLOHMANN_JSON_HPP -#include // all_of, copy, fill, find, for_each, none_of, remove, reverse, transform -#include // array +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 1 +#define NLOHMANN_JSON_VERSION_PATCH 1 + +#include // all_of, find, for_each #include // assert #include // and, not, or -#include // lconv, localeconv -#include // isfinite, labs, ldexp, signbit #include // nullptr_t, ptrdiff_t, size_t -#include // int64_t, uint64_t -#include // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull -#include // memcpy, strlen -#include // forward_list -#include // function, hash, less +#include // hash, less #include // initializer_list -#include // hex -#include // istream, ostream -#include // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator -#include // numeric_limits -#include // locale -#include // map -#include // addressof, allocator, allocator_traits, unique_ptr +#include // istream, ostream +#include // iterator_traits, random_access_iterator_tag #include // accumulate -#include // stringstream -#include // getline, stoi, string, to_string -#include // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type -#include // declval, forward, make_pair, move, pair, swap +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string #include // vector +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + // exclude unsupported compilers #if defined(__clang__) #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" #endif -#elif defined(__GNUC__) +#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" #endif @@ -89,7 +150,7 @@ SOFTWARE. #endif // allow to disable exceptions -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && not defined(JSON_NOEXCEPTION) +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch(exception) @@ -99,6 +160,20 @@ SOFTWARE. #define JSON_CATCH(exception) if(false) #endif +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER +#endif + // manual branch prediction #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) @@ -108,31 +183,16 @@ SOFTWARE. #define JSON_UNLIKELY(x) x #endif -/*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 -*/ -namespace nlohmann -{ -template -struct adl_serializer; +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif -// forward declaration of basic_json (required to split the class) -template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer> -class basic_json; - -// Ugly macros to avoid uglier copy-paste when specializing basic_json -// This is only temporary and will be removed in 3.0 +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. #define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ template class ObjectType, \ @@ -147,328 +207,53 @@ class basic_json; NumberIntegerType, NumberUnsignedType, NumberFloatType, \ AllocatorType, JSONSerializer> - /*! -@brief unnamed namespace with internal helper functions +@brief Helper to determine whether there's a key_type for T. -This namespace collects some functions that could not be defined inside the -@ref basic_json class. +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +// #include + + +#include // not +#include // size_t +#include // numeric_limits +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // declval + +// #include + +// #include + + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. @since version 2.1.0 */ namespace detail { -//////////////// -// exceptions // -//////////////// - -/*! -@brief general exception of the @ref basic_json class - -Extension of std::exception objects with a member @a id for exception ids. - -@note To have nothrow-copy-constructible exceptions, we internally use - std::runtime_error which can cope with arbitrary-length error messages. - Intermediate strings are built with static functions and then passed to - the actual constructor. - -@since version 3.0.0 -*/ -class exception : public std::exception -{ - public: - /// returns the explanatory string - const char* what() const noexcept override - { - return m.what(); - } - - /// the id of the exception - const int id; - - protected: - exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} - - static std::string name(const std::string& ename, int id) - { - return "[json.exception." + ename + "." + std::to_string(id) + "] "; - } - - private: - /// an exception object as storage for error messages - std::runtime_error m; -}; - -/*! -@brief exception indicating a parse error - -This excpetion is thrown by the library when a parse error occurs. Parse errors -can occur during the deserialization of JSON text as well as when using JSON -Patch. - -Member @a byte holds the byte index of the last read character in the input -file. - -@note For an input with n bytes, 1 is the index of the first character and n+1 - is the index of the terminating null byte or the end of file. This also - holds true when reading a byte vector (CBOR or MessagePack). - -Exceptions have ids 1xx. - -name / id | example message | description ------------------------------- | --------------- | ------------------------- -json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. -json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. -json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. -json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. -json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. -json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number wihtout a leading `0`. -json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. -json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. -json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. -json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. -json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xf8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. -json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. - -@since version 3.0.0 -*/ -class parse_error : public exception -{ - public: - /*! - @brief create a parse error exception - @param[in] id the id of the exception - @param[in] byte_ the byte index where the error occurred (or 0 if the - position cannot be determined) - @param[in] what_arg the explanatory string - @return parse_error object - */ - static parse_error create(int id, std::size_t byte_, const std::string& what_arg) - { - std::string w = exception::name("parse_error", id) + "parse error" + - (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + - ": " + what_arg; - return parse_error(id, byte_, w.c_str()); - } - - /*! - @brief byte index of the parse error - - The byte index of the last read character in the input file. - - @note For an input with n bytes, 1 is the index of the first character and - n+1 is the index of the terminating null byte or the end of file. - This also holds true when reading a byte vector (CBOR or MessagePack). - */ - const std::size_t byte; - - private: - parse_error(int id_, std::size_t byte_, const char* what_arg) - : exception(id_, what_arg), byte(byte_) {} -}; - -/*! -@brief exception indicating errors with iterators - -Exceptions have ids 2xx. - -name / id | example message | description ------------------------------------ | --------------- | ------------------------- -json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. -json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. -json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. -json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. -json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. -json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. -json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. -json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. -json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. -json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. -json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. -json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. -json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. -json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). - -@since version 3.0.0 -*/ -class invalid_iterator : public exception -{ - public: - static invalid_iterator create(int id, const std::string& what_arg) - { - std::string w = exception::name("invalid_iterator", id) + what_arg; - return invalid_iterator(id, w.c_str()); - } - - private: - invalid_iterator(int id_, const char* what_arg) - : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating executing a member function with a wrong type - -Exceptions have ids 3xx. - -name / id | example message | description ------------------------------ | --------------- | ------------------------- -json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. -json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. -json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. -json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. -json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. -json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. -json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. -json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. -json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. -json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. -json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. -json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. -json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. -json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. - -@since version 3.0.0 -*/ -class type_error : public exception -{ - public: - static type_error create(int id, const std::string& what_arg) - { - std::string w = exception::name("type_error", id) + what_arg; - return type_error(id, w.c_str()); - } - - private: - type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating access out of the defined range - -Exceptions have ids 4xx. - -name / id | example message | description -------------------------------- | --------------- | ------------------------- -json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. -json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. -json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. -json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. -json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. -json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. - -@since version 3.0.0 -*/ -class out_of_range : public exception -{ - public: - static out_of_range create(int id, const std::string& what_arg) - { - std::string w = exception::name("out_of_range", id) + what_arg; - return out_of_range(id, w.c_str()); - } - - private: - out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating other errors - -Exceptions have ids 5xx. - -name / id | example message | description ------------------------------- | --------------- | ------------------------- -json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. -json.exception.other_error.502 | invalid object size for conversion | Some conversions to user-defined types impose constraints on the object size (e.g. std::pair) - -@since version 3.0.0 -*/ -class other_error : public exception -{ - public: - static other_error create(int id, const std::string& what_arg) - { - std::string w = exception::name("other_error", id) + what_arg; - return other_error(id, w.c_str()); - } - - private: - other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - - - -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - discarded ///< discarded by the the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string -- furthermore, each type is not smaller than itself - -@since version 1.0.0 -*/ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - static constexpr std::array order = {{ - 0, // null - 3, // object - 4, // array - 5, // string - 1, // boolean - 2, // integer - 2, // unsigned - 2, // float - } - }; - - // discarded values are not comparable - return lhs != value_t::discarded and rhs != value_t::discarded and - order[static_cast(lhs)] < order[static_cast(rhs)]; - -} - - ///////////// // helpers // ///////////// @@ -503,17 +288,15 @@ struct merge_and_renumber; template struct merge_and_renumber, index_sequence> - : index_sequence < I1..., (sizeof...(I1) + I2)... > - {}; + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; template struct make_index_sequence : merge_and_renumber < typename make_index_sequence < N / 2 >::type, - typename make_index_sequence < N - N / 2 >::type > -{}; + typename make_index_sequence < N - N / 2 >::type > {}; -template<> struct make_index_sequence<0> : index_sequence<> { }; -template<> struct make_index_sequence<1> : index_sequence<0> { }; +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; template using index_sequence_for = make_index_sequence; @@ -536,13 +319,934 @@ template struct conjunction : B1 {}; template struct conjunction : std::conditional, B1>::type {}; -template struct negation : std::integral_constant < bool, !B::value > {}; +template struct negation : std::integral_constant {}; // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; +//////////////////////// +// has_/is_ functions // +//////////////////////// +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // also check the return type of from_json + template::from_json( + std::declval(), std::declval()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +template +struct is_compatible_complete_type +{ + static constexpr bool value = + not std::is_base_of::value and + not std::is_same::value and + not is_basic_json_nested_type::value and + has_to_json::value; +}; + +template +struct is_compatible_type + : conjunction, + is_compatible_complete_type> +{ +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} +} + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] byte_ the byte index where the error occurred (or 0 if the + position cannot be determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} +} + +// #include + + +#include // array +#include // and +#include // size_t +#include // uint8_t + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself +- discarded values are not comparable + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; +} +} +} + +// #include + + +#include // transform +#include // array +#include // and, not +#include // forward_list +#include // inserter, front_inserter, end +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // pair, declval +#include // valarray + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// overloads for basic_json template parameters +template::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_UNLIKELY(not j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_UNLIKELY(not j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) +{ + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value and + not std::is_same::value and + std::is_constructible < + BasicJsonType, typename CompatibleArrayType::value_type >::value, + int > = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2> {}); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + auto inner_object = j.template get_ptr(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} +} + +// #include + + +#include // or, and, not +#include // begin, end +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ ////////////////// // constructors // ////////////////// @@ -637,8 +1341,7 @@ struct external_constructor } template::value, + enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { @@ -655,12 +1358,23 @@ struct external_constructor j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->reserve(arr.size()); - for (bool x : arr) + for (const bool x : arr) { j.m_value.array->push_back(x); } j.assert_invariant(); } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + j.assert_invariant(); + } }; template<> @@ -683,8 +1397,7 @@ struct external_constructor } template::value, int> = 0> + enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const CompatibleObjectType& obj) { using std::begin; @@ -696,181 +1409,25 @@ struct external_constructor } }; - -//////////////////////// -// has_/is_ functions // -//////////////////////// - -/*! -@brief Helper to determine whether there's a key_type for T. - -This helper is used to tell associative containers apart from other containers -such as sequence containers. For instance, `std::map` passes the test as it -contains a `mapped_type`, whereas `std::vector` fails the test. - -@sa http://stackoverflow.com/a/7728728/266378 -@since version 1.0.0, overworked in version 2.0.6 -*/ -#define NLOHMANN_JSON_HAS_HELPER(type) \ - template struct has_##type { \ - private: \ - template \ - static int detect(U &&); \ - static void detect(...); \ - public: \ - static constexpr bool value = \ - std::is_integral()))>::value; \ - } - -NLOHMANN_JSON_HAS_HELPER(mapped_type); -NLOHMANN_JSON_HAS_HELPER(key_type); -NLOHMANN_JSON_HAS_HELPER(value_type); -NLOHMANN_JSON_HAS_HELPER(iterator); - -#undef NLOHMANN_JSON_HAS_HELPER - - -template -struct is_compatible_object_type_impl : std::false_type {}; - -template -struct is_compatible_object_type_impl -{ - static constexpr auto value = - std::is_constructible::value and - std::is_constructible::value; -}; - -template -struct is_compatible_object_type -{ - static auto constexpr value = is_compatible_object_type_impl < - conjunction>, - has_mapped_type, - has_key_type>::value, - typename BasicJsonType::object_t, CompatibleObjectType >::value; -}; - -template -struct is_basic_json_nested_type -{ - static auto constexpr value = std::is_same::value or - std::is_same::value or - std::is_same::value or - std::is_same::value; -}; - -template -struct is_compatible_array_type -{ - static auto constexpr value = - conjunction>, - negation>, - negation>, - negation>, - has_value_type, - has_iterator>::value; -}; - -template -struct is_compatible_integer_type_impl : std::false_type {}; - -template -struct is_compatible_integer_type_impl -{ - // is there an assert somewhere on overflows? - using RealLimits = std::numeric_limits; - using CompatibleLimits = std::numeric_limits; - - static constexpr auto value = - std::is_constructible::value and - CompatibleLimits::is_integer and - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template -struct is_compatible_integer_type -{ - static constexpr auto value = - is_compatible_integer_type_impl < - std::is_integral::value and - not std::is_same::value, - RealIntegerType, CompatibleNumberIntegerType > ::value; -}; - - -// trait checking if JSONSerializer::from_json(json const&, udt&) exists -template -struct has_from_json -{ - private: - // also check the return type of from_json - template::from_json( - std::declval(), std::declval()))>::value>> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral>()))>::value; -}; - -// This trait checks if JSONSerializer::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template -struct has_non_default_from_json -{ - private: - template < - typename U, - typename = enable_if_t::from_json(std::declval()))>::value >> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral>()))>::value; -}; - -// This trait checks if BasicJsonType::json_serializer::to_json exists -template -struct has_to_json -{ - private: - template::to_json( - std::declval(), std::declval()))> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral>()))>::value; -}; - - ///////////// // to_json // ///////////// -template::value, int> = 0> +template::value, int> = 0> void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> + enable_if_t::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } -template +template void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); @@ -883,19 +1440,15 @@ void to_json(BasicJsonType& j, FloatType val) noexcept external_constructor::construct(j, static_cast(val)); } -template < - typename BasicJsonType, typename CompatibleNumberUnsignedType, - enable_if_t::value, int> = 0 > +template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } -template < - typename BasicJsonType, typename CompatibleNumberIntegerType, - enable_if_t::value, int> = 0 > +template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); @@ -915,42 +1468,43 @@ void to_json(BasicJsonType& j, const std::vector& e) external_constructor::construct(j, e); } -template < - typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < - is_compatible_array_type::value or - std::is_same::value, - int > = 0 > -void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +template::value or + std::is_same::value, + int> = 0> +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } -template +template::value, int> = 0> +void to_json(BasicJsonType& j, std::valarray arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } -template < - typename BasicJsonType, typename CompatibleObjectType, - enable_if_t::value, - int> = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } -template +template void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } template::value, - int> = 0> + enable_if_t::value, int> = 0> void to_json(BasicJsonType& j, T (&arr)[N]) { external_constructor::construct(j, arr); @@ -974,249 +1528,6 @@ void to_json(BasicJsonType& j, const std::tuple& t) to_json_tuple_impl(j, t, index_sequence_for {}); } -/////////////// -// from_json // -/////////////// - -// overloads for basic_json template parameters -template::value and - not std::is_same::value, - int> = 0> -void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast(j)) - { - case value_t::number_unsigned: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_integer: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast(*j.template get_ptr()); - break; - } - - default: - JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); - } -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) -{ - if (JSON_UNLIKELY(not j.is_boolean())) - { - JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); - } - b = *j.template get_ptr(); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) -{ - if (JSON_UNLIKELY(not j.is_string())) - { - JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); - } - s = *j.template get_ptr(); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) -{ - get_arithmetic_value(j, val); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) -{ - get_arithmetic_value(j, val); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) -{ - get_arithmetic_value(j, val); -} - -template::value, int> = 0> -void from_json(const BasicJsonType& j, EnumType& e) -{ - typename std::underlying_type::type val; - get_arithmetic_value(j, val); - e = static_cast(val); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - arr = *j.template get_ptr(); -} - -// forward_list doesn't have an insert method -template::value, int> = 0> -void from_json(const BasicJsonType& j, std::forward_list& l) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - - for (auto it = j.rbegin(), end = j.rend(); it != end; ++it) - { - l.push_front(it->template get()); - } -} - -template -void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) -{ - using std::end; - - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); -} - -template -auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) --> decltype( - arr.reserve(std::declval()), - void()) -{ - using std::end; - - arr.reserve(j.size()); - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); -} - -template -void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) -{ - for (std::size_t i = 0; i < N; ++i) - { - arr[i] = j.at(i).template get(); - } -} - -template::value and - std::is_convertible::value and - not std::is_same::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleArrayType& arr) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - - from_json_array_impl(j, arr, priority_tag<2> {}); -} - -template::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleObjectType& obj) -{ - if (JSON_UNLIKELY(not j.is_object())) - { - JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); - } - - auto inner_object = j.template get_ptr(); - using value_type = typename CompatibleObjectType::value_type; - std::transform( - inner_object->begin(), inner_object->end(), - std::inserter(obj, obj.begin()), - [](typename BasicJsonType::object_t::value_type const & p) - { - return value_type(p.first, p.second.template get()); - }); -} - -// overload for arithmetic types, not chosen for basic_json template arguments -// (BooleanType, etc..); note: Is it really necessary to provide explicit -// overloads for boolean_t etc. in case of a custom BooleanType which is not -// an arithmetic type? -template::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value and - not std::is_same::value, - int> = 0> -void from_json(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast(j)) - { - case value_t::number_unsigned: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_integer: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::boolean: - { - val = static_cast(*j.template get_ptr()); - break; - } - - default: - JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); - } -} - -template -void from_json(const BasicJsonType& j, std::pair& p) -{ - p = {j.at(0), j.at(1)}; -} - -template -void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) -{ - t = std::make_tuple(j.at(Idx)...); -} - -template -void from_json(const BasicJsonType& j, std::tuple& t) -{ - from_json_tuple_impl(j, t, index_sequence_for {}); -} - struct to_json_fn { private: @@ -1232,6 +1543,13 @@ struct to_json_fn { static_assert(sizeof(BasicJsonType) == 0, "could not find to_json() method in T's namespace"); + +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif } public: @@ -1242,165 +1560,137 @@ struct to_json_fn return call(j, std::forward(val), priority_tag<1> {}); } }; +} -struct from_json_fn +/// namespace to hold default `to_json` function +namespace { - private: - template - auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const - noexcept(noexcept(from_json(j, val))) - -> decltype(from_json(j, val), void()) - { - return from_json(j, val); - } +constexpr const auto& to_json = detail::static_const::value; +} +} - template - void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept - { - static_assert(sizeof(BasicJsonType) == 0, - "could not find from_json() method in T's namespace"); - } +// #include - public: - template - void operator()(const BasicJsonType& j, T& val) const - noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) - { - return call(j, val, priority_tag<1> {}); - } -}; -// taken from ranges-v3 -template -struct static_const +#include // min +#include // array +#include // assert +#include // size_t +#include // strlen +#include // streamsize, streamoff, streampos +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +// #include + + +namespace nlohmann +{ +namespace detail { - static constexpr T value{}; -}; - -template -constexpr T static_const::value; - //////////////////// // input adapters // //////////////////// -/// abstract input adapter interface +/*! +@brief abstract input adapter interface + +Produces a stream of std::char_traits::int_type characters from a +std::istream, a buffer, or some other input type. Accepts the return of exactly +one non-EOF character for future input. The int_type characters returned +consist of all valid char values as positive values (typically unsigned char), +plus an EOF value outside that range, specified by the value of the function +std::char_traits::eof(). This value is typically -1, but could be any +arbitrary value which is not a valid char value. +*/ struct input_adapter_protocol { - virtual int get_character() = 0; - virtual std::string read(std::size_t offset, std::size_t length) = 0; + /// get a character [0,255] or std::char_traits::eof(). + virtual std::char_traits::int_type get_character() = 0; + /// restore the last non-eof() character to input + virtual void unget_character() = 0; virtual ~input_adapter_protocol() = default; }; /// a type to simplify interfaces using input_adapter_t = std::shared_ptr; -/// input adapter for cached stream input -template -class cached_input_stream_adapter : public input_adapter_protocol +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter : public input_adapter_protocol { public: - explicit cached_input_stream_adapter(std::istream& i) - : is(i), start_position(is.tellg()) + ~input_stream_adapter() override { - fill_buffer(); + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags + is.clear(); + } + explicit input_stream_adapter(std::istream& i) + : is(i), sb(*i.rdbuf()) + { // skip byte order mark - if (fill_size >= 3 and buffer[0] == '\xEF' and buffer[1] == '\xBB' and buffer[2] == '\xBF') + std::char_traits::int_type c; + if ((c = get_character()) == 0xEF) { - buffer_pos += 3; - processed_chars += 3; - } - } - - ~cached_input_stream_adapter() override - { - // clear stream flags - is.clear(); - // We initially read a lot of characters into the buffer, and we may - // not have processed all of them. Therefore, we need to "rewind" the - // stream after the last processed char. - is.seekg(start_position); - is.ignore(static_cast(processed_chars)); - // clear stream flags - is.clear(); - } - - int get_character() override - { - // check if refilling is necessary and possible - if (buffer_pos == fill_size and not eof) - { - fill_buffer(); - - // check and remember that filling did not yield new input - if (fill_size == 0) + if ((c = get_character()) == 0xBB) { - eof = true; - return std::char_traits::eof(); + if ((c = get_character()) == 0xBF) + { + return; // Ignore BOM + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xBB'); } - - // the buffer is ready - buffer_pos = 0; + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xEF'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); // no byte order mark; process as usual } - - ++processed_chars; - assert(buffer_pos < buffer.size()); - return buffer[buffer_pos++] & 0xFF; } - std::string read(std::size_t offset, std::size_t length) override + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + + // std::istream/std::streambuf use std::char_traits::to_int_type, to + // ensure that std::char_traits::eof() and the character 0xFF do not + // end up as the same value, eg. 0xFFFFFFFF. + std::char_traits::int_type get_character() override { - // create buffer - std::string result(length, '\0'); + return sb.sbumpc(); + } - // save stream position - const auto current_pos = is.tellg(); - // save stream flags - const auto flags = is.rdstate(); - - // clear stream flags - is.clear(); - // set stream position - is.seekg(static_cast(offset)); - // read bytes - is.read(&result[0], static_cast(length)); - - // reset stream position - is.seekg(current_pos); - // reset stream flags - is.setstate(flags); - - return result; + void unget_character() override + { + sb.sungetc(); // is.unget() avoided for performance } private: - void fill_buffer() - { - // fill - is.read(buffer.data(), static_cast(buffer.size())); - // store number of bytes in the buffer - fill_size = static_cast(is.gcount()); - } - /// the associated input stream std::istream& is; - - /// chars returned via get_character() - std::size_t processed_chars = 0; - /// chars processed in the current buffer - std::size_t buffer_pos = 0; - - /// whether stream reached eof - bool eof = false; - /// how many chars have been copied to the buffer by last (re)fill - std::size_t fill_size = 0; - - /// position of the stream when we started - const std::streampos start_position; - - /// internal buffer - std::array buffer{{}}; + std::streambuf& sb; }; /// input adapter for buffer input @@ -1421,21 +1711,22 @@ class input_buffer_adapter : public input_adapter_protocol input_buffer_adapter(const input_buffer_adapter&) = delete; input_buffer_adapter& operator=(input_buffer_adapter&) = delete; - int get_character() noexcept override + std::char_traits::int_type get_character() noexcept override { if (JSON_LIKELY(cursor < limit)) { - return *(cursor++) & 0xFF; + return std::char_traits::to_int_type(*(cursor++)); } return std::char_traits::eof(); } - std::string read(std::size_t offset, std::size_t length) override + void unget_character() noexcept override { - // avoid reading too many characters - const auto max_length = static_cast(limit - start); - return std::string(start + offset, (std::min)(length, max_length - offset)); + if (JSON_LIKELY(cursor > start)) + { + --cursor; + } } private: @@ -1454,18 +1745,17 @@ class input_adapter /// input adapter for input stream input_adapter(std::istream& i) - : ia(std::make_shared>(i)) {} + : ia(std::make_shared(i)) {} /// input adapter for input stream input_adapter(std::istream&& i) - : ia(std::make_shared>(i)) {} + : ia(std::make_shared(i)) {} /// input adapter for buffer template::value and - std::is_integral< - typename std::remove_pointer::type>::value and + std::is_integral::type>::value and sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> input_adapter(CharT b, std::size_t l) @@ -1477,8 +1767,7 @@ class input_adapter template::value and - std::is_integral< - typename std::remove_pointer::type>::value and + std::is_integral::type>::value and sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> input_adapter(CharT b) @@ -1488,8 +1777,7 @@ class input_adapter /// input adapter for iterator range with contiguous storage template::iterator_category, - std::random_access_iterator_tag>::value, + std::is_same::iterator_category, std::random_access_iterator_tag>::value, int>::type = 0> input_adapter(IteratorType first, IteratorType last) { @@ -1527,13 +1815,10 @@ class input_adapter : input_adapter(std::begin(array), std::end(array)) {} /// input adapter for contiguous container - template < - class ContiguousContainer, - typename std::enable_if < - not std::is_pointer::value and - std::is_base_of()))>::iterator_category>::value, - int >::type = 0 > + template::value and + std::is_base_of()))>::iterator_category>::value, + int>::type = 0> input_adapter(const ContiguousContainer& c) : input_adapter(std::begin(c), std::end(c)) {} @@ -1546,10 +1831,34 @@ class input_adapter /// the actual adapter input_adapter_t ia = nullptr; }; +} +} -////////////////////// -// lexer and parser // -////////////////////// +// #include + + +#include // localeconv +#include // size_t +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // hex, uppercase +#include // setw, setfill +#include // stringstream +#include // char_traits, string +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// /*! @brief lexical analysis @@ -1629,7 +1938,7 @@ class lexer } explicit lexer(detail::input_adapter_t adapter) - : ia(adapter), decimal_point_char(get_decimal_point()) {} + : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} // delete because of pointer members lexer(const lexer&) = delete; @@ -1645,7 +1954,7 @@ class lexer { const auto loc = localeconv(); assert(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : loc->decimal_point[0]; + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } ///////////////////// @@ -1673,7 +1982,8 @@ class lexer assert(current == 'u'); int codepoint = 0; - for (int factor = 12; factor >= 0; factor -= 4) + const auto factors = { 12, 8, 4, 0 }; + for (const auto factor : factors) { get(); @@ -1706,7 +2016,13 @@ class lexer checks if it is inside the range. If a violation was detected, set up an error message and return false. Otherwise, return true. - @return true iff no range violation was detected + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected */ bool next_byte_in_range(std::initializer_list ranges) { @@ -1734,9 +2050,10 @@ class lexer @brief scan a string literal This function scans a string according to Sect. 7 of RFC 7159. While - scanning, bytes are escaped and copied into buffer yytext. Then the - function returns successfully, yytext is null-terminated and yylen - contains the number of bytes in the string. + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. @return token_type::value_string if string could be successfully scanned, token_type::parse_error otherwise @@ -1746,7 +2063,7 @@ class lexer */ token_type scan_string() { - // reset yytext (ignore opening quote) + // reset token_buffer (ignore opening quote) reset(); // we entered the function by reading an open quote @@ -1767,9 +2084,6 @@ class lexer // closing quote case '\"': { - // terminate yytext - add('\0'); - --yylen; return token_type::value_string; } @@ -1814,8 +2128,8 @@ class lexer // unicode escapes case 'u': { - int codepoint; const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 if (JSON_UNLIKELY(codepoint1 == -1)) { @@ -1840,6 +2154,7 @@ class lexer // check if codepoint2 is a low surrogate if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) { + // overwrite codepoint codepoint = // high surrogate occupies the most significant 22 bits (codepoint1 << 10) @@ -1869,27 +2184,24 @@ class lexer error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; return token_type::parse_error; } - - // only work with first code point - codepoint = codepoint1; } // result of the above calculation yields a proper codepoint assert(0x00 <= codepoint and codepoint <= 0x10FFFF); - // translate code point to bytes + // translate codepoint into bytes if (codepoint < 0x80) { // 1-byte characters: 0xxxxxxx (ASCII) add(codepoint); } - else if (codepoint <= 0x7ff) + else if (codepoint <= 0x7FF) { // 2-byte characters: 110xxxxx 10xxxxxx add(0xC0 | (codepoint >> 6)); add(0x80 | (codepoint & 0x3F)); } - else if (codepoint <= 0xffff) + else if (codepoint <= 0xFFFF) { // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx add(0xE0 | (codepoint >> 12)); @@ -1928,12 +2240,12 @@ class lexer case 0x07: case 0x08: case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: case 0x10: case 0x11: case 0x12: @@ -1944,12 +2256,12 @@ class lexer case 0x17: case 0x18: case 0x19: - case 0x1a: - case 0x1b: - case 0x1c: - case 0x1d: - case 0x1e: - case 0x1f: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: { error_message = "invalid string: control character must be escaped"; return token_type::parse_error; @@ -1965,12 +2277,12 @@ class lexer case 0x27: case 0x28: case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: case 0x30: case 0x31: case 0x32: @@ -1981,12 +2293,12 @@ class lexer case 0x37: case 0x38: case 0x39: - case 0x3a: - case 0x3b: - case 0x3c: - case 0x3d: - case 0x3e: - case 0x3f: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: case 0x40: case 0x41: case 0x42: @@ -1997,12 +2309,12 @@ class lexer case 0x47: case 0x48: case 0x49: - case 0x4a: - case 0x4b: - case 0x4c: - case 0x4d: - case 0x4e: - case 0x4f: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: case 0x50: case 0x51: case 0x52: @@ -2013,11 +2325,11 @@ class lexer case 0x57: case 0x58: case 0x59: - case 0x5a: - case 0x5b: - case 0x5d: - case 0x5e: - case 0x5f: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: case 0x60: case 0x61: case 0x62: @@ -2028,12 +2340,12 @@ class lexer case 0x67: case 0x68: case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: case 0x70: case 0x71: case 0x72: @@ -2044,48 +2356,48 @@ class lexer case 0x77: case 0x78: case 0x79: - case 0x7a: - case 0x7b: - case 0x7c: - case 0x7d: - case 0x7e: - case 0x7f: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: { add(current); break; } // U+0080..U+07FF: bytes C2..DF 80..BF - case 0xc2: - case 0xc3: - case 0xc4: - case 0xc5: - case 0xc6: - case 0xc7: - case 0xc8: - case 0xc9: - case 0xca: - case 0xcb: - case 0xcc: - case 0xcd: - case 0xce: - case 0xcf: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xd3: - case 0xd4: - case 0xd5: - case 0xd6: - case 0xd7: - case 0xd8: - case 0xd9: - case 0xda: - case 0xdb: - case 0xdc: - case 0xdd: - case 0xde: - case 0xdf: + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: { if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) { @@ -2095,7 +2407,7 @@ class lexer } // U+0800..U+0FFF: bytes E0 A0..BF 80..BF - case 0xe0: + case 0xE0: { if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { @@ -2106,20 +2418,20 @@ class lexer // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF - case 0xe1: - case 0xe2: - case 0xe3: - case 0xe4: - case 0xe5: - case 0xe6: - case 0xe7: - case 0xe8: - case 0xe9: - case 0xea: - case 0xeb: - case 0xec: - case 0xee: - case 0xef: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: { if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { @@ -2129,7 +2441,7 @@ class lexer } // U+D000..U+D7FF: bytes ED 80..9F 80..BF - case 0xed: + case 0xED: { if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { @@ -2139,7 +2451,7 @@ class lexer } // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF - case 0xf0: + case 0xF0: { if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { @@ -2149,9 +2461,9 @@ class lexer } // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF - case 0xf1: - case 0xf2: - case 0xf3: + case 0xF1: + case 0xF2: + case 0xF3: { if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { @@ -2161,7 +2473,7 @@ class lexer } // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF - case 0xf4: + case 0xF4: { if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { @@ -2223,7 +2535,7 @@ class lexer contains cycles, but any cycle can be left when EOF is read. Therefore, the function is guaranteed to terminate. - During scanning, the read bytes are stored in yytext. This string is + During scanning, the read bytes are stored in token_buffer. This string is then converted to a signed integer, an unsigned integer, or a floating-point number. @@ -2237,7 +2549,7 @@ class lexer */ token_type scan_number() { - // reset yytext to store the number's bytes + // reset token_buffer to store the number's bytes reset(); // the type of the parsed number; initially set to unsigned; will be @@ -2513,22 +2825,18 @@ scan_number_any2: scan_number_done: // unget the character after the number (we only read it to know that // we are done scanning a number) - --chars_read; - next_unget = true; + unget(); - // terminate token - add('\0'); - --yylen; + char* endptr = nullptr; + errno = 0; // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { - char* endptr = nullptr; - errno = 0; - const auto x = std::strtoull(yytext.data(), &endptr, 10); + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); // we checked the number format before - assert(endptr == yytext.data() + yylen); + assert(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { @@ -2541,12 +2849,10 @@ scan_number_done: } else if (number_type == token_type::value_integer) { - char* endptr = nullptr; - errno = 0; - const auto x = std::strtoll(yytext.data(), &endptr, 10); + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); // we checked the number format before - assert(endptr == yytext.data() + yylen); + assert(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { @@ -2560,7 +2866,11 @@ scan_number_done: // this code is reached if we parse a floating-point number or if an // integer conversion above failed - strtof(value_float, yytext.data(), nullptr); + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + return token_type::value_float; } @@ -2588,32 +2898,51 @@ scan_number_done: // input management ///////////////////// - /// reset yytext + /// reset token_buffer; current character is beginning of token void reset() noexcept { - yylen = 0; - start_pos = chars_read - 1; + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); } - /// get a character from the input - int get() + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + std::char_traits::int_type get() { ++chars_read; - return next_unget ? (next_unget = false, current) - : (current = ia->get_character()); + current = ia->get_character(); + if (JSON_LIKELY(current != std::char_traits::eof())) + { + token_string.push_back(std::char_traits::to_char_type(current)); + } + return current; } - /// add a character to yytext + /// unget current character (return it again on next get) + void unget() + { + --chars_read; + if (JSON_LIKELY(current != std::char_traits::eof())) + { + ia->unget_character(); + assert(token_string.size() != 0); + token_string.pop_back(); + } + } + + /// add a character to token_buffer void add(int c) { - // resize yytext if necessary; this condition is deemed unlikely, - // because we start with a 1024-byte buffer - if (JSON_UNLIKELY((yylen + 1 > yytext.capacity()))) - { - yytext.resize(2 * yytext.capacity(), '\0'); - } - assert(yylen < yytext.size()); - yytext[yylen++] = static_cast(c); + token_buffer.push_back(std::char_traits::to_char_type(c)); } public: @@ -2639,12 +2968,10 @@ scan_number_done: return value_float; } - /// return string value - const std::string get_string() + /// return current string value (implicitly resets the token; useful only once) + std::string move_string() { - // yytext cannot be returned as char*, because it may contain a null - // byte (parsed as "\u0000") - return std::string(yytext.data(), yylen); + return std::move(token_buffer); } ///////////////////// @@ -2657,22 +2984,16 @@ scan_number_done: return chars_read; } - /// return the last read token (for errors only) + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. std::string get_token_string() const { - // get the raw byte sequence of the last token - std::string s = ia->read(start_pos, chars_read - start_pos); - // escape control characters std::string result; - for (auto c : s) + for (const auto c : token_string) { - if (c == '\0' or c == std::char_traits::eof()) - { - // ignore EOF - continue; - } - else if ('\x00' <= c and c <= '\x1f') + if ('\x00' <= c and c <= '\x1F') { // escape control characters std::stringstream ss; @@ -2683,7 +3004,7 @@ scan_number_done: else { // add character as is - result.append(1, c); + result.push_back(c); } } @@ -2769,20 +3090,16 @@ scan_number_done: detail::input_adapter_t ia = nullptr; /// the current character - int current = std::char_traits::eof(); - - /// whether get() should return the last character again - bool next_unget = false; + std::char_traits::int_type current = std::char_traits::eof(); /// the number of characters read std::size_t chars_read = 0; - /// the start position of the current token - std::size_t start_pos = 0; + + /// raw input token string (for error messages) + std::vector token_string {}; /// buffer for variable-length tokens (numbers, strings) - std::vector yytext = std::vector(1024, '\0'); - /// current index in yytext - std::size_t yylen = 0; + std::string token_buffer {}; /// a description of occurred lexer errors const char* error_message = ""; @@ -2795,6 +3112,37 @@ scan_number_done: /// the decimal point const char decimal_point_char = '.'; }; +} +} + +// #include + + +#include // assert +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// /*! @brief syntax analysis @@ -2834,8 +3182,7 @@ class parser explicit parser(detail::input_adapter_t adapter, const parser_callback_t cb = nullptr, const bool allow_exceptions_ = true) - : callback(cb), m_lexer(adapter), - allow_exceptions(allow_exceptions_) + : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) {} /*! @@ -2921,11 +3268,19 @@ class parser { case token_type::begin_object: { - if (keep and (not callback or ((keep = callback(depth++, parse_event_t::object_start, result))))) + if (keep) { - // explicitly set result to object to cope with {} - result.m_type = value_t::object; - result.m_value = value_t::object; + if (callback) + { + keep = callback(depth++, parse_event_t::object_start, result); + } + + if (not callback or keep) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } } // read next token @@ -2952,7 +3307,7 @@ class parser { return; } - key = m_lexer.get_string(); + key = m_lexer.move_string(); bool keep_tag = false; if (keep) @@ -3017,11 +3372,19 @@ class parser case token_type::begin_array: { - if (keep and (not callback or ((keep = callback(depth++, parse_event_t::array_start, result))))) + if (keep) { - // explicitly set result to object to cope with [] - result.m_type = value_t::array; - result.m_value = value_t::array; + if (callback) + { + keep = callback(depth++, parse_event_t::array_start, result); + } + + if (not callback or keep) + { + // explicitly set result to array to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } } // read next token @@ -3090,7 +3453,7 @@ class parser case token_type::value_string: { result.m_type = value_t::string; - result.m_value = m_lexer.get_string(); + result.m_value = m_lexer.move_string(); break; } @@ -3168,7 +3531,7 @@ class parser } /*! - @brief the acutal acceptor + @brief the actual acceptor @invariant 1. The last token is not yet processed. Therefore, the caller of this function must make sure a token has been read. @@ -3345,12 +3708,20 @@ class parser /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; +} +} -/////////////// -// iterators // -/////////////// +// #include -/*! + +#include // ptrdiff_t +#include // numeric_limits + +namespace nlohmann +{ +namespace detail +{ +/* @brief an iterator for primitive JSON types This class models an iterator for primitive JSON types (boolean, number, @@ -3361,9 +3732,15 @@ end_value (`1`) models past the end. */ class primitive_iterator_t { - public: + private: using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: constexpr difference_type get_value() const noexcept { return m_it; @@ -3384,23 +3761,18 @@ class primitive_iterator_t /// return whether the iterator can be dereferenced constexpr bool is_begin() const noexcept { - return (m_it == begin_value); + return m_it == begin_value; } /// return whether the iterator is at end constexpr bool is_end() const noexcept { - return (m_it == end_value); + return m_it == end_value; } friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { - return (lhs.m_it == rhs.m_it); - } - - friend constexpr bool operator!=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return not(lhs == rhs); + return lhs.m_it == rhs.m_it; } friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept @@ -3408,25 +3780,10 @@ class primitive_iterator_t return lhs.m_it < rhs.m_it; } - friend constexpr bool operator<=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it <= rhs.m_it; - } - - friend constexpr bool operator>(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it > rhs.m_it; - } - - friend constexpr bool operator>=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it >= rhs.m_it; - } - - primitive_iterator_t operator+(difference_type i) + primitive_iterator_t operator+(difference_type n) noexcept { auto result = *this; - result += i; + result += n; return result; } @@ -3435,57 +3792,57 @@ class primitive_iterator_t return lhs.m_it - rhs.m_it; } - friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it) - { - return os << it.m_it; - } - - primitive_iterator_t& operator++() + primitive_iterator_t& operator++() noexcept { ++m_it; return *this; } - primitive_iterator_t operator++(int) + primitive_iterator_t const operator++(int) noexcept { auto result = *this; m_it++; return result; } - primitive_iterator_t& operator--() + primitive_iterator_t& operator--() noexcept { --m_it; return *this; } - primitive_iterator_t operator--(int) + primitive_iterator_t const operator--(int) noexcept { auto result = *this; m_it--; return result; } - primitive_iterator_t& operator+=(difference_type n) + primitive_iterator_t& operator+=(difference_type n) noexcept { m_it += n; return *this; } - primitive_iterator_t& operator-=(difference_type n) + primitive_iterator_t& operator-=(difference_type n) noexcept { m_it -= n; return *this; } - - private: - static constexpr difference_type begin_value = 0; - static constexpr difference_type end_value = begin_value + 1; - - /// iterator as signed integer type - difference_type m_it = std::numeric_limits::denorm_min(); }; +} +} +// #include + + +// #include + + +namespace nlohmann +{ +namespace detail +{ /*! @brief an iterator value @@ -3501,11 +3858,38 @@ template struct internal_iterator /// generic iterator for all other types primitive_iterator_t primitive_iterator {}; }; +} +} +// #include + + +#include // not +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on template class iteration_proxy; /*! -@brief a template for a random access iterator for the @ref basic_json class +@brief a template for a bidirectional iterator for the @ref basic_json class This class implements a both iterators (iterator and const_iterator) for the @ref basic_json class. @@ -3517,14 +3901,15 @@ This class implements a both iterators (iterator and const_iterator) for the @requirement The class satisfies the following concept requirements: - -[RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): - The iterator that can be moved to point (forward and backward) to any - element in constant time. +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). -@since version 1.0.0, simplified in version 2.0.9 +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) */ template -class iter_impl : public std::iterator +class iter_impl { /// allow basic_json to access private members friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; @@ -3538,6 +3923,14 @@ class iter_impl : public std::iterator::value, typename BasicJsonType::const_reference, typename BasicJsonType::reference>::type; - /// the category of the iterator - using iterator_category = std::bidirectional_iterator_tag; /// default constructor iter_impl() = default; @@ -3763,7 +4154,7 @@ class iter_impl : public std::iterator::type> m_it = {}; + internal_iterator::type> m_it; }; +} +} -/// proxy class for the iterator_wrapper functions +// #include + + +#include // size_t +#include // string, to_string + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// proxy class for the items() function template class iteration_proxy { private: @@ -4160,7 +4566,7 @@ template class iteration_proxy public: /// construct iteration proxy from a container - explicit iteration_proxy(typename IteratorType::reference cont) + explicit iteration_proxy(typename IteratorType::reference cont) noexcept : container(cont) {} /// return iterator begin (needed for range-based for) @@ -4175,6 +4581,23 @@ template class iteration_proxy return iteration_proxy_internal(container.end()); } }; +} +} + +// #include + + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// /*! @brief a template for a reverse iterator class @@ -4185,9 +4608,9 @@ create @ref const_reverse_iterator). @requirement The class satisfies the following concept requirements: - -[RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): - The iterator that can be moved to point (forward and backward) to any - element in constant time. +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): It is possible to write to the pointed-to element (only if @a Base is @ref iterator). @@ -4199,7 +4622,7 @@ class json_reverse_iterator : public std::reverse_iterator { public: using difference_type = std::ptrdiff_t; - /// shortcut to the reverse iterator adaptor + /// shortcut to the reverse iterator adapter using base_iterator = std::reverse_iterator; /// the reference type for the pointed-to element using reference = typename Base::reference; @@ -4212,7 +4635,7 @@ class json_reverse_iterator : public std::reverse_iterator json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} /// post-increment (it++) - json_reverse_iterator operator++(int) + json_reverse_iterator const operator++(int) { return static_cast(base_iterator::operator++(1)); } @@ -4224,7 +4647,7 @@ class json_reverse_iterator : public std::reverse_iterator } /// post-decrement (it--) - json_reverse_iterator operator--(int) + json_reverse_iterator const operator--(int) { return static_cast(base_iterator::operator--(1)); } @@ -4279,11 +4702,25 @@ class json_reverse_iterator : public std::reverse_iterator return it.operator * (); } }; +} +} -///////////////////// -// output adapters // -///////////////////// +// #include + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector + +namespace nlohmann +{ +namespace detail +{ /// abstract output adapter interface template struct output_adapter_protocol { @@ -4380,10 +4817,43 @@ class output_adapter private: output_adapter_t oa = nullptr; }; +} +} -////////////////////////////// -// binary reader and writer // -////////////////////////////// +// #include + + +#include // generate_n +#include // array +#include // assert +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // setw, setfill +#include // hex +#include // back_inserter +#include // numeric_limits +#include // stringstream +#include // char_traits, string +#include // make_pair, move + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary reader // +/////////////////// /*! @brief deserialization of CBOR and MessagePack values @@ -4393,6 +4863,7 @@ class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using string_t = typename BasicJsonType::string_t; public: /*! @@ -4400,7 +4871,7 @@ class binary_reader @param[in] adapter input adapter to read from */ - explicit binary_reader(input_adapter_t adapter) : ia(adapter) + explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) { assert(ia); } @@ -4408,16 +4879,85 @@ class binary_reader /*! @brief create a JSON value from CBOR input - @param[in] get_char whether a new character should be retrieved from - the input (true, default) or whether the last - read character should be considered instead - + @param[in] strict whether to expect the input to be consumed completed @return JSON value created from CBOR input - @throw parse_error.110 if input ended unexpectedly + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true @throw parse_error.112 if unsupported byte was read */ - BasicJsonType parse_cbor(const bool get_char = true) + BasicJsonType parse_cbor(const bool strict) + { + const auto res = parse_cbor_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from MessagePack input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from MessagePack input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_msgpack(const bool strict) + { + const auto res = parse_msgpack_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from UBJSON input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from UBJSON input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_ubjson(const bool strict) + { + const auto res = parse_ubjson_internal(); + if (strict) + { + get_ignore_noop(); + expect_eof(); + } + return res; + } + + /*! + @brief determine system byte order + + @return true if and only if system's byte order is little endian + + @note from http://stackoverflow.com/a/1001328/266378 + */ + static constexpr bool little_endianess(int num = 1) noexcept + { + return (*reinterpret_cast(&num) == 1); + } + + private: + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_cbor_internal(const bool get_char = true) { switch (get_char ? get() : current) { @@ -4436,12 +4976,12 @@ class binary_reader case 0x07: case 0x08: case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: case 0x10: case 0x11: case 0x12: @@ -4458,10 +4998,10 @@ class binary_reader case 0x19: // Unsigned integer (two-byte uint16_t follows) return get_number(); - case 0x1a: // Unsigned integer (four-byte uint32_t follows) + case 0x1A: // Unsigned integer (four-byte uint32_t follows) return get_number(); - case 0x1b: // Unsigned integer (eight-byte uint64_t follows) + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) return get_number(); // Negative integer -1-0x00..-1-0x17 (-1..-24) @@ -4475,12 +5015,12 @@ class binary_reader case 0x27: case 0x28: case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: case 0x30: case 0x31: case 0x32: @@ -4493,7 +5033,6 @@ class binary_reader case 0x38: // Negative integer (one-byte uint8_t follows) { - // must be uint8_t ! return static_cast(-1) - get_number(); } @@ -4502,12 +5041,12 @@ class binary_reader return static_cast(-1) - get_number(); } - case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) { return static_cast(-1) - get_number(); } - case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) { return static_cast(-1) - static_cast(get_number()); @@ -4524,12 +5063,12 @@ class binary_reader case 0x67: case 0x68: case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: case 0x70: case 0x71: case 0x72: @@ -4540,9 +5079,9 @@ class binary_reader case 0x77: case 0x78: // UTF-8 string (one-byte uint8_t for n follows) case 0x79: // UTF-8 string (two-byte uint16_t for n follow) - case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) - case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) - case 0x7f: // UTF-8 string (indefinite length) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) { return get_cbor_string(); } @@ -4558,12 +5097,12 @@ class binary_reader case 0x87: case 0x88: case 0x89: - case 0x8a: - case 0x8b: - case 0x8c: - case 0x8d: - case 0x8e: - case 0x8f: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: case 0x90: case 0x91: case 0x92: @@ -4573,190 +5112,120 @@ class binary_reader case 0x96: case 0x97: { - BasicJsonType result = value_t::array; - const auto len = static_cast(current & 0x1f); - for (std::size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(current & 0x1F); } case 0x98: // array (one-byte uint8_t for n follows) { - BasicJsonType result = value_t::array; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(get_number()); } case 0x99: // array (two-byte uint16_t for n follow) { - BasicJsonType result = value_t::array; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(get_number()); } - case 0x9a: // array (four-byte uint32_t for n follow) + case 0x9A: // array (four-byte uint32_t for n follow) { - BasicJsonType result = value_t::array; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(get_number()); } - case 0x9b: // array (eight-byte uint64_t for n follow) + case 0x9B: // array (eight-byte uint64_t for n follow) { - BasicJsonType result = value_t::array; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(get_number()); } - case 0x9f: // array (indefinite length) + case 0x9F: // array (indefinite length) { BasicJsonType result = value_t::array; - while (get() != 0xff) + while (get() != 0xFF) { - result.push_back(parse_cbor(false)); + result.push_back(parse_cbor_internal(false)); } return result; } // map (0x00..0x17 pairs of data items follow) - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + { + return get_cbor_object(current & 0x1F); + } + + case 0xB8: // map (one-byte uint8_t for n follows) + { + return get_cbor_object(get_number()); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBF: // map (indefinite length) { BasicJsonType result = value_t::object; - const auto len = static_cast(current & 0x1f); - for (std::size_t i = 0; i < len; ++i) + while (get() != 0xFF) { - get(); auto key = get_cbor_string(); - result[key] = parse_cbor(); + result[key] = parse_cbor_internal(); } return result; } - case 0xb8: // map (one-byte uint8_t for n follows) - { - BasicJsonType result = value_t::object; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - get(); - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xb9: // map (two-byte uint16_t for n follow) - { - BasicJsonType result = value_t::object; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - get(); - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xba: // map (four-byte uint32_t for n follow) - { - BasicJsonType result = value_t::object; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - get(); - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xbb: // map (eight-byte uint64_t for n follow) - { - BasicJsonType result = value_t::object; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - get(); - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xbf: // map (indefinite length) - { - BasicJsonType result = value_t::object; - while (get() != 0xff) - { - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xf4: // false + case 0xF4: // false { return false; } - case 0xf5: // true + case 0xF5: // true { return true; } - case 0xf6: // null + case 0xF6: // null { return value_t::null; } - case 0xf9: // Half-Precision Float (two-byte IEEE 754) + case 0xF9: // Half-Precision Float (two-byte IEEE 754) { const int byte1 = get(); - check_eof(); + unexpect_eof(); const int byte2 = get(); - check_eof(); + unexpect_eof(); // code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added @@ -4767,8 +5236,8 @@ class binary_reader // half-precision floating-point numbers in the C language // is shown in Fig. 3. const int half = (byte1 << 8) + byte2; - const int exp = (half >> 10) & 0x1f; - const int mant = half & 0x3ff; + const int exp = (half >> 10) & 0x1F; + const int mant = half & 0x3FF; double val; if (exp == 0) { @@ -4786,12 +5255,12 @@ class binary_reader return (half & 0x8000) != 0 ? -val : val; } - case 0xfa: // Single-Precision Float (four-byte IEEE 754) + case 0xFA: // Single-Precision Float (four-byte IEEE 754) { return get_number(); } - case 0xfb: // Double-Precision Float (eight-byte IEEE 754) + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) { return get_number(); } @@ -4799,21 +5268,13 @@ class binary_reader default: // anything else (0xFF is handled inside the other types) { std::stringstream ss; - ss << std::setw(2) << std::setfill('0') << std::hex << current; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + ss.str())); } } } - /*! - @brief create a JSON value from MessagePack input - - @return JSON value created from MessagePack input - - @throw parse_error.110 if input ended unexpectedly - @throw parse_error.112 if unsupported byte was read - */ - BasicJsonType parse_msgpack() + BasicJsonType parse_msgpack_internal() { switch (get()) { @@ -4832,12 +5293,12 @@ class binary_reader case 0x07: case 0x08: case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: case 0x10: case 0x11: case 0x12: @@ -4848,12 +5309,12 @@ class binary_reader case 0x17: case 0x18: case 0x19: - case 0x1a: - case 0x1b: - case 0x1c: - case 0x1d: - case 0x1e: - case 0x1f: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: case 0x20: case 0x21: case 0x22: @@ -4864,12 +5325,12 @@ class binary_reader case 0x27: case 0x28: case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: case 0x30: case 0x31: case 0x32: @@ -4880,12 +5341,12 @@ class binary_reader case 0x37: case 0x38: case 0x39: - case 0x3a: - case 0x3b: - case 0x3c: - case 0x3d: - case 0x3e: - case 0x3f: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: case 0x40: case 0x41: case 0x42: @@ -4896,12 +5357,12 @@ class binary_reader case 0x47: case 0x48: case 0x49: - case 0x4a: - case 0x4b: - case 0x4c: - case 0x4d: - case 0x4e: - case 0x4f: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: case 0x50: case 0x51: case 0x52: @@ -4912,12 +5373,12 @@ class binary_reader case 0x57: case 0x58: case 0x59: - case 0x5a: - case 0x5b: - case 0x5c: - case 0x5d: - case 0x5e: - case 0x5f: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: case 0x60: case 0x61: case 0x62: @@ -4928,12 +5389,12 @@ class binary_reader case 0x67: case 0x68: case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: case 0x70: case 0x71: case 0x72: @@ -4944,12 +5405,12 @@ class binary_reader case 0x77: case 0x78: case 0x79: - case 0x7a: - case 0x7b: - case 0x7c: - case 0x7d: - case 0x7e: - case 0x7f: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: return static_cast(current); // fixmap @@ -4963,22 +5424,14 @@ class binary_reader case 0x87: case 0x88: case 0x89: - case 0x8a: - case 0x8b: - case 0x8c: - case 0x8d: - case 0x8e: - case 0x8f: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: { - BasicJsonType result = value_t::object; - const auto len = static_cast(current & 0x0f); - for (std::size_t i = 0; i < len; ++i) - { - get(); - auto key = get_msgpack_string(); - result[key] = parse_msgpack(); - } - return result; + return get_msgpack_object(current & 0x0F); } // fixarray @@ -4992,188 +5445,154 @@ class binary_reader case 0x97: case 0x98: case 0x99: - case 0x9a: - case 0x9b: - case 0x9c: - case 0x9d: - case 0x9e: - case 0x9f: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: { - BasicJsonType result = value_t::array; - const auto len = static_cast(current & 0x0f); - for (std::size_t i = 0; i < len; ++i) - { - result.push_back(parse_msgpack()); - } - return result; + return get_msgpack_array(current & 0x0F); } // fixstr - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: - case 0xb8: - case 0xb9: - case 0xba: - case 0xbb: - case 0xbc: - case 0xbd: - case 0xbe: - case 0xbf: + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: return get_msgpack_string(); - case 0xc0: // nil + case 0xC0: // nil return value_t::null; - case 0xc2: // false + case 0xC2: // false return false; - case 0xc3: // true + case 0xC3: // true return true; - case 0xca: // float 32 + case 0xCA: // float 32 return get_number(); - case 0xcb: // float 64 + case 0xCB: // float 64 return get_number(); - case 0xcc: // uint 8 + case 0xCC: // uint 8 return get_number(); - case 0xcd: // uint 16 + case 0xCD: // uint 16 return get_number(); - case 0xce: // uint 32 + case 0xCE: // uint 32 return get_number(); - case 0xcf: // uint 64 + case 0xCF: // uint 64 return get_number(); - case 0xd0: // int 8 + case 0xD0: // int 8 return get_number(); - case 0xd1: // int 16 + case 0xD1: // int 16 return get_number(); - case 0xd2: // int 32 + case 0xD2: // int 32 return get_number(); - case 0xd3: // int 64 + case 0xD3: // int 64 return get_number(); - case 0xd9: // str 8 - case 0xda: // str 16 - case 0xdb: // str 32 + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 return get_msgpack_string(); - case 0xdc: // array 16 + case 0xDC: // array 16 { - BasicJsonType result = value_t::array; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - result.push_back(parse_msgpack()); - } - return result; + return get_msgpack_array(get_number()); } - case 0xdd: // array 32 + case 0xDD: // array 32 { - BasicJsonType result = value_t::array; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - result.push_back(parse_msgpack()); - } - return result; + return get_msgpack_array(get_number()); } - case 0xde: // map 16 + case 0xDE: // map 16 { - BasicJsonType result = value_t::object; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - get(); - auto key = get_msgpack_string(); - result[key] = parse_msgpack(); - } - return result; + return get_msgpack_object(get_number()); } - case 0xdf: // map 32 + case 0xDF: // map 32 { - BasicJsonType result = value_t::object; - const auto len = static_cast(get_number()); - for (std::size_t i = 0; i < len; ++i) - { - get(); - auto key = get_msgpack_string(); - result[key] = parse_msgpack(); - } - return result; + return get_msgpack_object(get_number()); } // positive fixint - case 0xe0: - case 0xe1: - case 0xe2: - case 0xe3: - case 0xe4: - case 0xe5: - case 0xe6: - case 0xe7: - case 0xe8: - case 0xe9: - case 0xea: - case 0xeb: - case 0xec: - case 0xed: - case 0xee: - case 0xef: - case 0xf0: - case 0xf1: - case 0xf2: - case 0xf3: - case 0xf4: - case 0xf5: - case 0xf6: - case 0xf7: - case 0xf8: - case 0xf9: - case 0xfa: - case 0xfb: - case 0xfc: - case 0xfd: - case 0xfe: - case 0xff: + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: return static_cast(current); default: // anything else { std::stringstream ss; - ss << std::setw(2) << std::setfill('0') << std::hex << current; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; JSON_THROW(parse_error::create(112, chars_read, "error reading MessagePack; last byte: 0x" + ss.str())); } @@ -5181,23 +5600,20 @@ class binary_reader } /*! - @brief determine system byte order - - @return true iff system's byte order is little endian - - @note from http://stackoverflow.com/a/1001328/266378 + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead */ - static constexpr bool little_endianess(int num = 1) noexcept + BasicJsonType parse_ubjson_internal(const bool get_char = true) { - return (*reinterpret_cast(&num) == 1); + return get_ubjson_value(get_char ? get_ignore_noop() : current); } - private: /*! @brief get next character from the input This function provides the interface to the used input adapter. It does - not throw in case the input reached EOF, but returns + not throw in case the input reached EOF, but returns a -'ve valued `std::char_traits::eof()` in that case. @return character read from the input @@ -5208,6 +5624,20 @@ class binary_reader return (current = ia->get_character()); } + /*! + @return character read from the input after ignoring all 'N' entries + */ + int get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + /* @brief read a number from the input @@ -5228,7 +5658,7 @@ class binary_reader for (std::size_t i = 0; i < sizeof(NumberType); ++i) { get(); - check_eof(); + unexpect_eof(); // reverse byte order prior to conversion if necessary if (is_little_endian) @@ -5253,22 +5683,23 @@ class binary_reader @param[in] len number of bytes to read @note We can not reserve @a len bytes for the result, because @a len - may be too large. Usually, @ref check_eof() detects the end of + may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of string memory. @return string created by reading @a len bytes @throw parse_error.110 if input has less than @a len bytes */ - std::string get_string(const std::size_t len) + template + string_t get_string(const NumberType len) { - std::string result; - for (std::size_t i = 0; i < len; ++i) + string_t result; + std::generate_n(std::back_inserter(result), len, [this]() { get(); - check_eof(); - result.append(1, static_cast(current)); - } + unexpect_eof(); + return static_cast(current); + }); return result; } @@ -5284,9 +5715,9 @@ class binary_reader @throw parse_error.110 if input ended @throw parse_error.113 if an unexpected byte is read */ - std::string get_cbor_string() + string_t get_cbor_string() { - check_eof(); + unexpect_eof(); switch (current) { @@ -5301,12 +5732,12 @@ class binary_reader case 0x67: case 0x68: case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: case 0x70: case 0x71: case 0x72: @@ -5316,41 +5747,35 @@ class binary_reader case 0x76: case 0x77: { - const auto len = static_cast(current & 0x1f); - return get_string(len); + return get_string(current & 0x1F); } case 0x78: // UTF-8 string (one-byte uint8_t for n follows) { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } case 0x79: // UTF-8 string (two-byte uint16_t for n follow) { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0x7f: // UTF-8 string (indefinite length) + case 0x7F: // UTF-8 string (indefinite length) { - std::string result; - while (get() != 0xff) + string_t result; + while (get() != 0xFF) { - check_eof(); - result.append(1, static_cast(current)); + result.append(get_cbor_string()); } return result; } @@ -5358,12 +5783,39 @@ class binary_reader default: { std::stringstream ss; - ss << std::setw(2) << std::setfill('0') << std::hex << current; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + ss.str())); } } } + template + BasicJsonType get_cbor_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_cbor_internal(); + }); + return result; + } + + template + BasicJsonType get_cbor_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_cbor_string(); + auto val = parse_cbor_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + /*! @brief reads a MessagePack string @@ -5375,83 +5827,356 @@ class binary_reader @throw parse_error.110 if input ended @throw parse_error.113 if an unexpected byte is read */ - std::string get_msgpack_string() + string_t get_msgpack_string() { - check_eof(); + unexpect_eof(); switch (current) { // fixstr - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: - case 0xb8: - case 0xb9: - case 0xba: - case 0xbb: - case 0xbc: - case 0xbd: - case 0xbe: - case 0xbf: + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: { - const auto len = static_cast(current & 0x1f); - return get_string(len); + return get_string(current & 0x1F); } - case 0xd9: // str 8 + case 0xD9: // str 8 { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0xda: // str 16 + case 0xDA: // str 16 { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0xdb: // str 32 + case 0xDB: // str 32 { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } default: { std::stringstream ss; - ss << std::setw(2) << std::setfill('0') << std::hex << current; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; JSON_THROW(parse_error::create(113, chars_read, "expected a MessagePack string; last byte: 0x" + ss.str())); } } } + template + BasicJsonType get_msgpack_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_msgpack_internal(); + }); + return result; + } + + template + BasicJsonType get_msgpack_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_msgpack_string(); + auto val = parse_msgpack_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + /*! - @brief check if input ended + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_ubjson_string(const bool get_char = true) + { + if (get_char) + { + get(); // TODO: may we ignore N here? + } + + unexpect_eof(); + + switch (current) + { + case 'U': + return get_string(get_number()); + case 'i': + return get_string(get_number()); + case 'I': + return get_string(get_number()); + case 'l': + return get_string(get_number()); + case 'L': + return get_string(get_number()); + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @return pair of the size and the type + */ + std::pair get_ubjson_size_type() + { + std::size_t sz = string_t::npos; + int tc = 0; + + get_ignore_noop(); + + if (current == '$') + { + tc = get(); // must not ignore 'N', because 'N' maybe the type + unexpect_eof(); + + get_ignore_noop(); + if (current != '#') + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "expected '#' after UBJSON type information; last byte: 0x" + ss.str())); + } + sz = parse_ubjson_internal(); + } + else if (current == '#') + { + sz = parse_ubjson_internal(); + } + + return std::make_pair(sz, tc); + } + + BasicJsonType get_ubjson_value(const int prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': // true + return true; + case 'F': // false + return false; + + case 'Z': // null + return nullptr; + + case 'U': + return get_number(); + case 'i': + return get_number(); + case 'I': + return get_number(); + case 'l': + return get_number(); + case 'L': + return get_number(); + case 'd': + return get_number(); + case 'D': + return get_number(); + + case 'C': // char + { + get(); + unexpect_eof(); + if (JSON_UNLIKELY(current > 127)) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + ss.str())); + } + return string_t(1, static_cast(current)); + } + + case 'S': // string + return get_ubjson_string(); + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x" + ss.str())); + } + } + + BasicJsonType get_ubjson_array() + { + BasicJsonType result = value_t::array; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this, size_and_type]() + { + return get_ubjson_value(size_and_type.second); + }); + } + } + else + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this]() + { + return parse_ubjson_internal(); + }); + } + } + else + { + while (current != ']') + { + result.push_back(parse_ubjson_internal(false)); + get_ignore_noop(); + } + } + + return result; + } + + BasicJsonType get_ubjson_object() + { + BasicJsonType result = value_t::object; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this, size_and_type]() + { + auto key = get_ubjson_string(); + auto val = get_ubjson_value(size_and_type.second); + return std::make_pair(std::move(key), std::move(val)); + }); + } + else + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this]() + { + auto key = get_ubjson_string(); + auto val = parse_ubjson_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + } + } + else + { + while (current != '}') + { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + get_ignore_noop(); + } + } + + return result; + } + + /*! + @brief throw if end of input is not reached + @throw parse_error.110 if input not ended + */ + void expect_eof() const + { + if (JSON_UNLIKELY(current != std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + + /*! + @briefthrow if end of input is reached @throw parse_error.110 if input ended */ - void check_eof() const + void unexpect_eof() const { if (JSON_UNLIKELY(current == std::char_traits::eof())) { @@ -5472,6 +6197,30 @@ class binary_reader /// whether we can assume little endianess const bool is_little_endian = little_endianess(); }; +} +} + +// #include + + +#include // reverse +#include // array +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// /*! @brief serialization to CBOR and MessagePack values @@ -5499,15 +6248,15 @@ class binary_writer { case value_t::null: { - oa->write_character(static_cast(0xf6)); + oa->write_character(static_cast(0xF6)); break; } case value_t::boolean: { oa->write_character(j.m_value.boolean - ? static_cast(0xf5) - : static_cast(0xf4)); + ? static_cast(0xF5) + : static_cast(0xF4)); break; } @@ -5534,12 +6283,12 @@ class binary_writer } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0x1a)); + oa->write_character(static_cast(0x1A)); write_number(static_cast(j.m_value.number_integer)); } else { - oa->write_character(static_cast(0x1b)); + oa->write_character(static_cast(0x1B)); write_number(static_cast(j.m_value.number_integer)); } } @@ -5564,12 +6313,12 @@ class binary_writer } else if (positive_number <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0x3a)); + oa->write_character(static_cast(0x3A)); write_number(static_cast(positive_number)); } else { - oa->write_character(static_cast(0x3b)); + oa->write_character(static_cast(0x3B)); write_number(static_cast(positive_number)); } } @@ -5594,12 +6343,12 @@ class binary_writer } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0x1a)); + oa->write_character(static_cast(0x1A)); write_number(static_cast(j.m_value.number_unsigned)); } else { - oa->write_character(static_cast(0x1b)); + oa->write_character(static_cast(0x1B)); write_number(static_cast(j.m_value.number_unsigned)); } break; @@ -5607,7 +6356,7 @@ class binary_writer case value_t::number_float: // Double-Precision Float { - oa->write_character(static_cast(0xfb)); + oa->write_character(static_cast(0xFB)); write_number(j.m_value.number_float); break; } @@ -5620,25 +6369,25 @@ class binary_writer { write_number(static_cast(0x60 + N)); } - else if (N <= 0xff) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x78)); write_number(static_cast(N)); } - else if (N <= 0xffff) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x79)); write_number(static_cast(N)); } - else if (N <= 0xffffffff) + else if (N <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0x7a)); + oa->write_character(static_cast(0x7A)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) + else if (N <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0x7b)); + oa->write_character(static_cast(0x7B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP @@ -5658,25 +6407,25 @@ class binary_writer { write_number(static_cast(0x80 + N)); } - else if (N <= 0xff) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x98)); write_number(static_cast(N)); } - else if (N <= 0xffff) + else if (N <= (std::numeric_limits::max)()) { oa->write_character(static_cast(0x99)); write_number(static_cast(N)); } - else if (N <= 0xffffffff) + else if (N <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0x9a)); + oa->write_character(static_cast(0x9A)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) + else if (N <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0x9b)); + oa->write_character(static_cast(0x9B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP @@ -5695,27 +6444,27 @@ class binary_writer const auto N = j.m_value.object->size(); if (N <= 0x17) { - write_number(static_cast(0xa0 + N)); + write_number(static_cast(0xA0 + N)); } - else if (N <= 0xff) + else if (N <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0xb8)); + oa->write_character(static_cast(0xB8)); write_number(static_cast(N)); } - else if (N <= 0xffff) + else if (N <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0xb9)); + oa->write_character(static_cast(0xB9)); write_number(static_cast(N)); } - else if (N <= 0xffffffff) + else if (N <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0xba)); + oa->write_character(static_cast(0xBA)); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) + else if (N <= (std::numeric_limits::max)()) { - oa->write_character(static_cast(0xbb)); + oa->write_character(static_cast(0xBB)); write_number(static_cast(N)); } // LCOV_EXCL_STOP @@ -5743,15 +6492,15 @@ class binary_writer { case value_t::null: // nil { - oa->write_character(static_cast(0xc0)); + oa->write_character(static_cast(0xC0)); break; } case value_t::boolean: // true and false { oa->write_character(j.m_value.boolean - ? static_cast(0xc3) - : static_cast(0xc2)); + ? static_cast(0xC3) + : static_cast(0xC2)); break; } @@ -5770,25 +6519,25 @@ class binary_writer else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 - oa->write_character(static_cast(0xcc)); + oa->write_character(static_cast(0xCC)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 - oa->write_character(static_cast(0xcd)); + oa->write_character(static_cast(0xCD)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 - oa->write_character(static_cast(0xce)); + oa->write_character(static_cast(0xCE)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 - oa->write_character(static_cast(0xcf)); + oa->write_character(static_cast(0xCF)); write_number(static_cast(j.m_value.number_integer)); } } @@ -5803,28 +6552,28 @@ class binary_writer j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 8 - oa->write_character(static_cast(0xd0)); + oa->write_character(static_cast(0xD0)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() and j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 16 - oa->write_character(static_cast(0xd1)); + oa->write_character(static_cast(0xD1)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() and j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 32 - oa->write_character(static_cast(0xd2)); + oa->write_character(static_cast(0xD2)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() and j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 64 - oa->write_character(static_cast(0xd3)); + oa->write_character(static_cast(0xD3)); write_number(static_cast(j.m_value.number_integer)); } } @@ -5841,25 +6590,25 @@ class binary_writer else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 - oa->write_character(static_cast(0xcc)); + oa->write_character(static_cast(0xCC)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 - oa->write_character(static_cast(0xcd)); + oa->write_character(static_cast(0xCD)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 - oa->write_character(static_cast(0xce)); + oa->write_character(static_cast(0xCE)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 - oa->write_character(static_cast(0xcf)); + oa->write_character(static_cast(0xCF)); write_number(static_cast(j.m_value.number_integer)); } break; @@ -5867,7 +6616,7 @@ class binary_writer case value_t::number_float: // float 64 { - oa->write_character(static_cast(0xcb)); + oa->write_character(static_cast(0xCB)); write_number(j.m_value.number_float); break; } @@ -5879,24 +6628,24 @@ class binary_writer if (N <= 31) { // fixstr - write_number(static_cast(0xa0 | N)); + write_number(static_cast(0xA0 | N)); } - else if (N <= 255) + else if (N <= (std::numeric_limits::max)()) { // str 8 - oa->write_character(static_cast(0xd9)); + oa->write_character(static_cast(0xD9)); write_number(static_cast(N)); } - else if (N <= 65535) + else if (N <= (std::numeric_limits::max)()) { // str 16 - oa->write_character(static_cast(0xda)); + oa->write_character(static_cast(0xDA)); write_number(static_cast(N)); } - else if (N <= 4294967295) + else if (N <= (std::numeric_limits::max)()) { // str 32 - oa->write_character(static_cast(0xdb)); + oa->write_character(static_cast(0xDB)); write_number(static_cast(N)); } @@ -5916,16 +6665,16 @@ class binary_writer // fixarray write_number(static_cast(0x90 | N)); } - else if (N <= 0xffff) + else if (N <= (std::numeric_limits::max)()) { // array 16 - oa->write_character(static_cast(0xdc)); + oa->write_character(static_cast(0xDC)); write_number(static_cast(N)); } - else if (N <= 0xffffffff) + else if (N <= (std::numeric_limits::max)()) { // array 32 - oa->write_character(static_cast(0xdd)); + oa->write_character(static_cast(0xDD)); write_number(static_cast(N)); } @@ -5944,18 +6693,18 @@ class binary_writer if (N <= 15) { // fixmap - write_number(static_cast(0x80 | (N & 0xf))); + write_number(static_cast(0x80 | (N & 0xF))); } - else if (N <= 65535) + else if (N <= (std::numeric_limits::max)()) { // map 16 - oa->write_character(static_cast(0xde)); + oa->write_character(static_cast(0xDE)); write_number(static_cast(N)); } - else if (N <= 4294967295) + else if (N <= (std::numeric_limits::max)()) { // map 32 - oa->write_character(static_cast(0xdf)); + oa->write_character(static_cast(0xDF)); write_number(static_cast(N)); } @@ -5973,6 +6722,165 @@ class binary_writer } } + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(static_cast('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + oa->write_character(j.m_value.boolean + ? static_cast('T') + : static_cast('F')); + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(static_cast('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(static_cast('[')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.array->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(static_cast('{')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.object->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast('}')); + } + + break; + } + + default: + break; + } + } + private: /* @brief write a number to output input @@ -5981,10 +6889,11 @@ class binary_writer @tparam NumberType the type of the number @note This function needs to respect the system's endianess, because bytes - in CBOR and MessagePack are stored in network order (big endian) and - therefore need reordering on little endian systems. + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. */ - template void write_number(NumberType n) + template + void write_number(const NumberType n) { // step 1: write number to array of length NumberType std::array vec; @@ -6000,6 +6909,200 @@ class binary_writer oa->write_characters(vec.data(), sizeof(NumberType)); } + template + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (std::is_floating_point::value) + { + if (add_prefix) + { + oa->write_character(static_cast('D')); // float64 + } + write_number(n); + } + else if (std::is_unsigned::value) + { + if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + } + else + { + if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + // LCOV_EXCL_STOP + } + } + + /*! + @brief determine the type prefix of container values + + @note This function does not need to be 100% accurate when it comes to + integer limits. In case a number exceeds the limits of int64_t, + this will be detected by a later call to function + write_number_with_ubjson_prefix. Therefore, we return 'L' for any + value that does not fit the previous limits. + */ + char ubjson_prefix(const BasicJsonType& j) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_float: + return 'D'; + + case value_t::string: + return 'S'; + + case value_t::array: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + private: /// whether we can assume little endianess const bool is_little_endian = binary_reader::little_endianess(); @@ -6007,7 +7110,1140 @@ class binary_writer /// the output output_adapter_t oa = nullptr; }; +} +} +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // setfill +#include // next +#include // numeric_limits +#include // string +#include // stringstream +#include // is_same + +// #include + +// #include + + +#include // assert +#include // or, and, not +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + uint64_t f; + int e; + + constexpr diyfp() noexcept : f(0), e(0) {} + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + assert(x.e == y.e); + assert(x.f >= y.f); + + return diyfp(x.f - y.f, x.e); + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; + + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; + + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; + + uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += uint64_t{1} << (64 - 32 - 1); // round, ties up + + const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32); + + return diyfp(h, x.e + y.e + 64); + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + assert(x.f != 0); + + while ((x.f >> 63) == 0) + { + x.f <<= 1; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); + + return diyfp(x.f << delta, target_exponent); + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + assert(std::isfinite(value)); + assert(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + + const uint64_t bits = reinterpret_bits(value); + const uint64_t E = bits >> (kPrecision - 1); + const uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr cached_power kCachedPowers[] = + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + (f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast(kCachedPowersSize); // Fix warning. + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e + e + 64); + assert(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const uint32_t n, uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) +{ + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + and delta - rest >= ten_k + and (rest + ten_k < dist or dist - rest > rest + ten_k - dist)) + { + assert(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); + + uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + + uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + assert(p1 > 0); + + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const uint64_t rest = (uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + assert(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + assert(std::isfinite(value)); + assert(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +inline char* append_exponent(char* buf, int e) +{ + assert(e > -1000); + assert(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + uint32_t k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + assert(min_exp < 0); + assert(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n and n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n - k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (n + 2); + } + + if (0 < n and n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + assert(k > n); + + std::memmove(buf + (n + 1), buf + n, static_cast(k - n)); + buf[n] = '.'; + return buf + (k + 1); + } + + if (min_exp < n and n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + -n), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2 + (-n) + k); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k - 1)); + buf[1] = '.'; + buf += 1 + k; + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +char* to_chars(char* first, char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + assert(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + assert(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + assert(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + assert(last - first >= kMaxExp + 2); + assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + assert(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ /////////////////// // serialization // /////////////////// @@ -6019,16 +8255,20 @@ class serializer using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + public: /*! @param[in] s output stream to serialize to @param[in] ichar indentation character to use */ serializer(output_adapter_t s, const char ichar) - : o(s), loc(std::localeconv()), - thousands_sep(loc->thousands_sep == nullptr ? '\0' : loc->thousands_sep[0]), - decimal_point(loc->decimal_point == nullptr ? '\0' : loc->decimal_point[0]), - indent_char(ichar), indent_string(512, indent_char) {} + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) + {} // delete because of pointer members serializer(const serializer&) = delete; @@ -6091,6 +8331,7 @@ class serializer // last element assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); @@ -6118,6 +8359,7 @@ class serializer // last element assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\":", 2); @@ -6242,175 +8484,6 @@ class serializer } private: - /*! - @brief returns the number of expected bytes following in UTF-8 string - - @param[in] u the first byte of a UTF-8 string - @return the number of expected bytes following - */ - static constexpr std::size_t bytes_following(const uint8_t u) - { - return ((0 <= u and u <= 127) ? 0 - : ((192 <= u and u <= 223) ? 1 - : ((224 <= u and u <= 239) ? 2 - : ((240 <= u and u <= 247) ? 3 : std::string::npos)))); - } - - /*! - @brief calculates the extra space to escape a JSON string - - @param[in] s the string to escape - @param[in] ensure_ascii whether to escape non-ASCII characters with - \uXXXX sequences - @return the number of characters required to escape string @a s - - @complexity Linear in the length of string @a s. - */ - static std::size_t extra_space(const string_t& s, - const bool ensure_ascii) noexcept - { - std::size_t res = 0; - - for (std::size_t i = 0; i < s.size(); ++i) - { - switch (s[i]) - { - // control characters that can be escaped with a backslash - case '"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - { - // from c (1 byte) to \x (2 bytes) - res += 1; - break; - } - - // control characters that need \uxxxx escaping - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x0b: - case 0x0e: - case 0x0f: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x18: - case 0x19: - case 0x1a: - case 0x1b: - case 0x1c: - case 0x1d: - case 0x1e: - case 0x1f: - { - // from c (1 byte) to \uxxxx (6 bytes) - res += 5; - break; - } - - default: - { - if (ensure_ascii and (s[i] & 0x80 or s[i] == 0x7F)) - { - const auto bytes = bytes_following(static_cast(s[i])); - if (bytes == std::string::npos) - { - // invalid characters are treated as is, so no - // additional space will be used - break; - } - - if (bytes == 3) - { - // codepoints that need 4 bytes (i.e., 3 additional - // bytes) in UTF-8 needs a surrogate pair when \u - // escaping is used: from 4 bytes to \uxxxx\uxxxx - // (12 bytes) - res += (12 - bytes - 1); - } - else - { - // from x bytes to \uxxxx (6 bytes) - res += (6 - bytes - 1); - } - - // skip the additional bytes - i += bytes; - } - break; - } - } - } - - return res; - } - - static void escape_codepoint(int codepoint, string_t& result, std::size_t& pos) - { - // expecting a proper codepoint - assert(0x00 <= codepoint and codepoint <= 0x10FFFF); - - // the last written character was the backslash before the 'u' - assert(result[pos] == '\\'); - - // write the 'u' - result[++pos] = 'u'; - - // convert a number 0..15 to its hex representation (0..f) - static const std::array hexify = - { - { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - } - }; - - if (codepoint < 0x10000) - { - // codepoints U+0000..U+FFFF can be represented as \uxxxx. - result[++pos] = hexify[(codepoint >> 12) & 0x0F]; - result[++pos] = hexify[(codepoint >> 8) & 0x0F]; - result[++pos] = hexify[(codepoint >> 4) & 0x0F]; - result[++pos] = hexify[codepoint & 0x0F]; - } - else - { - // codepoints U+10000..U+10FFFF need a surrogate pair to be - // represented as \uxxxx\uxxxx. - // http://www.unicode.org/faq/utf_bom.html#utf16-4 - codepoint -= 0x10000; - const int high_surrogate = 0xD800 | ((codepoint >> 10) & 0x3FF); - const int low_surrogate = 0xDC00 | (codepoint & 0x3FF); - result[++pos] = hexify[(high_surrogate >> 12) & 0x0F]; - result[++pos] = hexify[(high_surrogate >> 8) & 0x0F]; - result[++pos] = hexify[(high_surrogate >> 4) & 0x0F]; - result[++pos] = hexify[high_surrogate & 0x0F]; - ++pos; // backslash is already in output - result[++pos] = 'u'; - result[++pos] = hexify[(low_surrogate >> 12) & 0x0F]; - result[++pos] = hexify[(low_surrogate >> 8) & 0x0F]; - result[++pos] = hexify[(low_surrogate >> 4) & 0x0F]; - result[++pos] = hexify[low_surrogate & 0x0F]; - } - - ++pos; - } - /*! @brief dump escaped string @@ -6425,143 +8498,146 @@ class serializer @complexity Linear in the length of string @a s. */ - void dump_escaped(const string_t& s, const bool ensure_ascii) const + void dump_escaped(const string_t& s, const bool ensure_ascii) { - const auto space = extra_space(s, ensure_ascii); - if (space == 0) - { - o->write_characters(s.c_str(), s.size()); - return; - } - - // create a result string of necessary size - string_t result(s.size() + space, '\\'); - std::size_t pos = 0; + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer for (std::size_t i = 0; i < s.size(); ++i) { - switch (s[i]) + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) { - case '"': // quotation mark (0x22) + case UTF8_ACCEPT: // decode found a new code point { - result[pos + 1] = '"'; - pos += 2; - break; - } - - case '\\': // reverse solidus (0x5c) - { - // nothing to change - pos += 2; - break; - } - - case '\b': // backspace (0x08) - { - result[pos + 1] = 'b'; - pos += 2; - break; - } - - case '\f': // formfeed (0x0c) - { - result[pos + 1] = 'f'; - pos += 2; - break; - } - - case '\n': // newline (0x0a) - { - result[pos + 1] = 'n'; - pos += 2; - break; - } - - case '\r': // carriage return (0x0d) - { - result[pos + 1] = 'r'; - pos += 2; - break; - } - - case '\t': // horizontal tab (0x09) - { - result[pos + 1] = 't'; - pos += 2; - break; - } - - default: - { - // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((0x00 <= s[i] and s[i] <= 0x1F) or - (ensure_ascii and (s[i] & 0x80 or s[i] == 0x7F))) + switch (codepoint) { - const auto bytes = bytes_following(static_cast(s[i])); - if (bytes == std::string::npos) + case 0x08: // backspace { - // copy invalid character as is - result[pos++] = s[i]; + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; break; } - // check that the additional bytes are present - assert(i + bytes < s.size()); - - // to use \uxxxx escaping, we first need to caluclate - // the codepoint from the UTF-8 bytes - int codepoint = 0; - - assert(0 <= bytes and bytes <= 3); - switch (bytes) + case 0x09: // horizontal tab { - case 0: - { - codepoint = s[i] & 0xFF; - break; - } - - case 1: - { - codepoint = ((s[i] & 0x3F) << 6) - + (s[i + 1] & 0x7F); - break; - } - - case 2: - { - codepoint = ((s[i] & 0x1F) << 12) - + ((s[i + 1] & 0x7F) << 6) - + (s[i + 2] & 0x7F); - break; - } - - case 3: - { - codepoint = ((s[i] & 0xF) << 18) - + ((s[i + 1] & 0x7F) << 12) - + ((s[i + 2] & 0x7F) << 6) - + (s[i + 3] & 0x7F); - break; - } + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; } - escape_codepoint(codepoint, result, pos); - i += bytes; + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } } - else + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) { - // all other characters are added as-is - result[pos++] = s[i]; + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str())); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; } break; } } } - assert(pos == result.size()); - o->write_characters(result.c_str(), result.size()); + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str())); + } } /*! @@ -6573,11 +8649,10 @@ class serializer @param[in] x integer number (signed or unsigned) to dump @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ - template < - typename NumberType, - detail::enable_if_t::value or - std::is_same::value, - int> = 0 > + template::value or + std::is_same::value, + int> = 0> void dump_integer(NumberType x) { // special case for "0" @@ -6587,20 +8662,19 @@ class serializer return; } - const bool is_negative = x < 0; + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 std::size_t i = 0; - // spare 1 byte for '\0' - while (x != 0 and i < number_buffer.size() - 1) + while (x != 0) { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + const auto digit = std::labs(static_cast(x % 10)); number_buffer[i++] = static_cast('0' + digit); x /= 10; } - // make sure the number has been processed completely - assert(x == 0); - if (is_negative) { // make sure there is capacity for the '-' @@ -6623,28 +8697,36 @@ class serializer void dump_float(number_float_t x) { // NaN / inf - if (not std::isfinite(x) or std::isnan(x)) + if (not std::isfinite(x)) { o->write_characters("null", 4); return; } - // special case for 0.0 and -0.0 - if (x == 0) - { - if (std::signbit(x)) - { - o->write_characters("-0.0", 4); - } - else - { - o->write_characters("0.0", 3); - } - return; - } + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); - // get number of digits for a text -> float -> text round-trip - static constexpr auto d = std::numeric_limits::digits10; + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; // the actual conversion std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); @@ -6667,13 +8749,10 @@ class serializer // convert decimal point to '.' if (decimal_point != '\0' and decimal_point != '.') { - for (auto& c : number_buffer) + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) { - if (c == decimal_point) - { - c = '.'; - break; - } + *dec_pos = '.'; } } @@ -6693,6 +8772,59 @@ class serializer } } + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; + } + private: /// the output of the serializer output_adapter_t o = nullptr; @@ -6707,13 +8839,27 @@ class serializer /// the locale's decimal point character const char decimal_point = '\0'; + /// string buffer + std::array string_buffer{{}}; + /// the indentation character const char indent_char; - /// the indentation string string_t indent_string; }; +} +} +// #include + + +#include +#include + +namespace nlohmann +{ +namespace detail +{ template class json_ref { @@ -6721,27 +8867,20 @@ class json_ref using value_type = BasicJsonType; json_ref(value_type&& value) - : owned_value(std::move(value)), - value_ref(&owned_value), - is_rvalue(true) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) {} json_ref(const value_type& value) - : value_ref(const_cast(&value)), - is_rvalue(false) + : value_ref(const_cast(&value)), is_rvalue(false) {} json_ref(std::initializer_list init) - : owned_value(init), - value_ref(&owned_value), - is_rvalue(true) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) {} - template - json_ref(Args... args) - : owned_value(std::forward(args)...), - value_ref(&owned_value), - is_rvalue(true) + template + json_ref(Args&& ... args) + : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) {} // class should be movable only @@ -6755,10 +8894,7 @@ class json_ref { return std::move(*value_ref); } - else - { - return *value_ref; - } + return *value_ref; } value_type const& operator*() const @@ -6776,74 +8912,30 @@ class json_ref value_type* value_ref = nullptr; const bool is_rvalue; }; - -} // namespace detail - -/// namespace to hold default `to_json` / `from_json` functions -namespace -{ -constexpr const auto& to_json = detail::static_const::value; -constexpr const auto& from_json = detail::static_const::value; +} } +// #include -/*! -@brief default JSONSerializer template argument -This serializer ignores the template arguments and uses ADL -([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) -for serialization. -*/ -template -struct adl_serializer +#include // assert +#include // accumulate +#include // string +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann { - /*! - @brief convert a JSON value to any value type - - This function is usually called by the `get()` function of the - @ref basic_json class (either explicit or via conversion operators). - - @param[in] j JSON value to read from - @param[in,out] val value to write to - */ - template - static void from_json(BasicJsonType&& j, ValueType& val) noexcept( - noexcept(::nlohmann::from_json(std::forward(j), val))) - { - ::nlohmann::from_json(std::forward(j), val); - } - - /*! - @brief convert any value type to a JSON value - - This function is usually called by the constructors of the @ref basic_json - class. - - @param[in,out] j JSON value to write to - @param[in] val value to read from - */ - template - static void to_json(BasicJsonType& j, ValueType&& val) noexcept( - noexcept(::nlohmann::to_json(j, std::forward(val)))) - { - ::nlohmann::to_json(j, std::forward(val)); - } -}; - -/*! -@brief JSON Pointer - -A JSON pointer defines a string syntax for identifying a specific value -within a JSON document. It can be used with functions `at` and -`operator[]`. Furthermore, JSON pointers are the base for JSON patches. - -@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) - -@since version 2.0.0 -*/ +template class json_pointer { - /// allow basic_json to access private members + // allow basic_json to access private members NLOHMANN_BASIC_JSON_TPL_DECLARATION friend class basic_json; @@ -6857,19 +8949,21 @@ class json_pointer @param[in] s string representing the JSON pointer; if omitted, the empty string is assumed which references the whole JSON value - @throw parse_error.107 if the given JSON pointer @a s is nonempty and - does not begin with a slash (`/`); see example below + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below - @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s - is not followed by `0` (representing `~`) or `1` (representing `/`); - see example below + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below - @liveexample{The example shows the construction several valid JSON - pointers as well as the exceptional behavior.,json_pointer} + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} @since version 2.0.0 */ - explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {} + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} /*! @brief return a string representation of the JSON pointer @@ -6902,6 +8996,27 @@ class json_pointer return to_string(); } + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw out_of_range.404 if string @a s could not be converted to an integer + */ + static int array_index(const std::string& s) + { + std::size_t processed_chars = 0; + const int res = std::stoi(s, &processed_chars); + + // check if the string was completely read + if (JSON_UNLIKELY(processed_chars != s.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + return res; + } + private: /*! @brief remove and return last reference pointer @@ -6937,7 +9052,6 @@ class json_pointer return result; } - /*! @brief create and return a reference to the pointed to value @@ -6946,8 +9060,66 @@ class json_pointer @throw parse_error.109 if array index is not a number @throw type_error.313 if value cannot be unflattened */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const; + BasicJsonType& get_and_create(BasicJsonType& j) const + { + using size_type = typename BasicJsonType::size_type; + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + JSON_TRY + { + result = &result->operator[](static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } /*! @brief return a reference to the pointed to value @@ -6968,8 +9140,75 @@ class json_pointer @throw parse_error.109 if an array index was not a number @throw out_of_range.404 if the JSON pointer can not be resolved */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const; + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == detail::value_t::null) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) + { + return (x >= '0' and x <= '9'); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } /*! @throw parse_error.106 if an array index begins with '0' @@ -6977,8 +9216,57 @@ class json_pointer @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const; + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } /*! @brief return a const reference to the pointed to value @@ -6993,8 +9281,58 @@ class json_pointer @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - const NLOHMANN_BASIC_JSON_TPL& get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const; + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // use unchecked array access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } /*! @throw parse_error.106 if an array index begins with '0' @@ -7002,8 +9340,57 @@ class json_pointer @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - const NLOHMANN_BASIC_JSON_TPL& get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const; + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } /*! @brief split the string input to reference tokens @@ -7122,10 +9509,57 @@ class json_pointer @note Empty objects or arrays are flattened to `null`. */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION static void flatten(const std::string& reference_string, - const NLOHMANN_BASIC_JSON_TPL& value, - NLOHMANN_BASIC_JSON_TPL& result); + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.m_type) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } /*! @param[in] value flattened JSON @@ -7137,19 +9571,108 @@ class json_pointer @throw type_error.315 if object values are not primitive @throw type_error.313 if value cannot be unflattened */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - static NLOHMANN_BASIC_JSON_TPL - unflatten(const NLOHMANN_BASIC_JSON_TPL& value); + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_UNLIKELY(not value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_UNLIKELY(not element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } friend bool operator==(json_pointer const& lhs, - json_pointer const& rhs) noexcept; + json_pointer const& rhs) noexcept + { + return (lhs.reference_tokens == rhs.reference_tokens); + } friend bool operator!=(json_pointer const& lhs, - json_pointer const& rhs) noexcept; + json_pointer const& rhs) noexcept + { + return not (lhs == rhs); + } /// the reference tokens std::vector reference_tokens; }; +} + +// #include + + +#include + +// #include + +// #include + + +namespace nlohmann +{ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; +} + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ /*! @brief a class to store JSON values @@ -7237,13 +9760,16 @@ class basic_json { private: template friend struct detail::external_constructor; - friend ::nlohmann::json_pointer; + friend ::nlohmann::json_pointer; friend ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; template friend class ::nlohmann::detail::iter_impl; template friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + /// workaround type for MSVC using basic_json_t = NLOHMANN_BASIC_JSON_TPL; @@ -7270,11 +9796,11 @@ class basic_json public: using value_t = detail::value_t; - // forward declarations - using json_pointer = ::nlohmann::json_pointer; + /// @copydoc nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; template using json_serializer = JSONSerializer; - + /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; //////////////// @@ -7370,6 +9896,9 @@ class basic_json @liveexample{The following code shows an example output of the `meta()` function.,meta} + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @complexity Constant. @since 2.1.0 @@ -7381,10 +9910,13 @@ class basic_json result["copyright"] = "(C) 2013-2017 Niels Lohmann"; result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; - result["version"] = - { - {"string", "2.1.1"}, {"major", 2}, {"minor", 1}, {"patch", 1} - }; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; #ifdef _WIN32 result["platform"] = "win32"; @@ -7436,6 +9968,14 @@ class basic_json /// the template arguments passed to class @ref basic_json. /// @{ +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + /*! @brief a type for an object @@ -7478,10 +10018,10 @@ class basic_json - When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings. - - When the names within an object are not unique, later stored name/value - pairs overwrite previously stored name/value pairs, leaving the used - names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will - be treated as equal and both stored as `{"key": 1}`. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. - Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see @ref dump) in this order. For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored @@ -7521,7 +10061,7 @@ class basic_json */ using object_t = ObjectType, + object_comparator_t, AllocatorType>>; @@ -7870,12 +10410,14 @@ class basic_json static T* create(Args&& ... args) { AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + auto deleter = [&](T * object) { - alloc.deallocate(object, 1); + AllocatorTraits::deallocate(alloc, object, 1); }; - std::unique_ptr object(alloc.allocate(1), deleter); - alloc.construct(object.get(), std::forward(args)...); + std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); assert(object != nullptr); return object.release(); } @@ -7984,14 +10526,16 @@ class basic_json case value_t::null: { + object = nullptr; // silence warning, see #821 break; } default: { + object = nullptr; // silence warning, see #821 if (JSON_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 2.1.1")); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.1")); // LCOV_EXCL_LINE } break; } @@ -8034,31 +10578,31 @@ class basic_json array = create(std::move(value)); } - void destroy(value_t t) + void destroy(value_t t) noexcept { switch (t) { case value_t::object: { AllocatorType alloc; - alloc.destroy(object); - alloc.deallocate(object, 1); + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); break; } case value_t::array: { AllocatorType alloc; - alloc.destroy(array); - alloc.deallocate(array, 1); + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); break; } case value_t::string: { AllocatorType alloc; - alloc.destroy(string); - alloc.deallocate(string, 1); + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); break; } @@ -8079,7 +10623,7 @@ class basic_json value is changed, because the invariant expresses a relationship between @a m_type and @a m_value. */ - void assert_invariant() const + void assert_invariant() const noexcept { assert(m_type != value_t::object or m_value.object != nullptr); assert(m_type != value_t::array or m_value.array != nullptr); @@ -8091,19 +10635,32 @@ class basic_json // JSON parser callback // ////////////////////////// + /*! + @brief parser event types + + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + @sa @ref parser_callback_t for more information and examples + */ using parse_event_t = typename parser::parse_event_t; /*! @brief per-element parser callback type With a parser callback function, the result of parsing a JSON text can be - influenced. When passed to @ref parse(std::istream&, const - parser_callback_t) or @ref parse(const CharT, const parser_callback_t), - it is called on certain events (passed as @ref parse_event_t via parameter - @a event) with a set recursion depth @a depth and context JSON value - @a parsed. The return value of the callback function is a boolean - indicating whether the element that emitted the callback shall be kept or - not. + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. We distinguish six scenarios (determined by the event type) in which the callback function can be called. The following table describes the values @@ -8140,8 +10697,7 @@ class basic_json should be kept (`true`) or not (`false`). In the latter case, it is either skipped completely or replaced by an empty discarded object. - @sa @ref parse(std::istream&, parser_callback_t) or - @ref parse(const CharT, const parser_callback_t) for examples + @sa @ref parse for examples @since version 1.0.0 */ @@ -8176,9 +10732,14 @@ class basic_json @complexity Constant. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The following code shows the constructor for different @ref value_t values,basic_json__value_t} + @sa @ref clear() -- restores the postcondition of this constructor + @since version 1.0.0 */ basic_json(const value_t v) @@ -8215,7 +10776,7 @@ class basic_json @brief create a JSON value This is a "catch all" constructor for all compatible JSON types; that is, - types for which a `to_json()` method exsits. The constructor forwards the + types for which a `to_json()` method exists. The constructor forwards the parameter @a val to that method (to `json_serializer::to_json` method with `U = uncvref_t`, to be exact). @@ -8223,9 +10784,9 @@ class basic_json following types: - **arrays**: @ref array_t and all kinds of compatible containers such as `std::vector`, `std::deque`, `std::list`, `std::forward_list`, - `std::array`, `std::set`, `std::unordered_set`, `std::multiset`, and - `unordered_multiset` with a `value_type` from which a @ref basic_json - value can be constructed. + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. - **objects**: @ref object_t and all kinds of compatible associative containers such as `std::map`, `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with a `key_type` compatible to @@ -8251,28 +10812,29 @@ class basic_json @tparam U = `uncvref_t` - @param[in] val the value to be forwarded + @param[in] val the value to be forwarded to the respective constructor @complexity Usually linear in the size of the passed @a val, also depending on the implementation of the called `to_json()` method. - @throw what `json_serializer::to_json()` throws + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. @liveexample{The following code shows the constructor with several compatible types.,basic_json__CompatibleType} @since version 2.1.0 */ - template, - detail::enable_if_t::value and - not std::is_same::value and - not detail::is_basic_json_nested_type< - basic_json_t, U>::value and - detail::has_to_json::value, - int> = 0> - basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer::to_json( - std::declval(), std::forward(val)))) + template , + detail::enable_if_t< + detail::is_compatible_type::value, int> = 0> + basic_json(CompatibleType && val) noexcept(noexcept( + JSONSerializer::to_json(std::declval(), + std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); assert_invariant(); @@ -8297,7 +10859,7 @@ class basic_json 1. The empty initializer list is written as `{}` which is exactly an empty JSON object. - 2. C++ has now way of describing mapped types other than to list a list of + 2. C++ has no way of describing mapped types other than to list a list of pairs. As JSON requires that keys must be of type string, rule 2 is the weakest constraint one can pose on initializer lists to interpret them as an object. @@ -8339,6 +10901,9 @@ class basic_json @complexity Linear in the size of the initializer list @a init. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The example below shows how JSON values are created from initializer lists.,basic_json__list_init_t} @@ -8425,6 +10990,9 @@ class basic_json @complexity Linear in the size of @a init. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The following code shows an example for the `array` function.,array} @@ -8465,6 +11033,9 @@ class basic_json @complexity Linear in the size of @a init. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The following code shows an example for the `object` function.,object} @@ -8484,14 +11055,18 @@ class basic_json @brief construct an array with count copies of given value Constructs a JSON array value by creating @a cnt copies of a passed value. - In case @a cnt is `0`, an empty array is created. As postcondition, - `std::distance(begin(),end()) == cnt` holds. + In case @a cnt is `0`, an empty array is created. @param[in] cnt the number of JSON copies of @a val to create @param[in] val the JSON value to copy + @post `std::distance(begin(),end()) == cnt` holds. + @complexity Linear in @a cnt. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The following code shows examples for the @ref basic_json(size_type\, const basic_json&) constructor.,basic_json__size_type_basic_json} @@ -8510,12 +11085,13 @@ class basic_json Constructs the JSON value with the contents of the range `[first, last)`. The semantics depends on the different types a JSON value can have: - - In case of primitive types (number, boolean, or string), @a first must - be `begin()` and @a last must be `end()`. In this case, the value is + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is copied. Otherwise, invalid_iterator.204 is thrown. - In case of structured types (array, object), the constructor behaves as - similar versions for `std::vector`. - - In case of a null type, invalid_iterator.206 is thrown. + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. @tparam InputIT an input iterator type (@ref iterator or @ref const_iterator) @@ -8524,11 +11100,20 @@ class basic_json @param[in] last end of the range to copy from (excluded) @pre Iterators @a first and @a last must be initialized. **This - precondition is enforced with an assertion.** + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. @pre Range `[first, last)` is valid. Usually, this precondition cannot be checked efficiently. Only certain edge cases are detected; see the - description of the exceptions below. + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See http://en.cppreference.com/w/cpp/error/assert for more + information. @throw invalid_iterator.201 if iterators @a first and @a last are not compatible (i.e., do not belong to the same JSON value). In this case, @@ -8542,6 +11127,9 @@ class basic_json @complexity Linear in distance between @a first and @a last. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The example below shows several ways to create JSON values by specifying a subrange with iterators.,basic_json__InputIt_InputIt} @@ -8644,6 +11232,7 @@ class basic_json // other constructors and destructor // /////////////////////////////////////// + /// @private basic_json(const detail::json_ref& ref) : basic_json(ref.moved_or_copied()) {} @@ -8655,8 +11244,13 @@ class basic_json @param[in] other the JSON value to copy + @post `*this == other` + @complexity Linear in the size of @a other. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @requirement This function helps `basic_json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) requirements: @@ -8734,10 +11328,18 @@ class basic_json @param[in,out] other value to move to this object - @post @a other is a JSON null value + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. @complexity Constant. + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible) + requirements. + @liveexample{The code below shows the move constructor explicitly called via std::move.,basic_json__moveconstructor} @@ -8762,7 +11364,7 @@ class basic_json Copy assignment operator. Copies a JSON value via the "copy and swap" strategy: It is expressed in terms of the copy constructor, destructor, - and the swap() member function. + and the `swap()` member function. @param[in] other value to copy from @@ -8813,7 +11415,7 @@ class basic_json @since version 1.0.0 */ - ~basic_json() + ~basic_json() noexcept { assert_invariant(); m_value.destroy(m_type); @@ -8843,20 +11445,28 @@ class basic_json representation. @param[in] indent_char The character to use for indentation if @a indent is greater than `0`. The default is ` ` (space). - @param[in] ensure_ascii If ensure_ascii is true, all non-ASCII characters - in the output are escaped with \uXXXX sequences, and the result consists + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists of ASCII characters only. @return string containing the serialization of the JSON value + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + @complexity Linear. - @liveexample{The following example shows the effect of different @a indent - parameters to the result of the serialization.,dump} + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} @see https://docs.python.org/2/library/json.html#json.dump - @since version 1.0.0; indentation character added in version 3.0.0 + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0 */ string_t dump(const int indent = -1, const char indent_char = ' ', const bool ensure_ascii = false) const @@ -8883,6 +11493,17 @@ class basic_json enumeration. @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + discarded | value_t::discarded @complexity Constant. @@ -8892,6 +11513,9 @@ class basic_json @liveexample{The following code exemplifies `type()` for all JSON types.,type} + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + @since version 1.0.0 */ constexpr value_t type() const noexcept @@ -8902,8 +11526,8 @@ class basic_json /*! @brief return whether type is primitive - This function returns true iff the JSON type is primitive (string, number, - boolean, or null). + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). @return `true` if type is primitive (string, number, boolean, or null), `false` otherwise. @@ -8932,8 +11556,8 @@ class basic_json /*! @brief return whether type is structured - This function returns true iff the JSON type is structured (array or - object). + This function returns true if and only if the JSON type is structured + (array or object). @return `true` if type is structured (array or object), `false` otherwise. @@ -8959,7 +11583,7 @@ class basic_json /*! @brief return whether value is null - This function returns true iff the JSON value is null. + This function returns true if and only if the JSON value is null. @return `true` if type is null, `false` otherwise. @@ -8981,7 +11605,7 @@ class basic_json /*! @brief return whether value is a boolean - This function returns true iff the JSON value is a boolean. + This function returns true if and only if the JSON value is a boolean. @return `true` if type is boolean, `false` otherwise. @@ -9003,8 +11627,8 @@ class basic_json /*! @brief return whether value is a number - This function returns true iff the JSON value is a number. This includes - both integer and floating-point values. + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. @return `true` if type is number (regardless whether integer, unsigned integer or floating-type), `false` otherwise. @@ -9033,8 +11657,8 @@ class basic_json /*! @brief return whether value is an integer number - This function returns true iff the JSON value is an integer or unsigned - integer number. This excludes floating-point values. + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. @return `true` if type is an integer or unsigned integer number, `false` otherwise. @@ -9062,8 +11686,8 @@ class basic_json /*! @brief return whether value is an unsigned integer number - This function returns true iff the JSON value is an unsigned integer - number. This excludes floating-point and (signed) integer values. + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. @return `true` if type is an unsigned integer number, `false` otherwise. @@ -9090,8 +11714,8 @@ class basic_json /*! @brief return whether value is a floating-point number - This function returns true iff the JSON value is a floating-point number. - This excludes integer and unsigned integer values. + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. @return `true` if type is a floating-point number, `false` otherwise. @@ -9118,7 +11742,7 @@ class basic_json /*! @brief return whether value is an object - This function returns true iff the JSON value is an object. + This function returns true if and only if the JSON value is an object. @return `true` if type is object, `false` otherwise. @@ -9140,7 +11764,7 @@ class basic_json /*! @brief return whether value is an array - This function returns true iff the JSON value is an array. + This function returns true if and only if the JSON value is an array. @return `true` if type is array, `false` otherwise. @@ -9162,7 +11786,7 @@ class basic_json /*! @brief return whether value is a string - This function returns true iff the JSON value is a string. + This function returns true if and only if the JSON value is a string. @return `true` if type is string, `false` otherwise. @@ -9184,8 +11808,8 @@ class basic_json /*! @brief return whether value is discarded - This function returns true iff the JSON value was discarded during parsing - with a callback function (see @ref parser_callback_t). + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). @note This function will always be `false` for JSON values after parsing. That is, discarded values can only occur during parsing, but will be @@ -9224,6 +11848,9 @@ class basic_json @liveexample{The following code exemplifies the @ref value_t operator for all JSON types.,operator__value_t} + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + @since version 1.0.0 */ constexpr operator value_t() const noexcept @@ -9377,11 +12004,9 @@ class basic_json @since version 2.1.0 */ - template < - typename BasicJsonType, - detail::enable_if_t::type, - basic_json_t>::value, - int> = 0 > + template::type, basic_json_t>::value, + int> = 0> basic_json get() const { return *this; @@ -9426,14 +12051,12 @@ class basic_json @since version 2.1.0 */ - template < - typename ValueTypeCV, - typename ValueType = detail::uncvref_t, - detail::enable_if_t < - not std::is_same::value and - detail::has_from_json::value and - not detail::has_non_default_from_json::value, - int > = 0 > + template, + detail::enable_if_t < + not std::is_same::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int> = 0> ValueType get() const noexcept(noexcept( JSONSerializer::from_json(std::declval(), std::declval()))) { @@ -9481,12 +12104,10 @@ class basic_json @since version 2.1.0 */ - template < - typename ValueTypeCV, - typename ValueType = detail::uncvref_t, - detail::enable_if_t::value and - detail::has_non_default_from_json::value, int> = 0 > + template, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, + int> = 0> ValueType get() const noexcept(noexcept( JSONSerializer::from_json(std::declval()))) { @@ -9616,7 +12237,7 @@ class basic_json , "incompatible pointer type"); // delegate the call to get_impl_ptr<>() const - return get_impl_ptr(static_cast(nullptr)); + return get_impl_ptr(static_cast(nullptr)); } /*! @@ -9702,7 +12323,7 @@ class basic_json #ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 and not std::is_same>::value #endif -#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSC_VER) && _MSC_VER >1900 && defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 +#if defined(JSON_HAS_CPP_17) and not std::is_same::value #endif , int >::type = 0 > @@ -9980,7 +12601,7 @@ class basic_json @return const reference to the element at index @a idx - @throw type_error.305 if the JSON value is not an array; in that cases, + @throw type_error.305 if the JSON value is not an array; in that case, using the [] operator with an index makes no sense. @complexity Constant. @@ -10063,7 +12684,7 @@ class basic_json @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** - @throw type_error.305 if the JSON value is not an object; in that cases, + @throw type_error.305 if the JSON value is not an object; in that case, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -10152,7 +12773,7 @@ class basic_json @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** - @throw type_error.305 if the JSON value is not an object; in that cases, + @throw type_error.305 if the JSON value is not an object; in that case, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -10212,7 +12833,7 @@ class basic_json @return copy of the element at key @a key or @a default_value if @a key is not found - @throw type_error.306 if the JSON value is not an objec; in that cases, + @throw type_error.306 if the JSON value is not an object; in that case, using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -10243,10 +12864,8 @@ class basic_json return default_value; } - else - { - JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); - } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); } /*! @@ -10287,7 +12906,7 @@ class basic_json @return copy of the element at key @a key or @a default_value if @a key is not found - @throw type_error.306 if the JSON value is not an objec; in that cases, + @throw type_error.306 if the JSON value is not an objec; in that case, using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -10491,8 +13110,8 @@ class basic_json if (is_string()) { AllocatorType alloc; - alloc.destroy(m_value.string); - alloc.deallocate(m_value.string, 1); + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } @@ -10597,8 +13216,8 @@ class basic_json if (is_string()) { AllocatorType alloc; - alloc.destroy(m_value.string); - alloc.deallocate(m_value.string, 1); + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } @@ -10730,7 +13349,7 @@ class basic_json @note This method always returns @ref end() when executed on a JSON type that is not an object. - @param[in] key key value of the element to search for + @param[in] key key value of the element to search for. @return Iterator to an element with key equivalent to @a key. If no such element is found or the JSON value is not an object, past-the-end (see @@ -10742,13 +13361,14 @@ class basic_json @since version 1.0.0 */ - iterator find(typename object_t::key_type key) + template + iterator find(KeyT&& key) { auto result = end(); if (is_object()) { - result.m_it.object_iterator = m_value.object->find(key); + result.m_it.object_iterator = m_value.object->find(std::forward(key)); } return result; @@ -10756,15 +13376,16 @@ class basic_json /*! @brief find an element in a JSON object - @copydoc find(typename object_t::key_type) + @copydoc find(KeyT&&) */ - const_iterator find(typename object_t::key_type key) const + template + const_iterator find(KeyT&& key) const { auto result = cend(); if (is_object()) { - result.m_it.object_iterator = m_value.object->find(key); + result.m_it.object_iterator = m_value.object->find(std::forward(key)); } return result; @@ -10791,10 +13412,11 @@ class basic_json @since version 1.0.0 */ - size_type count(typename object_t::key_type key) const + template + size_type count(KeyT&& key) const { // return 0 for all nonobject types - return is_object() ? m_value.object->count(key) : 0; + return is_object() ? m_value.object->count(std::forward(key)) : 0; } /// @} @@ -11089,22 +13711,133 @@ class basic_json reference to the JSON values is returned, so there is no access to the underlying iterator. + For loop without iterator_wrapper: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without iterator proxy: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with iterator proxy: + + @code{cpp} + for (auto it : json::iterator_wrapper(j_object)) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). + + @param[in] ref reference to a JSON value + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + @note The name of this function is not yet final and may change in the future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. */ - static iteration_proxy iterator_wrapper(reference cont) + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(reference ref) noexcept { - return iteration_proxy(cont); + return ref.items(); } /*! @copydoc iterator_wrapper(reference) */ - static iteration_proxy iterator_wrapper(const_reference cont) + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(const_reference ref) noexcept { - return iteration_proxy(cont); + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto it : j_object.items()) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.x.x. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); } /// @} @@ -11118,9 +13851,9 @@ class basic_json /// @{ /*! - @brief checks whether the container is empty + @brief checks whether the container is empty. - Checks if a JSON value has no elements. + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). @return The return value depends on the different types and is defined as follows: @@ -11133,23 +13866,27 @@ class basic_json object | result of function `object_t::empty()` array | result of function `array_t::empty()` - @note This function does not return whether a string stored as JSON value - is empty - it returns whether the JSON container itself is empty which is - false in the case of a string. + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their `empty()` functions have constant complexity. + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + @requirement This function helps `basic_json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) requirements: - The complexity is constant. - Has the semantics of `begin() == end()`. - @liveexample{The following code uses `empty()` to check if a JSON - object contains any elements.,empty} - @sa @ref size() -- returns the number of elements @since version 1.0.0 @@ -11200,23 +13937,27 @@ class basic_json object | result of function object_t::size() array | result of function array_t::size() - @note This function does not return the length of a string stored as JSON - value - it returns the number of elements in the JSON value which is 1 in - the case of a string. + @liveexample{The following code calls `size()` on the different value + types.,size} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their size() functions have constant complexity. + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + @requirement This function helps `basic_json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) requirements: - The complexity is constant. - Has the semantics of `std::distance(begin(), end())`. - @liveexample{The following code calls `size()` on the different value - types.,size} - @sa @ref empty() -- checks whether the container is empty @sa @ref max_size() -- returns the maximal number of elements @@ -11270,10 +14011,17 @@ class basic_json object | result of function `object_t::max_size()` array | result of function `array_t::max_size()` + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their `max_size()` functions have constant complexity. + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @requirement This function helps `basic_json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) requirements: @@ -11281,9 +14029,6 @@ class basic_json - Has the semantics of returning `b.size()` where `b` is the largest possible JSON value. - @liveexample{The following code calls `max_size()` on the different value - types. Note the output is implementation specific.,max_size} - @sa @ref size() -- returns the number of elements @since version 1.0.0 @@ -11326,7 +14071,8 @@ class basic_json @brief clears the contents Clears the content of a JSON value and resets it to the default value as - if @ref basic_json(value_t) would have been called: + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): Value type | initial value ----------- | ------------- @@ -11337,11 +14083,24 @@ class basic_json object | `{}` array | `[]` - @complexity Linear in the size of the JSON value. + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode @liveexample{The example below shows the effect of `clear()` to different JSON types.,clear} + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + @since version 1.0.0 */ void clear() noexcept @@ -11821,7 +14580,7 @@ class basic_json JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } - if (JSON_UNLIKELY(first.m_object == this or last.m_object == this)) + if (JSON_UNLIKELY(first.m_object == this)) { JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); } @@ -11917,8 +14676,7 @@ class basic_json } // passed iterators must belong to objects - if (JSON_UNLIKELY(not first.m_object->is_object() - or not last.m_object->is_object())) + if (JSON_UNLIKELY(not first.m_object->is_object())) { JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); } @@ -11926,6 +14684,110 @@ class basic_json m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); } + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object() + or not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + /*! @brief exchanges the values @@ -12075,12 +14937,27 @@ class basic_json comparison. Note than two NaN values are always treated as unequal. - Two JSON null values are equal. + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + @note NaN values never compare equal to themselves or to other NaN values. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether the values @a lhs and @a rhs are equal + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @complexity Linear. @liveexample{The example demonstrates comparing several JSON @@ -12186,6 +15063,8 @@ class basic_json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__notequal} @@ -12237,6 +15116,8 @@ class basic_json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__less} @@ -12344,6 +15225,8 @@ class basic_json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__greater} @@ -12388,6 +15271,8 @@ class basic_json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__lessequal} @@ -12432,6 +15317,8 @@ class basic_json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__greaterequal} @@ -12484,7 +15371,7 @@ class basic_json `std::setw(4)` on @a o sets the indentation level to `4` and the serialization result is the same as calling `dump(4)`. - - The indentation characrer can be controlled with the member variable + - The indentation character can be controlled with the member variable `fill` of the output stream @a o. For instance, the manipulator `std::setfill('\\t')` sets indentation to use a tab character rather than the default space character. @@ -12494,12 +15381,15 @@ class basic_json @return the stream @a o + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + @complexity Linear. @liveexample{The example below shows the serialization with different parameters to `width` to adjust the indentation level.,operator_serialize} - @since version 1.0.0; indentaction character added in version 3.0.0 + @since version 1.0.0; indentation character added in version 3.0.0 */ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { @@ -12518,10 +15408,11 @@ class basic_json /*! @brief serialize to stream - @deprecated This stream operator is deprecated and will be removed in a - future version of the library. Please use - @ref std::ostream& operator<<(std::ostream&, const basic_json&) + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 */ JSON_DEPRECATED friend std::ostream& operator>>(const basic_json& j, std::ostream& o) @@ -12659,6 +15550,8 @@ class basic_json @param[in] cb a parser callback function of type @ref parser_callback_t which is used to control the deserialization by filtering unwanted values (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) @return result of the deserialization @@ -12701,10 +15594,11 @@ class basic_json /*! @brief deserialize from stream - @deprecated This stream operator is deprecated and will be removed in a - future version of the library. Please use - @ref std::istream& operator>>(std::istream&, basic_json&) + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use + @ref operator>>(std::istream&, basic_json&) instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 */ JSON_DEPRECATED friend std::istream& operator<<(basic_json& j, std::istream& i) @@ -12755,16 +15649,31 @@ class basic_json Returns the type name as string to be used in error messages - usually to indicate that a function was called on a wrong JSON type. - @return basically a string representation of a the @a m_type member + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. @complexity Constant. @liveexample{The following code exemplifies `type_name()` for all JSON types.,type_name} - @since version 1.0.0, public since 2.1.0, const char* since 3.0.0 + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 */ - const char* type_name() const + const char* type_name() const noexcept { { switch (m_type) @@ -12820,59 +15729,63 @@ class basic_json JSON value type | value/range | CBOR type | first byte --------------- | ------------------------------------------ | ---------------------------------- | --------------- - null | `null` | Null | 0xf6 - boolean | `true` | True | 0xf5 - boolean | `false` | False | 0xf4 - number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3b - number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3a + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 number_integer | -24..-1 | Negative integer | 0x20..0x37 number_integer | 0..23 | Integer | 0x00..0x17 number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 - number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1a - number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1b + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B number_unsigned | 0..23 | Integer | 0x00..0x17 number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 - number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1a - number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1b - number_float | *any value* | Double-Precision Float | 0xfb + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value* | Double-Precision Float | 0xFB string | *length*: 0..23 | UTF-8 string | 0x60..0x77 string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 - string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7a - string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7b + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B array | *size*: 0..23 | array | 0x80..0x97 array | *size*: 23..255 | array (1 byte follow) | 0x98 array | *size*: 256..65535 | array (2 bytes follow) | 0x99 - array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9a - array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9b - object | *size*: 0..23 | map | 0xa0..0xb7 - object | *size*: 23..255 | map (1 byte follow) | 0xb8 - object | *size*: 256..65535 | map (2 bytes follow) | 0xb9 - object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xba - object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xbb + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB @note The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value. + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + @note The following CBOR types are not used in the conversion: - - byte strings (0x40..0x5f) - - UTF-8 strings terminated by "break" (0x7f) - - arrays terminated by "break" (0x9f) - - maps terminated by "break" (0xbf) - - date/time (0xc0..0xc1) - - bignum (0xc2..0xc3) - - decimal fraction (0xc4) - - bigfloat (0xc5) - - tagged items (0xc6..0xd4, 0xd8..0xdb) - - expected conversions (0xd5..0xd7) - - simple values (0xe0..0xf3, 0xf8) - - undefined (0xf7) - - half and single-precision floats (0xf9-0xfa) - - break (0xff) + - byte strings (0x40..0x5F) + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half and single-precision floats (0xF9-0xFA) + - break (0xFF) @param[in] j JSON value to serialize @return MessagePack serialization as byte vector @@ -12883,9 +15796,11 @@ class basic_json vector in CBOR format.,to_cbor} @sa http://cbor.io - @sa @ref from_cbor(const std::vector&, const size_t) for the + @sa @ref from_cbor(detail::input_adapter, const bool strict) for the analogous deserialization - @sa @ref to_msgpack(const basic_json& for the related MessagePack format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format @since version 2.0.9 */ @@ -12918,35 +15833,35 @@ class basic_json JSON value type | value/range | MessagePack type | first byte --------------- | --------------------------------- | ---------------- | ---------- - null | `null` | nil | 0xc0 - boolean | `true` | true | 0xc3 - boolean | `false` | false | 0xc2 - number_integer | -9223372036854775808..-2147483649 | int64 | 0xd3 - number_integer | -2147483648..-32769 | int32 | 0xd2 - number_integer | -32768..-129 | int16 | 0xd1 - number_integer | -128..-33 | int8 | 0xd0 - number_integer | -32..-1 | negative fixint | 0xe0..0xff - number_integer | 0..127 | positive fixint | 0x00..0x7f - number_integer | 128..255 | uint 8 | 0xcc - number_integer | 256..65535 | uint 16 | 0xcd - number_integer | 65536..4294967295 | uint 32 | 0xce - number_integer | 4294967296..18446744073709551615 | uint 64 | 0xcf - number_unsigned | 0..127 | positive fixint | 0x00..0x7f - number_unsigned | 128..255 | uint 8 | 0xcc - number_unsigned | 256..65535 | uint 16 | 0xcd - number_unsigned | 65536..4294967295 | uint 32 | 0xce - number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xcf - number_float | *any value* | float 64 | 0xcb - string | *length*: 0..31 | fixstr | 0xa0..0xbf - string | *length*: 32..255 | str 8 | 0xd9 - string | *length*: 256..65535 | str 16 | 0xda - string | *length*: 65536..4294967295 | str 32 | 0xdb - array | *size*: 0..15 | fixarray | 0x90..0x9f - array | *size*: 16..65535 | array 16 | 0xdc - array | *size*: 65536..4294967295 | array 32 | 0xdd - object | *size*: 0..15 | fix map | 0x80..0x8f - object | *size*: 16..65535 | map 16 | 0xde - object | *size*: 65536..4294967295 | map 32 | 0xdf + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF @note The mapping is **complete** in the sense that any JSON value type can be converted to a MessagePack value. @@ -12957,14 +15872,18 @@ class basic_json - objects with more than 4294967295 elements @note The following MessagePack types are not used in the conversion: - - bin 8 - bin 32 (0xc4..0xc6) - - ext 8 - ext 32 (0xc7..0xc9) - - float 32 (0xca) - - fixext 1 - fixext 16 (0xd4..0xd8) + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - float 32 (0xCA) + - fixext 1 - fixext 16 (0xD4..0xD8) @note Any MessagePack output created @ref to_msgpack can be successfully parsed by @ref from_msgpack. + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + @param[in] j JSON value to serialize @return MessagePack serialization as byte vector @@ -12977,6 +15896,8 @@ class basic_json @sa @ref from_msgpack(const std::vector&, const size_t) for the analogous deserialization @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format @since version 2.0.9 */ @@ -12998,10 +15919,111 @@ class basic_json } /*! - @brief create a JSON value from a byte vector in CBOR format + @brief create a UBJSON serialization of a given JSON value - Deserializes a given byte vector @a v to a JSON value using the CBOR - (Concise Binary Object Representation) serialization format. + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + - unsigned integer numbers above 9223372036854775807 + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. The library maps CBOR types to JSON value types as follows: @@ -13010,51 +16032,51 @@ class basic_json Integer | number_unsigned | 0x00..0x17 Unsigned integer | number_unsigned | 0x18 Unsigned integer | number_unsigned | 0x19 - Unsigned integer | number_unsigned | 0x1a - Unsigned integer | number_unsigned | 0x1b + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B Negative integer | number_integer | 0x20..0x37 Negative integer | number_integer | 0x38 Negative integer | number_integer | 0x39 - Negative integer | number_integer | 0x3a - Negative integer | number_integer | 0x3b + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B Negative integer | number_integer | 0x40..0x57 UTF-8 string | string | 0x60..0x77 UTF-8 string | string | 0x78 UTF-8 string | string | 0x79 - UTF-8 string | string | 0x7a - UTF-8 string | string | 0x7b - UTF-8 string | string | 0x7f + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F array | array | 0x80..0x97 array | array | 0x98 array | array | 0x99 - array | array | 0x9a - array | array | 0x9b - array | array | 0x9f - map | object | 0xa0..0xb7 - map | object | 0xb8 - map | object | 0xb9 - map | object | 0xba - map | object | 0xbb - map | object | 0xbf - False | `false` | 0xf4 - True | `true` | 0xf5 - Nill | `null` | 0xf6 - Half-Precision Float | number_float | 0xf9 - Single-Precision Float | number_float | 0xfa - Double-Precision Float | number_float | 0xfb + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Nill | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB @warning The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors (parse_error.112): - - byte strings (0x40..0x5f) - - date/time (0xc0..0xc1) - - bignum (0xc2..0xc3) - - decimal fraction (0xc4) - - bigfloat (0xc5) - - tagged items (0xc6..0xd4, 0xd8..0xdb) - - expected conversions (0xd5..0xd7) - - simple values (0xe0..0xf3, 0xf8) - - undefined (0xf7) + - byte strings (0x40..0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) @warning CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys @@ -13063,117 +16085,202 @@ class basic_json @note Any CBOR output created @ref to_cbor can be successfully parsed by @ref from_cbor. - @param[in] v a byte vector in CBOR format - @param[in] start_index the index to start reading from @a v (0 by default) + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) @return deserialized JSON value - @throw parse_error.110 if the given vector ends prematurely + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true @throw parse_error.112 if unsupported features from CBOR were - used in the given vector @a v or if the input is not valid CBOR + used in the given input @a v or if the input is not valid CBOR @throw parse_error.113 if a string was expected as map key, but not found - @complexity Linear in the size of the byte vector @a v. + @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in CBOR format to a JSON value.,from_cbor} @sa http://cbor.io @sa @ref to_cbor(const basic_json&) for the analogous serialization - @sa @ref from_msgpack(const std::vector&, const size_t) for the + @sa @ref from_msgpack(detail::input_adapter, const bool) for the related MessagePack format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format - @since version 2.0.9, parameter @a start_index since 2.1.1 + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 */ - static basic_json from_cbor(const std::vector& v, - const std::size_t start_index = 0) + static basic_json from_cbor(detail::input_adapter i, + const bool strict = true) { - binary_reader br(detail::input_adapter(v.begin() + static_cast(start_index), v.end())); - return br.parse_cbor(); + return binary_reader(i).parse_cbor(strict); } - static basic_json from_cbor(detail::input_adapter i) - { - return binary_reader(i).parse_cbor(); - } - - /*! - @brief create a JSON value from a byte vector in MessagePack format + @copydoc from_cbor(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_cbor(strict); + } - Deserializes a given byte vector @a v to a JSON value using the MessagePack + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack serialization format. The library maps MessagePack types to JSON value types as follows: MessagePack type | JSON value type | first byte ---------------- | --------------- | ---------- - positive fixint | number_unsigned | 0x00..0x7f - fixmap | object | 0x80..0x8f - fixarray | array | 0x90..0x9f - fixstr | string | 0xa0..0xbf - nil | `null` | 0xc0 - false | `false` | 0xc2 - true | `true` | 0xc3 - float 32 | number_float | 0xca - float 64 | number_float | 0xcb - uint 8 | number_unsigned | 0xcc - uint 16 | number_unsigned | 0xcd - uint 32 | number_unsigned | 0xce - uint 64 | number_unsigned | 0xcf - int 8 | number_integer | 0xd0 - int 16 | number_integer | 0xd1 - int 32 | number_integer | 0xd2 - int 64 | number_integer | 0xd3 - str 8 | string | 0xd9 - str 16 | string | 0xda - str 32 | string | 0xdb - array 16 | array | 0xdc - array 32 | array | 0xdd - map 16 | object | 0xde - map 32 | object | 0xdf - negative fixint | number_integer | 0xe0-0xff + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + negative fixint | number_integer | 0xE0-0xFF @warning The mapping is **incomplete** in the sense that not all MessagePack types can be converted to a JSON value. The following MessagePack types are not supported and will yield parse errors: - - bin 8 - bin 32 (0xc4..0xc6) - - ext 8 - ext 32 (0xc7..0xc9) - - fixext 1 - fixext 16 (0xd4..0xd8) + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - fixext 1 - fixext 16 (0xD4..0xD8) @note Any MessagePack output created @ref to_msgpack can be successfully parsed by @ref from_msgpack. - @param[in] v a byte vector in MessagePack format - @param[in] start_index the index to start reading from @a v (0 by default) - @return deserialized JSON value + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) - @throw parse_error.110 if the given vector ends prematurely + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true @throw parse_error.112 if unsupported features from MessagePack were - used in the given vector @a v or if the input is not valid MessagePack + used in the given input @a i or if the input is not valid MessagePack @throw parse_error.113 if a string was expected as map key, but not found - @complexity Linear in the size of the byte vector @a v. + @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in MessagePack format to a JSON value.,from_msgpack} @sa http://msgpack.org @sa @ref to_msgpack(const basic_json&) for the analogous serialization - @sa @ref from_cbor(const std::vector&, const size_t) for the - related CBOR format + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format - @since version 2.0.9, parameter @a start_index since 2.1.1 + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 */ - static basic_json from_msgpack(const std::vector& v, - const std::size_t start_index = 0) + static basic_json from_msgpack(detail::input_adapter i, + const bool strict = true) { - binary_reader br(detail::input_adapter(v.begin() + static_cast(start_index), v.end())); - return br.parse_msgpack(); + return binary_reader(i).parse_msgpack(strict); } - static basic_json from_msgpack(detail::input_adapter i) + /*! + @copydoc from_msgpack(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true) { - return binary_reader(i).parse_msgpack(); + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_msgpack(strict); + } + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_msgpack(detail::input_adapter, const bool) for the related + MessagePack format + + @since version 3.1.0 + */ + static basic_json from_ubjson(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_ubjson(strict); + } + + template::value, int> = 0> + static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_ubjson(strict); } /// @} @@ -13274,6 +16381,9 @@ class basic_json pointer @a ptr. As `at` provides checked access (and no elements are implicitly inserted), the index '-' is always invalid. See example below. + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. See example below. @@ -13314,6 +16424,9 @@ class basic_json pointer @a ptr. As `at` provides checked access (and no elements are implicitly inserted), the index '-' is always invalid. See example below. + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. See example below. @@ -13529,7 +16642,7 @@ class basic_json } else { - const auto idx = std::stoi(last_path); + const auto idx = json_pointer::array_index(last_path); if (JSON_UNLIKELY(static_cast(idx) > parent.size())) { // avoid undefined behavior @@ -13577,7 +16690,7 @@ class basic_json else if (parent.is_array()) { // note erase performs range check - parent.erase(static_cast(std::stoi(last_path))); + parent.erase(static_cast(json_pointer::array_index(last_path))); } }; @@ -13593,7 +16706,7 @@ class basic_json // wrapper to get a value for an operation const auto get_value = [&val](const std::string & op, const std::string & member, - bool string_type) -> basic_json& + bool string_type) -> basic_json & { // find value auto it = val.m_value.object->find(member); @@ -13672,7 +16785,12 @@ class basic_json const json_pointer from_ptr(from_path); // the "from" location must exist - use at() - result[ptr] = result.at(from_ptr); + basic_json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); break; } @@ -13738,6 +16856,7 @@ class basic_json diff for two JSON values.,diff} @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) @@ -13814,7 +16933,7 @@ class basic_json case value_t::object: { // first pass: traverse this object's elements - for (auto it = source.begin(); it != source.end(); ++it) + for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch const auto key = json_pointer::escape(it.key()); @@ -13836,7 +16955,7 @@ class basic_json } // second pass: traverse other object's elements - for (auto it = target.begin(); it != target.end(); ++it) + for (auto it = target.cbegin(); it != target.cend(); ++it) { if (source.find(it.key()) == source.end()) { @@ -13869,418 +16988,86 @@ class basic_json } /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& patch) + { + if (patch.is_object()) + { + if (not is_object()) + { + *this = object(); + } + for (auto it = patch.begin(); it != patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = patch; + } + } + + /// @} }; - -///////////// -// presets // -///////////// - -/*! -@brief default JSON class - -This type is the default specialization of the @ref basic_json class which -uses the standard template types. - -@since version 1.0.0 -*/ -using json = basic_json<>; - -////////////////// -// json_pointer // -////////////////// - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - auto result = &j; - - // in case no reference tokens exist, return a reference to the JSON value - // j which will be overwritten by a primitive value - for (const auto& reference_token : reference_tokens) - { - switch (result->m_type) - { - case detail::value_t::null: - { - if (reference_token == "0") - { - // start a new array if reference token is 0 - result = &result->operator[](0); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } - - case detail::value_t::object: - { - // create an entry in the object - result = &result->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - // create an entry in the array - JSON_TRY - { - result = &result->operator[](static_cast(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - /* - The following code is only reached if there exists a reference - token _and_ the current value is primitive. In this case, we have - an error situation, because primitive values may only occur as - single value; that is, with an empty list of reference tokens. - */ - default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); - } - } - - return *result; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - // convert null values to arrays or objects before continuing - if (ptr->m_type == detail::value_t::null) - { - // check if reference token is a number - const bool nums = - std::all_of(reference_token.begin(), reference_token.end(), - [](const char x) - { - return (x >= '0' and x <= '9'); - }); - - // change value to array for numbers or "-" or to object otherwise - *ptr = (nums or reference_token == "-") - ? detail::value_t::array - : detail::value_t::object; - } - - switch (ptr->m_type) - { - case detail::value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - if (reference_token == "-") - { - // explicitly treat "-" as index beyond the end - ptr = &ptr->operator[](ptr->m_value.array->size()); - } - else - { - // convert array index to number; unchecked access - JSON_TRY - { - ptr = &ptr->operator[]( - static_cast(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // note: at performs range check - JSON_TRY - { - ptr = &ptr->at(static_cast(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -const NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // use unchecked array access - JSON_TRY - { - ptr = &ptr->operator[]( - static_cast(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -const NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // note: at performs range check - JSON_TRY - { - ptr = &ptr->at(static_cast(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -void json_pointer::flatten(const std::string& reference_string, - const NLOHMANN_BASIC_JSON_TPL& value, - NLOHMANN_BASIC_JSON_TPL& result) -{ - switch (value.m_type) - { - case detail::value_t::array: - { - if (value.m_value.array->empty()) - { - // flatten empty array as null - result[reference_string] = nullptr; - } - else - { - // iterate array and use index as reference string - for (std::size_t i = 0; i < value.m_value.array->size(); ++i) - { - flatten(reference_string + "/" + std::to_string(i), - value.m_value.array->operator[](i), result); - } - } - break; - } - - case detail::value_t::object: - { - if (value.m_value.object->empty()) - { - // flatten empty object as null - result[reference_string] = nullptr; - } - else - { - // iterate object and use keys as reference string - for (const auto& element : *value.m_value.object) - { - flatten(reference_string + "/" + escape(element.first), element.second, result); - } - } - break; - } - - default: - { - // add primitive value with its reference string - result[reference_string] = value; - break; - } - } -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL -json_pointer::unflatten(const NLOHMANN_BASIC_JSON_TPL& value) -{ - if (JSON_UNLIKELY(not value.is_object())) - { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); - } - - NLOHMANN_BASIC_JSON_TPL result; - - // iterate the JSON object values - for (const auto& element : *value.m_value.object) - { - if (JSON_UNLIKELY(not element.second.is_primitive())) - { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); - } - - // assign value to reference pointed to by JSON pointer; Note that if - // the JSON pointer is "" (i.e., points to the whole value), function - // get_and_create returns a reference to result itself. An assignment - // will then create a primitive value. - json_pointer(element.first).get_and_create(result) = element.second; - } - - return result; -} - -inline bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept -{ - return (lhs.reference_tokens == rhs.reference_tokens); -} - -inline bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept -{ - return not (lhs == rhs); -} } // namespace nlohmann - /////////////////////// // nonmember support // /////////////////////// @@ -14324,7 +17111,7 @@ struct hash /// @note: do not remove the space after '<', /// see https://github.com/nlohmann/json/pull/679 template<> -struct less< ::nlohmann::detail::value_t> +struct less< ::nlohmann::detail::value_t> { /*! @brief compare two value_t enum values @@ -14375,6 +17162,9 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std return nlohmann::json::json_pointer(std::string(s, n)); } +// #include + + // restore GCC/clang diagnostic settings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop @@ -14390,7 +17180,11 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std #undef JSON_LIKELY #undef JSON_UNLIKELY #undef JSON_DEPRECATED +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 #undef NLOHMANN_BASIC_JSON_TPL_DECLARATION #undef NLOHMANN_BASIC_JSON_TPL +#undef NLOHMANN_JSON_HAS_HELPER + #endif