update to json 3.2 and catch 2.3, clean cmake
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a1a64a8..19d6f46 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,42 +1,45 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.8)
+
+
+##
+## PROJECT
+##
+project(inja LANGUAGES CXX VERSION 1.0.1)
+set(INJA_VERSION ${PROJECT_VERSION})
+
##
## OPTIONS
##
option(BUILD_UNIT_TESTS "Build the unit tests" ON)
+option(HUNTER_ENABLED "Use hunter to manage dependencies" OFF)
+
##
## HUNTER
##
-option(HUNTER_ENABLED "Use hunter to manage dependencies" OFF)
if(HUNTER_ENABLED)
- include("cmake/HunterGate.cmake")
- HunterGate(
- URL "https://github.com/ruslo/hunter/archive/v0.19.156.tar.gz"
- SHA1 "8d5e4635b137365e0d1ade4d60accf4e2bb41f0d"
- )
+ include("cmake/HunterGate.cmake")
+ HunterGate(
+ URL "https://github.com/ruslo/hunter/archive/v0.19.156.tar.gz"
+ SHA1 "8d5e4635b137365e0d1ade4d60accf4e2bb41f0d"
+ )
endif()
-##
-## PROJECT
-## name and version
-##
-project(inja LANGUAGES CXX VERSION 1.0.1)
-set(INJA_VERSION ${PROJECT_VERSION})
##
## CONFIGURATION
##
-# set(CMAKE_CXX_COMPILER g++-7)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(INJA_SOURCE_DIR src)
set(INJA_HEADER_INSTALL_DIR include)
if(WIN32 AND MSVC AND MSVC_VERSION LESS 1900)
- message(FATAL_ERROR "[${PROJECT_NAME}] Visual Studio versions prior to 2015 do not support the noexcept keyword, which is used in the JSON library.")
+ message(FATAL_ERROR "[${PROJECT_NAME}] Visual Studio versions prior to 2015 do not support the noexcept keyword, which is used in the JSON library.")
endif()
+
##
## TESTS
## create and configure the unit test target
@@ -46,28 +49,30 @@
add_subdirectory(test)
endif()
+
##
## TARGETS
## Build targets for the interface library
##
add_library(inja INTERFACE)
target_include_directories(inja INTERFACE
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INJA_SOURCE_DIR}>
- $<INSTALL_INTERFACE:${INJA_HEADER_INSTALL_DIR}>
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/${INJA_SOURCE_DIR}>
+ $<INSTALL_INTERFACE:${INJA_HEADER_INSTALL_DIR}>
)
if(HUNTER_ENABLED) # Use Hunter to manage dependencies
- # Add JSON package
- hunter_add_package(nlohmann_json)
- find_package(nlohmann_json CONFIG REQUIRED)
- # Add dependencies to target
- target_link_libraries(inja INTERFACE nlohmann_json)
+ # Add JSON package
+ hunter_add_package(nlohmann_json)
+ find_package(nlohmann_json CONFIG REQUIRED)
+ # Add dependencies to target
+ target_link_libraries(inja INTERFACE nlohmann_json)
else()
- target_include_directories(inja INTERFACE
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/test/thirdparty>
- $<INSTALL_INTERFACE:${INJA_HEADER_INSTALL_DIR}>
- )
+ target_include_directories(inja INTERFACE
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/test/thirdparty>
+ $<INSTALL_INTERFACE:${INJA_HEADER_INSTALL_DIR}>
+ )
endif()
+
##
## INSTALL
## install header files, generate and install cmake config files for find_package()
@@ -81,42 +86,37 @@
set(namespace "${PROJECT_NAME}::")
include(CMakePackageConfigHelpers)
-# Use:
-# * PROJECT_VERSION
+
write_basic_package_version_file(
- "${version_config}" COMPATIBILITY SameMajorVersion
+ "${version_config}" COMPATIBILITY SameMajorVersion
)
-
-# Use:
-# * TARGETS_EXPORT_NAME
-# * PROJECT_NAME
configure_package_config_file(
- "cmake/Config.cmake.in"
- "${project_config}"
- INSTALL_DESTINATION "${config_install_dir}"
+ "cmake/Config.cmake.in"
+ "${project_config}"
+ INSTALL_DESTINATION "${config_install_dir}"
)
install(
- TARGETS inja
- EXPORT "${TARGETS_EXPORT_NAME}"
- LIBRARY DESTINATION "lib"
- ARCHIVE DESTINATION "lib"
- RUNTIME DESTINATION "bin"
- INCLUDES DESTINATION "${include_install_dir}"
+ TARGETS inja
+ EXPORT "${TARGETS_EXPORT_NAME}"
+ LIBRARY DESTINATION "lib"
+ ARCHIVE DESTINATION "lib"
+ RUNTIME DESTINATION "bin"
+ INCLUDES DESTINATION "${include_install_dir}"
)
install(
- FILES ${INJA_SOURCE_DIR}/inja.hpp
- DESTINATION "${include_install_dir}"
+ FILES ${INJA_SOURCE_DIR}/inja.hpp
+ DESTINATION "${include_install_dir}"
)
install(
- FILES "${project_config}" "${version_config}"
- DESTINATION "${config_install_dir}"
+ FILES "${project_config}" "${version_config}"
+ DESTINATION "${config_install_dir}"
)
install(
- EXPORT "${TARGETS_EXPORT_NAME}"
- NAMESPACE "${namespace}"
- DESTINATION "${config_install_dir}"
+ EXPORT "${TARGETS_EXPORT_NAME}"
+ NAMESPACE "${namespace}"
+ DESTINATION "${config_install_dir}"
)
diff --git a/LICENSE b/LICENSE
index 3ff4b9b..d2ea3c5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017 lbersch
+Copyright (c) 2018 lbersch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/src/inja.hpp b/src/inja.hpp
index 518246a..3ceffa0 100644
--- a/src/inja.hpp
+++ b/src/inja.hpp
@@ -1,6 +1,6 @@
/*
Inja - A Template Engine for Modern C++
-version 1.0.0
+version 1.1.0
https://github.com/pantor/inja
Licensed under the MIT License <https://opensource.org/licenses/MIT>.
@@ -29,8 +29,8 @@
#define PANTOR_INJA_HPP
#define PANTOR_INJA_VERSION_MAJOR 1
-#define PANTOR_INJA_VERSION_MINOR 0
-#define PANTOR_INJA_VERSION_PATCH 1
+#define PANTOR_INJA_VERSION_MINOR 1
+#define PANTOR_INJA_VERSION_PATCH 0
#include <algorithm>
diff --git a/src/nlohmann/json.hpp b/src/nlohmann/json.hpp
index 3dcb834..b80386f 100644
--- a/src/nlohmann/json.hpp
+++ b/src/nlohmann/json.hpp
@@ -1,10 +1,11 @@
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++
-| | |__ | | | | | | version 3.1.1
+| | |__ | | | | | | version 3.2.0
|_____|_____|_____|_|___| https://github.com/nlohmann/json
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+SPDX-License-Identifier: MIT
Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -30,8 +31,8 @@
#define NLOHMANN_JSON_HPP
#define NLOHMANN_JSON_VERSION_MAJOR 3
-#define NLOHMANN_JSON_VERSION_MINOR 1
-#define NLOHMANN_JSON_VERSION_PATCH 1
+#define NLOHMANN_JSON_VERSION_MINOR 2
+#define NLOHMANN_JSON_VERSION_PATCH 0
#include <algorithm> // all_of, find, for_each
#include <cassert> // assert
@@ -66,10 +67,10 @@
@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))
+([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
-template<typename = void, typename = void>
+template<typename T = void, typename SFINAE = void>
struct adl_serializer;
template<template<typename U, typename V, typename... Args> class ObjectType =
@@ -118,13 +119,15 @@
// 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__) && !(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"
+#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK)
+ #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__) && !(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
#endif
#endif
@@ -154,10 +157,12 @@
#define JSON_THROW(exception) throw exception
#define JSON_TRY try
#define JSON_CATCH(exception) catch(exception)
+ #define JSON_INTERNAL_CATCH(exception) catch(exception)
#else
#define JSON_THROW(exception) std::abort()
#define JSON_TRY if(true)
#define JSON_CATCH(exception) if(false)
+ #define JSON_INTERNAL_CATCH(exception) if(false)
#endif
// override exception macros
@@ -172,6 +177,11 @@
#if defined(JSON_CATCH_USER)
#undef JSON_CATCH
#define JSON_CATCH JSON_CATCH_USER
+ #define JSON_INTERNAL_CATCH JSON_CATCH_USER
+#endif
+#if defined(JSON_INTERNAL_CATCH_USER)
+ #undef JSON_INTERNAL_CATCH
+ #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER
#endif
// manual branch prediction
@@ -228,41 +238,17 @@
std::is_integral<decltype(detect(std::declval<T>()))>::value; \
}
-// #include <nlohmann/detail/meta.hpp>
+// #include <nlohmann/detail/meta/cpp_future.hpp>
#include <ciso646> // not
#include <cstddef> // size_t
-#include <limits> // numeric_limits
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
-#include <utility> // declval
-
-// #include <nlohmann/json_fwd.hpp>
-
-// #include <nlohmann/detail/macro_scope.hpp>
-
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
{
-/////////////
-// helpers //
-/////////////
-
-template<typename> struct is_basic_json : std::false_type {};
-
-NLOHMANN_BASIC_JSON_TPL_DECLARATION
-struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};
-
// alias templates to reduce boilerplate
template<bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
@@ -325,6 +311,54 @@
template<unsigned N> struct priority_tag : priority_tag < N - 1 > {};
template<> struct priority_tag<0> {};
+// taken from ranges-v3
+template<typename T>
+struct static_const
+{
+ static constexpr T value{};
+};
+
+template<typename T>
+constexpr T static_const<T>::value;
+}
+}
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
+
+
+#include <ciso646> // not
+#include <limits> // numeric_limits
+#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
+#include <utility> // declval
+
+// #include <nlohmann/json_fwd.hpp>
+
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+
+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
+{
+/////////////
+// helpers //
+/////////////
+
+template<typename> struct is_basic_json : std::false_type {};
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};
+
////////////////////////
// has_/is_ functions //
////////////////////////
@@ -353,6 +387,17 @@
std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value;
};
+template<bool B, class RealType, class CompatibleStringType>
+struct is_compatible_string_type_impl : std::false_type {};
+
+template<class RealType, class CompatibleStringType>
+struct is_compatible_string_type_impl<true, RealType, CompatibleStringType>
+{
+ static constexpr auto value =
+ std::is_same<typename RealType::value_type, typename CompatibleStringType::value_type>::value and
+ std::is_constructible<RealType, CompatibleStringType>::value;
+};
+
template<class BasicJsonType, class CompatibleObjectType>
struct is_compatible_object_type
{
@@ -363,6 +408,15 @@
typename BasicJsonType::object_t, CompatibleObjectType >::value;
};
+template<class BasicJsonType, class CompatibleStringType>
+struct is_compatible_string_type
+{
+ static auto constexpr value = is_compatible_string_type_impl <
+ conjunction<negation<std::is_same<void, CompatibleStringType>>,
+ has_value_type<CompatibleStringType>>::value,
+ typename BasicJsonType::string_t, CompatibleStringType >::value;
+};
+
template<typename BasicJsonType, typename T>
struct is_basic_json_nested_type
{
@@ -466,7 +520,7 @@
{
static constexpr bool value =
not std::is_base_of<std::istream, CompatibleCompleteType>::value and
- not std::is_same<BasicJsonType, CompatibleCompleteType>::value and
+ not is_basic_json<CompatibleCompleteType>::value and
not is_basic_json_nested_type<BasicJsonType, CompatibleCompleteType>::value and
has_to_json<BasicJsonType, CompatibleCompleteType>::value;
};
@@ -477,16 +531,6 @@
is_compatible_complete_type<BasicJsonType, CompatibleType>>
{
};
-
-// taken from ranges-v3
-template<typename T>
-struct static_const
-{
- static constexpr T value{};
-};
-
-template<typename T>
-constexpr T static_const<T>::value;
}
}
@@ -908,9 +952,11 @@
#include <ciso646> // and, not
#include <forward_list> // forward_list
#include <iterator> // inserter, front_inserter, end
+#include <map> // map
#include <string> // string
#include <tuple> // tuple, make_tuple
#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
+#include <unordered_map> // unordered_map
#include <utility> // pair, declval
#include <valarray> // valarray
@@ -918,7 +964,9 @@
// #include <nlohmann/detail/macro_scope.hpp>
-// #include <nlohmann/detail/meta.hpp>
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
// #include <nlohmann/detail/value_t.hpp>
@@ -927,6 +975,16 @@
{
namespace detail
{
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename std::nullptr_t& n)
+{
+ if (JSON_UNLIKELY(not j.is_null()))
+ {
+ JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name())));
+ }
+ n = nullptr;
+}
+
// overloads for basic_json template parameters
template<typename BasicJsonType, typename ArithmeticType,
enable_if_t<std::is_arithmetic<ArithmeticType>::value and
@@ -977,6 +1035,23 @@
s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
}
+template <
+ typename BasicJsonType, typename CompatibleStringType,
+ enable_if_t <
+ is_compatible_string_type<BasicJsonType, CompatibleStringType>::value and
+ not std::is_same<typename BasicJsonType::string_t,
+ CompatibleStringType>::value,
+ int > = 0 >
+void from_json(const BasicJsonType& j, CompatibleStringType& 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<const typename BasicJsonType::string_t*>();
+}
+
template<typename BasicJsonType>
void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)
{
@@ -1184,6 +1259,44 @@
from_json_tuple_impl(j, t, index_sequence_for<Args...> {});
}
+template <typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
+ typename = enable_if_t<not std::is_constructible<
+ typename BasicJsonType::string_t, Key>::value>>
+void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
+{
+ 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 (const auto& p : j)
+ {
+ if (JSON_UNLIKELY(not p.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
+ }
+ m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
+ }
+}
+
+template <typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator,
+ typename = enable_if_t<not std::is_constructible<
+ typename BasicJsonType::string_t, Key>::value>>
+void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
+{
+ 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 (const auto& p : j)
+ {
+ if (JSON_UNLIKELY(not p.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
+ }
+ m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
+ }
+}
+
struct from_json_fn
{
private:
@@ -1238,10 +1351,143 @@
#include <valarray> // valarray
#include <vector> // vector
-// #include <nlohmann/detail/meta.hpp>
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
// #include <nlohmann/detail/value_t.hpp>
+// #include <nlohmann/detail/iterators/iteration_proxy.hpp>
+
+
+#include <cstddef> // size_t
+#include <string> // string, to_string
+#include <iterator> // input_iterator_tag
+
+// #include <nlohmann/detail/value_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+/// proxy class for the items() function
+template<typename IteratorType> class iteration_proxy
+{
+ private:
+ /// helper class for iteration
+ class iteration_proxy_internal
+ {
+ public:
+ using difference_type = std::ptrdiff_t;
+ using value_type = iteration_proxy_internal;
+ using pointer = iteration_proxy_internal*;
+ using reference = iteration_proxy_internal&;
+ using iterator_category = std::input_iterator_tag;
+
+ private:
+ /// the iterator
+ IteratorType anchor;
+ /// an index for arrays (used to create key names)
+ std::size_t array_index = 0;
+ /// last stringified array index
+ mutable std::size_t array_index_last = 0;
+ /// a string representation of the array index
+ mutable std::string array_index_str = "0";
+ /// an empty string (to return a reference for primitive values)
+ const std::string empty_str = "";
+
+ public:
+ explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {}
+
+ iteration_proxy_internal(const iteration_proxy_internal&) = default;
+ iteration_proxy_internal& operator=(const iteration_proxy_internal&) = default;
+
+ /// dereference operator (needed for range-based for)
+ iteration_proxy_internal& operator*()
+ {
+ return *this;
+ }
+
+ /// increment operator (needed for range-based for)
+ iteration_proxy_internal& operator++()
+ {
+ ++anchor;
+ ++array_index;
+
+ return *this;
+ }
+
+ /// equality operator (needed for InputIterator)
+ bool operator==(const iteration_proxy_internal& o) const noexcept
+ {
+ return anchor == o.anchor;
+ }
+
+ /// inequality operator (needed for range-based for)
+ bool operator!=(const iteration_proxy_internal& o) const noexcept
+ {
+ return anchor != o.anchor;
+ }
+
+ /// return key of the iterator
+ const std::string& key() const
+ {
+ assert(anchor.m_object != nullptr);
+
+ switch (anchor.m_object->type())
+ {
+ // use integer array index as key
+ case value_t::array:
+ {
+ if (array_index != array_index_last)
+ {
+ array_index_str = std::to_string(array_index);
+ array_index_last = array_index;
+ }
+ return array_index_str;
+ }
+
+ // use key from the object
+ case value_t::object:
+ return anchor.key();
+
+ // use an empty key for all primitive types
+ default:
+ return empty_str;
+ }
+ }
+
+ /// return value of the iterator
+ typename IteratorType::reference value() const
+ {
+ return anchor.value();
+ }
+ };
+
+ /// the container to iterate
+ typename IteratorType::reference container;
+
+ public:
+ /// construct iteration proxy from a container
+ explicit iteration_proxy(typename IteratorType::reference cont) noexcept
+ : container(cont) {}
+
+ /// return iterator begin (needed for range-based for)
+ iteration_proxy_internal begin() noexcept
+ {
+ return iteration_proxy_internal(container.begin());
+ }
+
+ /// return iterator end (needed for range-based for)
+ iteration_proxy_internal end() noexcept
+ {
+ return iteration_proxy_internal(container.end());
+ }
+};
+}
+}
+
namespace nlohmann
{
@@ -1283,6 +1529,16 @@
j.m_value = std::move(s);
j.assert_invariant();
}
+
+ template<typename BasicJsonType, typename CompatibleStringType,
+ enable_if_t<not std::is_same<CompatibleStringType, typename BasicJsonType::string_t>::value,
+ int> = 0>
+ static void construct(BasicJsonType& j, const CompatibleStringType& str)
+ {
+ j.m_type = value_t::string;
+ j.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
+ j.assert_invariant();
+ }
};
template<>
@@ -1479,7 +1735,7 @@
template<typename BasicJsonType, typename T,
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
-void to_json(BasicJsonType& j, std::valarray<T> arr)
+void to_json(BasicJsonType& j, const std::valarray<T>& arr)
{
external_constructor<value_t::array>::construct(j, std::move(arr));
}
@@ -1516,6 +1772,14 @@
j = {p.first, p.second};
}
+// for https://github.com/nlohmann/json/pull/1134
+template<typename BasicJsonType, typename T,
+ enable_if_t<std::is_same<T, typename iteration_proxy<typename BasicJsonType::iterator>::iteration_proxy_internal>::value, int> = 0>
+void to_json(BasicJsonType& j, T b) noexcept
+{
+ j = {{b.key(), b.value()}};
+}
+
template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...>)
{
@@ -1572,12 +1836,9 @@
// #include <nlohmann/detail/input/input_adapters.hpp>
-#include <algorithm> // min
-#include <array> // array
#include <cassert> // assert
#include <cstddef> // size_t
#include <cstring> // strlen
-#include <ios> // streamsize, streamoff, streampos
#include <istream> // istream
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next
#include <memory> // shared_ptr, make_shared, addressof
@@ -1593,6 +1854,9 @@
{
namespace detail
{
+/// the supported input formats
+enum class input_format_t { json, cbor, msgpack, ubjson };
+
////////////////////
// input adapters //
////////////////////
@@ -1601,19 +1865,17 @@
@brief abstract input adapter interface
Produces a stream of std::char_traits<char>::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<char>::eof(). This value is typically -1, but could be any
-arbitrary value which is not a valid char value.
+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<char>::eof(). This value is typically -1, but
+could be any arbitrary value which is not a valid char value.
*/
struct input_adapter_protocol
{
/// get a character [0,255] or std::char_traits<char>::eof().
virtual std::char_traits<char>::int_type get_character() = 0;
- /// restore the last non-eof() character to input
- virtual void unget_character() = 0;
virtual ~input_adapter_protocol() = default;
};
@@ -1641,34 +1903,7 @@
explicit input_stream_adapter(std::istream& i)
: is(i), sb(*i.rdbuf())
- {
- // skip byte order mark
- std::char_traits<char>::int_type c;
- if ((c = get_character()) == 0xEF)
- {
- if ((c = get_character()) == 0xBB)
- {
- if ((c = get_character()) == 0xBF)
- {
- return; // Ignore BOM
- }
- else if (c != std::char_traits<char>::eof())
- {
- is.unget();
- }
- is.putback('\xBB');
- }
- else if (c != std::char_traits<char>::eof())
- {
- is.unget();
- }
- is.putback('\xEF');
- }
- else if (c != std::char_traits<char>::eof())
- {
- is.unget(); // no byte order mark; process as usual
- }
- }
+ {}
// delete because of pointer members
input_stream_adapter(const input_stream_adapter&) = delete;
@@ -1682,11 +1917,6 @@
return sb.sbumpc();
}
- void unget_character() override
- {
- sb.sungetc(); // is.unget() avoided for performance
- }
-
private:
/// the associated input stream
std::istream& is;
@@ -1698,14 +1928,8 @@
{
public:
input_buffer_adapter(const char* b, const std::size_t l)
- : cursor(b), limit(b + l), start(b)
- {
- // skip byte order mark
- if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF')
- {
- cursor += 3;
- }
- }
+ : cursor(b), limit(b + l)
+ {}
// delete because of pointer members
input_buffer_adapter(const input_buffer_adapter&) = delete;
@@ -1721,21 +1945,164 @@
return std::char_traits<char>::eof();
}
- void unget_character() noexcept override
- {
- if (JSON_LIKELY(cursor > start))
- {
- --cursor;
- }
- }
-
private:
/// pointer to the current character
const char* cursor;
/// pointer past the last character
- const char* limit;
- /// pointer to the first character
- const char* start;
+ const char* const limit;
+};
+
+template<typename WideStringType>
+class wide_string_input_adapter : public input_adapter_protocol
+{
+ public:
+ explicit wide_string_input_adapter(const WideStringType& w) : str(w) {}
+
+ std::char_traits<char>::int_type get_character() noexcept override
+ {
+ // check if buffer needs to be filled
+ if (utf8_bytes_index == utf8_bytes_filled)
+ {
+ if (sizeof(typename WideStringType::value_type) == 2)
+ {
+ fill_buffer_utf16();
+ }
+ else
+ {
+ fill_buffer_utf32();
+ }
+
+ assert(utf8_bytes_filled > 0);
+ assert(utf8_bytes_index == 0);
+ }
+
+ // use buffer
+ assert(utf8_bytes_filled > 0);
+ assert(utf8_bytes_index < utf8_bytes_filled);
+ return utf8_bytes[utf8_bytes_index++];
+ }
+
+ private:
+ void fill_buffer_utf16()
+ {
+ utf8_bytes_index = 0;
+
+ if (current_wchar == str.size())
+ {
+ utf8_bytes[0] = std::char_traits<char>::eof();
+ utf8_bytes_filled = 1;
+ }
+ else
+ {
+ // get the current character
+ const int wc = static_cast<int>(str[current_wchar++]);
+
+ // UTF-16 to UTF-8 encoding
+ if (wc < 0x80)
+ {
+ utf8_bytes[0] = wc;
+ utf8_bytes_filled = 1;
+ }
+ else if (wc <= 0x7FF)
+ {
+ utf8_bytes[0] = 0xC0 | ((wc >> 6));
+ utf8_bytes[1] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 2;
+ }
+ else if (0xD800 > wc or wc >= 0xE000)
+ {
+ utf8_bytes[0] = 0xE0 | ((wc >> 12));
+ utf8_bytes[1] = 0x80 | ((wc >> 6) & 0x3F);
+ utf8_bytes[2] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 3;
+ }
+ else
+ {
+ if (current_wchar < str.size())
+ {
+ const int wc2 = static_cast<int>(str[current_wchar++]);
+ const int charcode = 0x10000 + (((wc & 0x3FF) << 10) | (wc2 & 0x3FF));
+ utf8_bytes[0] = 0xf0 | (charcode >> 18);
+ utf8_bytes[1] = 0x80 | ((charcode >> 12) & 0x3F);
+ utf8_bytes[2] = 0x80 | ((charcode >> 6) & 0x3F);
+ utf8_bytes[3] = 0x80 | (charcode & 0x3F);
+ utf8_bytes_filled = 4;
+ }
+ else
+ {
+ // unknown character
+ ++current_wchar;
+ utf8_bytes[0] = wc;
+ utf8_bytes_filled = 1;
+ }
+ }
+ }
+ }
+
+ void fill_buffer_utf32()
+ {
+ utf8_bytes_index = 0;
+
+ if (current_wchar == str.size())
+ {
+ utf8_bytes[0] = std::char_traits<char>::eof();
+ utf8_bytes_filled = 1;
+ }
+ else
+ {
+ // get the current character
+ const int wc = static_cast<int>(str[current_wchar++]);
+
+ // UTF-32 to UTF-8 encoding
+ if (wc < 0x80)
+ {
+ utf8_bytes[0] = wc;
+ utf8_bytes_filled = 1;
+ }
+ else if (wc <= 0x7FF)
+ {
+ utf8_bytes[0] = 0xC0 | ((wc >> 6) & 0x1F);
+ utf8_bytes[1] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 2;
+ }
+ else if (wc <= 0xFFFF)
+ {
+ utf8_bytes[0] = 0xE0 | ((wc >> 12) & 0x0F);
+ utf8_bytes[1] = 0x80 | ((wc >> 6) & 0x3F);
+ utf8_bytes[2] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 3;
+ }
+ else if (wc <= 0x10FFFF)
+ {
+ utf8_bytes[0] = 0xF0 | ((wc >> 18 ) & 0x07);
+ utf8_bytes[1] = 0x80 | ((wc >> 12) & 0x3F);
+ utf8_bytes[2] = 0x80 | ((wc >> 6) & 0x3F);
+ utf8_bytes[3] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 4;
+ }
+ else
+ {
+ // unknown character
+ utf8_bytes[0] = wc;
+ utf8_bytes_filled = 1;
+ }
+ }
+ }
+
+ private:
+ /// the wstring to process
+ const WideStringType& str;
+
+ /// index of the current wchar in str
+ std::size_t current_wchar = 0;
+
+ /// a buffer for UTF-8 bytes
+ std::array<std::char_traits<char>::int_type, 4> utf8_bytes = {{0, 0, 0, 0}};
+
+ /// index to the utf8_codes array for the next valid byte
+ std::size_t utf8_bytes_index = 0;
+ /// number of valid bytes in the utf8_codes array
+ std::size_t utf8_bytes_filled = 0;
};
class input_adapter
@@ -1751,6 +2118,15 @@
input_adapter(std::istream&& i)
: ia(std::make_shared<input_stream_adapter>(i)) {}
+ input_adapter(const std::wstring& ws)
+ : ia(std::make_shared<wide_string_input_adapter<std::wstring>>(ws)) {}
+
+ input_adapter(const std::u16string& ws)
+ : ia(std::make_shared<wide_string_input_adapter<std::u16string>>(ws)) {}
+
+ input_adapter(const std::u32string& ws)
+ : ia(std::make_shared<wide_string_input_adapter<std::u32string>>(ws)) {}
+
/// input adapter for buffer
template<typename CharT,
typename std::enable_if<
@@ -1840,10 +2216,8 @@
#include <clocale> // localeconv
#include <cstddef> // size_t
#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull
+#include <cstdio> // snprintf
#include <initializer_list> // initializer_list
-#include <ios> // hex, uppercase
-#include <iomanip> // setw, setfill
-#include <sstream> // stringstream
#include <string> // char_traits, string
#include <vector> // vector
@@ -1871,6 +2245,7 @@
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
public:
/// token types for the parser
@@ -1932,12 +2307,14 @@
return "end of input";
case token_type::literal_or_value:
return "'[', '{', or a literal";
+ // LCOV_EXCL_START
default: // catch non-enum values
- return "unknown token"; // LCOV_EXCL_LINE
+ return "unknown token";
+ // LCOV_EXCL_STOP
}
}
- explicit lexer(detail::input_adapter_t adapter)
+ explicit lexer(detail::input_adapter_t&& adapter)
: ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {}
// delete because of pointer members
@@ -2585,11 +2962,13 @@
goto scan_number_any1;
}
+ // LCOV_EXCL_START
default:
{
// all other characters are rejected outside scan_number()
- assert(false); // LCOV_EXCL_LINE
+ assert(false);
}
+ // LCOV_EXCL_STOP
}
scan_number_minus:
@@ -2919,7 +3298,16 @@
std::char_traits<char>::int_type get()
{
++chars_read;
- current = ia->get_character();
+ if (next_unget)
+ {
+ // just reset the next_unget variable and work with current
+ next_unget = false;
+ }
+ else
+ {
+ current = ia->get_character();
+ }
+
if (JSON_LIKELY(current != std::char_traits<char>::eof()))
{
token_string.push_back(std::char_traits<char>::to_char_type(current));
@@ -2927,13 +3315,20 @@
return current;
}
- /// unget current character (return it again on next get)
+ /*!
+ @brief unget current character (read it again on next get)
+
+ We implement unget by setting variable next_unget to true. The input is not
+ changed - we just simulate ungetting by modifying chars_read and
+ token_string. The next call to get() will behave as if the unget character
+ is read again.
+ */
void unget()
{
+ next_unget = true;
--chars_read;
if (JSON_LIKELY(current != std::char_traits<char>::eof()))
{
- ia->unget_character();
assert(token_string.size() != 0);
token_string.pop_back();
}
@@ -2969,9 +3364,9 @@
}
/// return current string value (implicitly resets the token; useful only once)
- std::string move_string()
+ string_t& get_string()
{
- return std::move(token_buffer);
+ return token_buffer;
}
/////////////////////
@@ -2996,10 +3391,9 @@
if ('\x00' <= c and c <= '\x1F')
{
// escape control characters
- std::stringstream ss;
- ss << "<U+" << std::setw(4) << std::uppercase << std::setfill('0')
- << std::hex << static_cast<int>(c) << ">";
- result += ss.str();
+ char cs[9];
+ snprintf(cs, 9, "<U+%.4X>", static_cast<unsigned char>(c));
+ result += cs;
}
else
{
@@ -3021,8 +3415,43 @@
// actual scanner
/////////////////////
+ /*!
+ @brief skip the UTF-8 byte order mark
+ @return true iff there is no BOM or the correct BOM has been skipped
+ */
+ bool skip_bom()
+ {
+ if (get() == 0xEF)
+ {
+ if (get() == 0xBB and get() == 0xBF)
+ {
+ // we completely parsed the BOM
+ return true;
+ }
+ else
+ {
+ // after reading 0xEF, an unexpected character followed
+ return false;
+ }
+ }
+ else
+ {
+ // the first character is not the beginning of the BOM; unget it to
+ // process is later
+ unget();
+ return true;
+ }
+ }
+
token_type scan()
{
+ // initially, skip the BOM
+ if (chars_read == 0 and not skip_bom())
+ {
+ error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given";
+ return token_type::parse_error;
+ }
+
// read next character and ignore whitespace
do
{
@@ -3092,6 +3521,9 @@
/// the current character
std::char_traits<char>::int_type current = std::char_traits<char>::eof();
+ /// whether the next get() call should just return current
+ bool next_unget = false;
+
/// the number of characters read
std::size_t chars_read = 0;
@@ -3099,7 +3531,7 @@
std::vector<char> token_string {};
/// buffer for variable-length tokens (numbers, strings)
- std::string token_buffer {};
+ string_t token_buffer {};
/// a description of occurred lexer errors
const char* error_message = "";
@@ -3129,8 +3561,926 @@
// #include <nlohmann/detail/macro_scope.hpp>
+// #include <nlohmann/detail/meta/is_sax.hpp>
+
+
+#include <cstdint> // size_t
+#include <utility> // declval
+
+// #include <nlohmann/detail/meta/detected.hpp>
+
+
+#include <type_traits>
+
+// #include <nlohmann/detail/meta/void_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+template <typename...>
+using void_t = void;
+}
+}
+
+
+// http://en.cppreference.com/w/cpp/experimental/is_detected
+namespace nlohmann
+{
+namespace detail
+{
+struct nonesuch
+{
+ nonesuch() = delete;
+ ~nonesuch() = delete;
+ nonesuch(nonesuch const&) = delete;
+ void operator=(nonesuch const&) = delete;
+};
+
+template <class Default,
+ class AlwaysVoid,
+ template <class...> class Op,
+ class... Args>
+struct detector
+{
+ using value_t = std::false_type;
+ using type = Default;
+};
+
+template <class Default, template <class...> class Op, class... Args>
+struct detector<Default, void_t<Op<Args...>>, Op, Args...>
+{
+ using value_t = std::true_type;
+ using type = Op<Args...>;
+};
+
+template <template <class...> class Op, class... Args>
+using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
+
+template <template <class...> class Op, class... Args>
+using detected_t = typename detector<nonesuch, void, Op, Args...>::type;
+
+template <class Default, template <class...> class Op, class... Args>
+using detected_or = detector<Default, void, Op, Args...>;
+
+template <class Default, template <class...> class Op, class... Args>
+using detected_or_t = typename detected_or<Default, Op, Args...>::type;
+
+template <class Expected, template <class...> class Op, class... Args>
+using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
+
+template <class To, template <class...> class Op, class... Args>
+using is_detected_convertible =
+ std::is_convertible<detected_t<Op, Args...>, To>;
+}
+}
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+template <typename T>
+using null_function_t = decltype(std::declval<T&>().null());
+
+template <typename T>
+using boolean_function_t =
+ decltype(std::declval<T&>().boolean(std::declval<bool>()));
+
+template <typename T, typename Integer>
+using number_integer_function_t =
+ decltype(std::declval<T&>().number_integer(std::declval<Integer>()));
+
+template <typename T, typename Unsigned>
+using number_unsigned_function_t =
+ decltype(std::declval<T&>().number_unsigned(std::declval<Unsigned>()));
+
+template <typename T, typename Float, typename String>
+using number_float_function_t = decltype(std::declval<T&>().number_float(
+ std::declval<Float>(), std::declval<const String&>()));
+
+template <typename T, typename String>
+using string_function_t =
+ decltype(std::declval<T&>().string(std::declval<String&>()));
+
+template <typename T>
+using start_object_function_t =
+ decltype(std::declval<T&>().start_object(std::declval<std::size_t>()));
+
+template <typename T, typename String>
+using key_function_t =
+ decltype(std::declval<T&>().key(std::declval<String&>()));
+
+template <typename T>
+using end_object_function_t = decltype(std::declval<T&>().end_object());
+
+template <typename T>
+using start_array_function_t =
+ decltype(std::declval<T&>().start_array(std::declval<std::size_t>()));
+
+template <typename T>
+using end_array_function_t = decltype(std::declval<T&>().end_array());
+
+template <typename T, typename Exception>
+using parse_error_function_t = decltype(std::declval<T&>().parse_error(
+ std::declval<std::size_t>(), std::declval<const std::string&>(),
+ std::declval<const Exception&>()));
+
+template <typename SAX, typename BasicJsonType>
+struct is_sax
+{
+ private:
+ static_assert(is_basic_json<BasicJsonType>::value,
+ "BasicJsonType must be of type basic_json<...>");
+
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+ using exception_t = typename BasicJsonType::exception;
+
+ public:
+ static constexpr bool value =
+ is_detected_exact<bool, null_function_t, SAX>::value &&
+ is_detected_exact<bool, boolean_function_t, SAX>::value &&
+ is_detected_exact<bool, number_integer_function_t, SAX,
+ number_integer_t>::value &&
+ is_detected_exact<bool, number_unsigned_function_t, SAX,
+ number_unsigned_t>::value &&
+ is_detected_exact<bool, number_float_function_t, SAX, number_float_t,
+ string_t>::value &&
+ is_detected_exact<bool, string_function_t, SAX, string_t>::value &&
+ is_detected_exact<bool, start_object_function_t, SAX>::value &&
+ is_detected_exact<bool, key_function_t, SAX, string_t>::value &&
+ is_detected_exact<bool, end_object_function_t, SAX>::value &&
+ is_detected_exact<bool, start_array_function_t, SAX>::value &&
+ is_detected_exact<bool, end_array_function_t, SAX>::value &&
+ is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value;
+};
+
+template <typename SAX, typename BasicJsonType>
+struct is_sax_static_asserts
+{
+ private:
+ static_assert(is_basic_json<BasicJsonType>::value,
+ "BasicJsonType must be of type basic_json<...>");
+
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+ using exception_t = typename BasicJsonType::exception;
+
+ public:
+ static_assert(is_detected_exact<bool, null_function_t, SAX>::value,
+ "Missing/invalid function: bool null()");
+ static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
+ "Missing/invalid function: bool boolean(bool)");
+ static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
+ "Missing/invalid function: bool boolean(bool)");
+ static_assert(
+ is_detected_exact<bool, number_integer_function_t, SAX,
+ number_integer_t>::value,
+ "Missing/invalid function: bool number_integer(number_integer_t)");
+ static_assert(
+ is_detected_exact<bool, number_unsigned_function_t, SAX,
+ number_unsigned_t>::value,
+ "Missing/invalid function: bool number_unsigned(number_unsigned_t)");
+ static_assert(is_detected_exact<bool, number_float_function_t, SAX,
+ number_float_t, string_t>::value,
+ "Missing/invalid function: bool number_float(number_float_t, const string_t&)");
+ static_assert(
+ is_detected_exact<bool, string_function_t, SAX, string_t>::value,
+ "Missing/invalid function: bool string(string_t&)");
+ static_assert(is_detected_exact<bool, start_object_function_t, SAX>::value,
+ "Missing/invalid function: bool start_object(std::size_t)");
+ static_assert(is_detected_exact<bool, key_function_t, SAX, string_t>::value,
+ "Missing/invalid function: bool key(string_t&)");
+ static_assert(is_detected_exact<bool, end_object_function_t, SAX>::value,
+ "Missing/invalid function: bool end_object()");
+ static_assert(is_detected_exact<bool, start_array_function_t, SAX>::value,
+ "Missing/invalid function: bool start_array(std::size_t)");
+ static_assert(is_detected_exact<bool, end_array_function_t, SAX>::value,
+ "Missing/invalid function: bool end_array()");
+ static_assert(
+ is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value,
+ "Missing/invalid function: bool parse_error(std::size_t, const "
+ "std::string&, const exception&)");
+};
+}
+}
+
// #include <nlohmann/detail/input/input_adapters.hpp>
+// #include <nlohmann/detail/input/json_sax.hpp>
+
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+// #include <nlohmann/detail/input/parser.hpp>
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+
+namespace nlohmann
+{
+
+/*!
+@brief SAX interface
+
+This class describes the SAX interface used by @ref nlohmann::json::sax_parse.
+Each function is called in different situations while the input is parsed. The
+boolean return value informs the parser whether to continue processing the
+input.
+*/
+template<typename BasicJsonType>
+struct json_sax
+{
+ /// type for (signed) integers
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ /// type for unsigned integers
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ /// type for floating-point numbers
+ using number_float_t = typename BasicJsonType::number_float_t;
+ /// type for strings
+ using string_t = typename BasicJsonType::string_t;
+
+ /*!
+ @brief a null value was read
+ @return whether parsing should proceed
+ */
+ virtual bool null() = 0;
+
+ /*!
+ @brief a boolean value was read
+ @param[in] val boolean value
+ @return whether parsing should proceed
+ */
+ virtual bool boolean(bool val) = 0;
+
+ /*!
+ @brief an integer number was read
+ @param[in] val integer value
+ @return whether parsing should proceed
+ */
+ virtual bool number_integer(number_integer_t val) = 0;
+
+ /*!
+ @brief an unsigned integer number was read
+ @param[in] val unsigned integer value
+ @return whether parsing should proceed
+ */
+ virtual bool number_unsigned(number_unsigned_t val) = 0;
+
+ /*!
+ @brief an floating-point number was read
+ @param[in] val floating-point value
+ @param[in] s raw token value
+ @return whether parsing should proceed
+ */
+ virtual bool number_float(number_float_t val, const string_t& s) = 0;
+
+ /*!
+ @brief a string was read
+ @param[in] val string value
+ @return whether parsing should proceed
+ @note It is safe to move the passed string.
+ */
+ virtual bool string(string_t& val) = 0;
+
+ /*!
+ @brief the beginning of an object was read
+ @param[in] elements number of object elements or -1 if unknown
+ @return whether parsing should proceed
+ @note binary formats may report the number of elements
+ */
+ virtual bool start_object(std::size_t elements) = 0;
+
+ /*!
+ @brief an object key was read
+ @param[in] val object key
+ @return whether parsing should proceed
+ @note It is safe to move the passed string.
+ */
+ virtual bool key(string_t& val) = 0;
+
+ /*!
+ @brief the end of an object was read
+ @return whether parsing should proceed
+ */
+ virtual bool end_object() = 0;
+
+ /*!
+ @brief the beginning of an array was read
+ @param[in] elements number of array elements or -1 if unknown
+ @return whether parsing should proceed
+ @note binary formats may report the number of elements
+ */
+ virtual bool start_array(std::size_t elements) = 0;
+
+ /*!
+ @brief the end of an array was read
+ @return whether parsing should proceed
+ */
+ virtual bool end_array() = 0;
+
+ /*!
+ @brief a parse error occurred
+ @param[in] position the position in the input where the error occurs
+ @param[in] last_token the last read token
+ @param[in] error_msg a detailed error message
+ @return whether parsing should proceed (must return false)
+ */
+ virtual bool parse_error(std::size_t position,
+ const std::string& last_token,
+ const detail::exception& ex) = 0;
+
+ virtual ~json_sax() = default;
+};
+
+
+namespace detail
+{
+/*!
+@brief SAX implementation to create a JSON value from SAX events
+
+This class implements the @ref json_sax interface and processes the SAX events
+to create a JSON value which makes it basically a DOM parser. The structure or
+hierarchy of the JSON value is managed by the stack `ref_stack` which contains
+a pointer to the respective array or object for each recursion depth.
+
+After successful parsing, the value that is passed by reference to the
+constructor contains the parsed value.
+
+@tparam BasicJsonType the JSON type
+*/
+template<typename BasicJsonType>
+class json_sax_dom_parser
+{
+ public:
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+
+ /*!
+ @param[in, out] r reference to a JSON value that is manipulated while
+ parsing
+ @param[in] allow_exceptions_ whether parse errors yield exceptions
+ */
+ explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true)
+ : root(r), allow_exceptions(allow_exceptions_)
+ {}
+
+ bool null()
+ {
+ handle_value(nullptr);
+ return true;
+ }
+
+ bool boolean(bool val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_integer(number_integer_t val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_unsigned(number_unsigned_t val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_float(number_float_t val, const string_t&)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool string(string_t& val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool start_object(std::size_t len)
+ {
+ ref_stack.push_back(handle_value(BasicJsonType::value_t::object));
+
+ if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+ {
+ JSON_THROW(out_of_range::create(408,
+ "excessive object size: " + std::to_string(len)));
+ }
+
+ return true;
+ }
+
+ bool key(string_t& val)
+ {
+ // add null at given key and store the reference for later
+ object_element = &(ref_stack.back()->m_value.object->operator[](val));
+ return true;
+ }
+
+ bool end_object()
+ {
+ ref_stack.pop_back();
+ return true;
+ }
+
+ bool start_array(std::size_t len)
+ {
+ ref_stack.push_back(handle_value(BasicJsonType::value_t::array));
+
+ if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+ {
+ JSON_THROW(out_of_range::create(408,
+ "excessive array size: " + std::to_string(len)));
+ }
+
+ return true;
+ }
+
+ bool end_array()
+ {
+ ref_stack.pop_back();
+ return true;
+ }
+
+ bool parse_error(std::size_t, const std::string&,
+ const detail::exception& ex)
+ {
+ errored = true;
+ if (allow_exceptions)
+ {
+ // determine the proper exception type from the id
+ switch ((ex.id / 100) % 100)
+ {
+ case 1:
+ JSON_THROW(*reinterpret_cast<const detail::parse_error*>(&ex));
+ case 4:
+ JSON_THROW(*reinterpret_cast<const detail::out_of_range*>(&ex));
+ // LCOV_EXCL_START
+ case 2:
+ JSON_THROW(*reinterpret_cast<const detail::invalid_iterator*>(&ex));
+ case 3:
+ JSON_THROW(*reinterpret_cast<const detail::type_error*>(&ex));
+ case 5:
+ JSON_THROW(*reinterpret_cast<const detail::other_error*>(&ex));
+ default:
+ assert(false);
+ // LCOV_EXCL_STOP
+ }
+ }
+ return false;
+ }
+
+ constexpr bool is_errored() const
+ {
+ return errored;
+ }
+
+ private:
+ /*!
+ @invariant If the ref stack is empty, then the passed value will be the new
+ root.
+ @invariant If the ref stack contains a value, then it is an array or an
+ object to which we can add elements
+ */
+ template<typename Value>
+ BasicJsonType* handle_value(Value&& v)
+ {
+ if (ref_stack.empty())
+ {
+ root = BasicJsonType(std::forward<Value>(v));
+ return &root;
+ }
+ else
+ {
+ assert(ref_stack.back()->is_array() or ref_stack.back()->is_object());
+ if (ref_stack.back()->is_array())
+ {
+ ref_stack.back()->m_value.array->emplace_back(std::forward<Value>(v));
+ return &(ref_stack.back()->m_value.array->back());
+ }
+ else
+ {
+ assert(object_element);
+ *object_element = BasicJsonType(std::forward<Value>(v));
+ return object_element;
+ }
+ }
+ }
+
+ /// the parsed JSON value
+ BasicJsonType& root;
+ /// stack to model hierarchy of values
+ std::vector<BasicJsonType*> ref_stack;
+ /// helper to hold the reference for the next object element
+ BasicJsonType* object_element = nullptr;
+ /// whether a syntax error occurred
+ bool errored = false;
+ /// whether to throw exceptions in case of errors
+ const bool allow_exceptions = true;
+};
+
+template<typename BasicJsonType>
+class json_sax_dom_callback_parser
+{
+ public:
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+ using parser_callback_t = typename BasicJsonType::parser_callback_t;
+ using parse_event_t = typename BasicJsonType::parse_event_t;
+
+ json_sax_dom_callback_parser(BasicJsonType& r,
+ const parser_callback_t cb,
+ const bool allow_exceptions_ = true)
+ : root(r), callback(cb), allow_exceptions(allow_exceptions_)
+ {
+ keep_stack.push_back(true);
+ }
+
+ bool null()
+ {
+ handle_value(nullptr);
+ return true;
+ }
+
+ bool boolean(bool val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_integer(number_integer_t val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_unsigned(number_unsigned_t val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_float(number_float_t val, const string_t&)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool string(string_t& val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool start_object(std::size_t len)
+ {
+ // check callback for object start
+ const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
+ keep_stack.push_back(keep);
+
+ auto val = handle_value(BasicJsonType::value_t::object, true);
+ ref_stack.push_back(val.second);
+
+ // check object limit
+ if (ref_stack.back())
+ {
+ if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+ {
+ JSON_THROW(out_of_range::create(408,
+ "excessive object size: " + std::to_string(len)));
+ }
+ }
+
+ return true;
+ }
+
+ bool key(string_t& val)
+ {
+ BasicJsonType k = BasicJsonType(val);
+
+ // check callback for key
+ const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
+ key_keep_stack.push_back(keep);
+
+ // add discarded value at given key and store the reference for later
+ if (keep and ref_stack.back())
+ {
+ object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded);
+ }
+
+ return true;
+ }
+
+ bool end_object()
+ {
+ if (ref_stack.back())
+ {
+ if (not callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
+ {
+ // discard object
+ *ref_stack.back() = discarded;
+ }
+ }
+
+ assert(not ref_stack.empty());
+ assert(not keep_stack.empty());
+ ref_stack.pop_back();
+ keep_stack.pop_back();
+
+ if (not ref_stack.empty() and ref_stack.back())
+ {
+ // remove discarded value
+ if (ref_stack.back()->is_object())
+ {
+ for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)
+ {
+ if (it->is_discarded())
+ {
+ ref_stack.back()->erase(it);
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool start_array(std::size_t len)
+ {
+ const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
+ keep_stack.push_back(keep);
+
+ auto val = handle_value(BasicJsonType::value_t::array, true);
+ ref_stack.push_back(val.second);
+
+ // check array limit
+ if (ref_stack.back())
+ {
+ if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+ {
+ JSON_THROW(out_of_range::create(408,
+ "excessive array size: " + std::to_string(len)));
+ }
+ }
+
+ return true;
+ }
+
+ bool end_array()
+ {
+ bool keep = true;
+
+ if (ref_stack.back())
+ {
+ keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
+ if (not keep)
+ {
+ // discard array
+ *ref_stack.back() = discarded;
+ }
+ }
+
+ assert(not ref_stack.empty());
+ assert(not keep_stack.empty());
+ ref_stack.pop_back();
+ keep_stack.pop_back();
+
+ // remove discarded value
+ if (not keep and not ref_stack.empty())
+ {
+ if (ref_stack.back()->is_array())
+ {
+ ref_stack.back()->m_value.array->pop_back();
+ }
+ }
+
+ return true;
+ }
+
+ bool parse_error(std::size_t, const std::string&,
+ const detail::exception& ex)
+ {
+ errored = true;
+ if (allow_exceptions)
+ {
+ // determine the proper exception type from the id
+ switch ((ex.id / 100) % 100)
+ {
+ case 1:
+ JSON_THROW(*reinterpret_cast<const detail::parse_error*>(&ex));
+ case 4:
+ JSON_THROW(*reinterpret_cast<const detail::out_of_range*>(&ex));
+ // LCOV_EXCL_START
+ case 2:
+ JSON_THROW(*reinterpret_cast<const detail::invalid_iterator*>(&ex));
+ case 3:
+ JSON_THROW(*reinterpret_cast<const detail::type_error*>(&ex));
+ case 5:
+ JSON_THROW(*reinterpret_cast<const detail::other_error*>(&ex));
+ default:
+ assert(false);
+ // LCOV_EXCL_STOP
+ }
+ }
+ return false;
+ }
+
+ constexpr bool is_errored() const
+ {
+ return errored;
+ }
+
+ private:
+ /*!
+ @param[in] v value to add to the JSON value we build during parsing
+ @param[in] skip_callback whether we should skip calling the callback
+ function; this is required after start_array() and
+ start_object() SAX events, because otherwise we would call the
+ callback function with an empty array or object, respectively.
+
+ @invariant If the ref stack is empty, then the passed value will be the new
+ root.
+ @invariant If the ref stack contains a value, then it is an array or an
+ object to which we can add elements
+
+ @return pair of boolean (whether value should be kept) and pointer (to the
+ passed value in the ref_stack hierarchy; nullptr if not kept)
+ */
+ template<typename Value>
+ std::pair<bool, BasicJsonType*> handle_value(Value&& v, const bool skip_callback = false)
+ {
+ assert(not keep_stack.empty());
+
+ // do not handle this value if we know it would be added to a discarded
+ // container
+ if (not keep_stack.back())
+ {
+ return {false, nullptr};
+ }
+
+ // create value
+ auto value = BasicJsonType(std::forward<Value>(v));
+
+ // check callback
+ const bool keep = skip_callback or callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value);
+
+ // do not handle this value if we just learnt it shall be discarded
+ if (not keep)
+ {
+ return {false, nullptr};
+ }
+
+ if (ref_stack.empty())
+ {
+ root = std::move(value);
+ return {true, &root};
+ }
+ else
+ {
+ // skip this value if we already decided to skip the parent
+ // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360)
+ if (not ref_stack.back())
+ {
+ return {false, nullptr};
+ }
+
+ assert(ref_stack.back()->is_array() or ref_stack.back()->is_object());
+ if (ref_stack.back()->is_array())
+ {
+ ref_stack.back()->m_value.array->push_back(std::move(value));
+ return {true, &(ref_stack.back()->m_value.array->back())};
+ }
+ else
+ {
+ // check if we should store an element for the current key
+ assert(not key_keep_stack.empty());
+ const bool store_element = key_keep_stack.back();
+ key_keep_stack.pop_back();
+
+ if (not store_element)
+ {
+ return {false, nullptr};
+ }
+
+ assert(object_element);
+ *object_element = std::move(value);
+ return {true, object_element};
+ }
+ }
+ }
+
+ /// the parsed JSON value
+ BasicJsonType& root;
+ /// stack to model hierarchy of values
+ std::vector<BasicJsonType*> ref_stack;
+ /// stack to manage which values to keep
+ std::vector<bool> keep_stack;
+ /// stack to manage which object keys to keep
+ std::vector<bool> key_keep_stack;
+ /// helper to hold the reference for the next object element
+ BasicJsonType* object_element = nullptr;
+ /// whether a syntax error occurred
+ bool errored = false;
+ /// callback function
+ const parser_callback_t callback = nullptr;
+ /// whether to throw exceptions in case of errors
+ const bool allow_exceptions = true;
+ /// a discarded value for the callback
+ BasicJsonType discarded = BasicJsonType::value_t::discarded;
+};
+
+template<typename BasicJsonType>
+class json_sax_acceptor
+{
+ public:
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+
+ bool null()
+ {
+ return true;
+ }
+
+ bool boolean(bool)
+ {
+ return true;
+ }
+
+ bool number_integer(number_integer_t)
+ {
+ return true;
+ }
+
+ bool number_unsigned(number_unsigned_t)
+ {
+ return true;
+ }
+
+ bool number_float(number_float_t, const string_t&)
+ {
+ return true;
+ }
+
+ bool string(string_t&)
+ {
+ return true;
+ }
+
+ bool start_object(std::size_t = std::size_t(-1))
+ {
+ return true;
+ }
+
+ bool key(string_t&)
+ {
+ return true;
+ }
+
+ bool end_object()
+ {
+ return true;
+ }
+
+ bool start_array(std::size_t = std::size_t(-1))
+ {
+ return true;
+ }
+
+ bool end_array()
+ {
+ return true;
+ }
+
+ bool parse_error(std::size_t, const std::string&, const detail::exception&)
+ {
+ return false;
+ }
+};
+}
+
+}
+
// #include <nlohmann/detail/input/lexer.hpp>
// #include <nlohmann/detail/value_t.hpp>
@@ -3155,6 +4505,7 @@
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
using lexer_t = lexer<BasicJsonType>;
using token_type = typename lexer_t::token_type;
@@ -3179,11 +4530,14 @@
std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>;
/// a parser reading from an input adapter
- explicit parser(detail::input_adapter_t adapter,
+ 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(std::move(adapter)), allow_exceptions(allow_exceptions_)
+ {
+ // read first token
+ get_token();
+ }
/*!
@brief public parser interface
@@ -3197,31 +4551,54 @@
*/
void parse(const bool strict, BasicJsonType& result)
{
- // read first token
- get_token();
-
- parse_internal(true, result);
- result.assert_invariant();
-
- // in strict mode, input must be completely read
- if (strict)
+ if (callback)
{
- get_token();
- expect(token_type::end_of_input);
+ json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback, allow_exceptions);
+ sax_parse_internal(&sdp);
+ result.assert_invariant();
+
+ // in strict mode, input must be completely read
+ if (strict and (get_token() != token_type::end_of_input))
+ {
+ sdp.parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input)));
+ }
+
+ // in case of an error, return discarded value
+ if (sdp.is_errored())
+ {
+ result = value_t::discarded;
+ return;
+ }
+
+ // set top-level value to null if it was discarded by the callback
+ // function
+ if (result.is_discarded())
+ {
+ result = nullptr;
+ }
}
-
- // in case of an error, return discarded value
- if (errored)
+ else
{
- result = value_t::discarded;
- return;
- }
+ json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions);
+ sax_parse_internal(&sdp);
+ result.assert_invariant();
- // set top-level value to null if it was discarded by the callback
- // function
- if (result.is_discarded())
- {
- result = nullptr;
+ // in strict mode, input must be completely read
+ if (strict and (get_token() != token_type::end_of_input))
+ {
+ sdp.parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input)));
+ }
+
+ // in case of an error, return discarded value
+ if (sdp.is_errored())
+ {
+ result = value_t::discarded;
+ return;
+ }
}
}
@@ -3233,413 +4610,311 @@
*/
bool accept(const bool strict = true)
{
- // read first token
- get_token();
+ json_sax_acceptor<BasicJsonType> sax_acceptor;
+ return sax_parse(&sax_acceptor, strict);
+ }
- if (not accept_internal())
+ template <typename SAX>
+ bool sax_parse(SAX* sax, const bool strict = true)
+ {
+ (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
+ const bool result = sax_parse_internal(sax);
+
+ // strict mode: next byte must be EOF
+ if (result and strict and (get_token() != token_type::end_of_input))
{
- return false;
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input)));
}
- // strict => last token must be EOF
- return not strict or (get_token() == token_type::end_of_input);
+ return result;
}
private:
- /*!
- @brief the actual parser
- @throw parse_error.101 in case of an unexpected token
- @throw parse_error.102 if to_unicode fails or surrogate error
- @throw parse_error.103 if to_unicode fails
- */
- void parse_internal(bool keep, BasicJsonType& result)
+ template <typename SAX>
+ bool sax_parse_internal(SAX* sax)
{
- // never parse after a parse error was detected
- assert(not errored);
+ // stack to remember the hieararchy of structured values we are parsing
+ // true = array; false = object
+ std::vector<bool> states;
+ // value to avoid a goto (see comment where set to true)
+ bool skip_to_state_evaluation = false;
- // start with a discarded value
- if (not result.is_discarded())
+ while (true)
{
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
-
- switch (last_token)
- {
- case token_type::begin_object:
+ if (not skip_to_state_evaluation)
{
- if (keep)
+ // invariant: get_token() was called before each iteration
+ switch (last_token)
{
- if (callback)
+ case token_type::begin_object:
{
- 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
- get_token();
-
- // closing } -> we are done
- if (last_token == token_type::end_object)
- {
- if (keep and callback and not callback(--depth, parse_event_t::object_end, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- break;
- }
-
- // parse values
- std::string key;
- BasicJsonType value;
- while (true)
- {
- // store key
- if (not expect(token_type::value_string))
- {
- return;
- }
- key = m_lexer.move_string();
-
- bool keep_tag = false;
- if (keep)
- {
- if (callback)
+ if (JSON_UNLIKELY(not sax->start_object(std::size_t(-1))))
{
- BasicJsonType k(key);
- keep_tag = callback(depth, parse_event_t::key, k);
+ return false;
+ }
+
+ // closing } -> we are done
+ if (get_token() == token_type::end_object)
+ {
+ if (JSON_UNLIKELY(not sax->end_object()))
+ {
+ return false;
+ }
+ break;
+ }
+
+ // parse key
+ if (JSON_UNLIKELY(last_token != token_type::value_string))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string)));
}
else
{
- keep_tag = true;
+ if (JSON_UNLIKELY(not sax->key(m_lexer.get_string())))
+ {
+ return false;
+ }
+ }
+
+ // parse separator (:)
+ if (JSON_UNLIKELY(get_token() != token_type::name_separator))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator)));
+ }
+
+ // remember we are now inside an object
+ states.push_back(false);
+
+ // parse values
+ get_token();
+ continue;
+ }
+
+ case token_type::begin_array:
+ {
+ if (JSON_UNLIKELY(not sax->start_array(std::size_t(-1))))
+ {
+ return false;
+ }
+
+ // closing ] -> we are done
+ if (get_token() == token_type::end_array)
+ {
+ if (JSON_UNLIKELY(not sax->end_array()))
+ {
+ return false;
+ }
+ break;
+ }
+
+ // remember we are now inside an array
+ states.push_back(true);
+
+ // parse values (no need to call get_token)
+ continue;
+ }
+
+ case token_type::value_float:
+ {
+ const auto res = m_lexer.get_number_float();
+
+ if (JSON_UNLIKELY(not std::isfinite(res)))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'"));
+ }
+ else
+ {
+ if (JSON_UNLIKELY(not sax->number_float(res, m_lexer.get_string())))
+ {
+ return false;
+ }
+ break;
}
}
- // parse separator (:)
- get_token();
- if (not expect(token_type::name_separator))
+ case token_type::literal_false:
{
- return;
+ if (JSON_UNLIKELY(not sax->boolean(false)))
+ {
+ return false;
+ }
+ break;
}
- // parse and add value
- get_token();
- value.m_value.destroy(value.m_type);
- value.m_type = value_t::discarded;
- parse_internal(keep, value);
-
- if (JSON_UNLIKELY(errored))
+ case token_type::literal_null:
{
- return;
+ if (JSON_UNLIKELY(not sax->null()))
+ {
+ return false;
+ }
+ break;
}
- if (keep and keep_tag and not value.is_discarded())
+ case token_type::literal_true:
{
- result.m_value.object->emplace(std::move(key), std::move(value));
+ if (JSON_UNLIKELY(not sax->boolean(true)))
+ {
+ return false;
+ }
+ break;
}
- // comma -> next value
- get_token();
- if (last_token == token_type::value_separator)
+ case token_type::value_integer:
{
- get_token();
- continue;
+ if (JSON_UNLIKELY(not sax->number_integer(m_lexer.get_number_integer())))
+ {
+ return false;
+ }
+ break;
}
- // closing }
- if (not expect(token_type::end_object))
+ case token_type::value_string:
{
- return;
- }
- break;
- }
-
- if (keep and callback and not callback(--depth, parse_event_t::object_end, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- break;
- }
-
- case token_type::begin_array:
- {
- if (keep)
- {
- if (callback)
- {
- keep = callback(depth++, parse_event_t::array_start, result);
+ if (JSON_UNLIKELY(not sax->string(m_lexer.get_string())))
+ {
+ return false;
+ }
+ break;
}
- if (not callback or keep)
+ case token_type::value_unsigned:
{
- // explicitly set result to array to cope with []
- result.m_type = value_t::array;
- result.m_value = value_t::array;
+ if (JSON_UNLIKELY(not sax->number_unsigned(m_lexer.get_number_unsigned())))
+ {
+ return false;
+ }
+ break;
+ }
+
+ case token_type::parse_error:
+ {
+ // using "uninitialized" to avoid "expected" message
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized)));
+ }
+
+ default: // the last token was unexpected
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value)));
}
}
-
- // read next token
- get_token();
-
- // closing ] -> we are done
- if (last_token == token_type::end_array)
- {
- if (callback and not callback(--depth, parse_event_t::array_end, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- break;
- }
-
- // parse values
- BasicJsonType value;
- while (true)
- {
- // parse value
- value.m_value.destroy(value.m_type);
- value.m_type = value_t::discarded;
- parse_internal(keep, value);
-
- if (JSON_UNLIKELY(errored))
- {
- return;
- }
-
- if (keep and not value.is_discarded())
- {
- result.m_value.array->push_back(std::move(value));
- }
-
- // comma -> next value
- get_token();
- if (last_token == token_type::value_separator)
- {
- get_token();
- continue;
- }
-
- // closing ]
- if (not expect(token_type::end_array))
- {
- return;
- }
- break;
- }
-
- if (keep and callback and not callback(--depth, parse_event_t::array_end, result))
- {
- result.m_value.destroy(result.m_type);
- result.m_type = value_t::discarded;
- }
- break;
}
-
- case token_type::literal_null:
+ else
{
- result.m_type = value_t::null;
- break;
+ skip_to_state_evaluation = false;
}
- case token_type::value_string:
+ // we reached this line after we successfully parsed a value
+ if (states.empty())
{
- result.m_type = value_t::string;
- result.m_value = m_lexer.move_string();
- break;
- }
-
- case token_type::literal_true:
- {
- result.m_type = value_t::boolean;
- result.m_value = true;
- break;
- }
-
- case token_type::literal_false:
- {
- result.m_type = value_t::boolean;
- result.m_value = false;
- break;
- }
-
- case token_type::value_unsigned:
- {
- result.m_type = value_t::number_unsigned;
- result.m_value = m_lexer.get_number_unsigned();
- break;
- }
-
- case token_type::value_integer:
- {
- result.m_type = value_t::number_integer;
- result.m_value = m_lexer.get_number_integer();
- break;
- }
-
- case token_type::value_float:
- {
- result.m_type = value_t::number_float;
- result.m_value = m_lexer.get_number_float();
-
- // throw in case of infinity or NAN
- if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float)))
- {
- if (allow_exceptions)
- {
- JSON_THROW(out_of_range::create(406, "number overflow parsing '" +
- m_lexer.get_token_string() + "'"));
- }
- expect(token_type::uninitialized);
- }
- break;
- }
-
- case token_type::parse_error:
- {
- // using "uninitialized" to avoid "expected" message
- if (not expect(token_type::uninitialized))
- {
- return;
- }
- break; // LCOV_EXCL_LINE
- }
-
- default:
- {
- // the last token was unexpected; we expected a value
- if (not expect(token_type::literal_or_value))
- {
- return;
- }
- break; // LCOV_EXCL_LINE
- }
- }
-
- if (keep and callback and not callback(depth, parse_event_t::value, result))
- {
- result.m_type = value_t::discarded;
- }
- }
-
- /*!
- @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.
- 2. When this function returns, the last token is processed.
- That is, the last read character was already considered.
-
- This invariant makes sure that no token needs to be "unput".
- */
- bool accept_internal()
- {
- switch (last_token)
- {
- case token_type::begin_object:
- {
- // read next token
- get_token();
-
- // closing } -> we are done
- if (last_token == token_type::end_object)
- {
- return true;
- }
-
- // parse values
- while (true)
- {
- // parse key
- if (last_token != token_type::value_string)
- {
- return false;
- }
-
- // parse separator (:)
- get_token();
- if (last_token != token_type::name_separator)
- {
- return false;
- }
-
- // parse value
- get_token();
- if (not accept_internal())
- {
- return false;
- }
-
- // comma -> next value
- get_token();
- if (last_token == token_type::value_separator)
- {
- get_token();
- continue;
- }
-
- // closing }
- return (last_token == token_type::end_object);
- }
- }
-
- case token_type::begin_array:
- {
- // read next token
- get_token();
-
- // closing ] -> we are done
- if (last_token == token_type::end_array)
- {
- return true;
- }
-
- // parse values
- while (true)
- {
- // parse value
- if (not accept_internal())
- {
- return false;
- }
-
- // comma -> next value
- get_token();
- if (last_token == token_type::value_separator)
- {
- get_token();
- continue;
- }
-
- // closing ]
- return (last_token == token_type::end_array);
- }
- }
-
- case token_type::value_float:
- {
- // reject infinity or NAN
- return std::isfinite(m_lexer.get_number_float());
- }
-
- case token_type::literal_false:
- case token_type::literal_null:
- case token_type::literal_true:
- case token_type::value_integer:
- case token_type::value_string:
- case token_type::value_unsigned:
+ // empty stack: we reached the end of the hieararchy: done
return true;
+ }
+ else
+ {
+ if (states.back()) // array
+ {
+ // comma -> next value
+ if (get_token() == token_type::value_separator)
+ {
+ // parse a new value
+ get_token();
+ continue;
+ }
- default: // the last token was unexpected
- return false;
+ // closing ]
+ if (JSON_LIKELY(last_token == token_type::end_array))
+ {
+ if (JSON_UNLIKELY(not sax->end_array()))
+ {
+ return false;
+ }
+
+ // We are done with this array. Before we can parse a
+ // new value, we need to evaluate the new state first.
+ // By setting skip_to_state_evaluation to false, we
+ // are effectively jumping to the beginning of this if.
+ assert(not states.empty());
+ states.pop_back();
+ skip_to_state_evaluation = true;
+ continue;
+ }
+ else
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array)));
+ }
+ }
+ else // object
+ {
+ // comma -> next value
+ if (get_token() == token_type::value_separator)
+ {
+ // parse key
+ if (JSON_UNLIKELY(get_token() != token_type::value_string))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string)));
+ }
+ else
+ {
+ if (JSON_UNLIKELY(not sax->key(m_lexer.get_string())))
+ {
+ return false;
+ }
+ }
+
+ // parse separator (:)
+ if (JSON_UNLIKELY(get_token() != token_type::name_separator))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator)));
+ }
+
+ // parse values
+ get_token();
+ continue;
+ }
+
+ // closing }
+ if (JSON_LIKELY(last_token == token_type::end_object))
+ {
+ if (JSON_UNLIKELY(not sax->end_object()))
+ {
+ return false;
+ }
+
+ // We are done with this object. Before we can parse a
+ // new value, we need to evaluate the new state first.
+ // By setting skip_to_state_evaluation to false, we
+ // are effectively jumping to the beginning of this if.
+ assert(not states.empty());
+ states.pop_back();
+ skip_to_state_evaluation = true;
+ continue;
+ }
+ else
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object)));
+ }
+ }
+ }
}
}
@@ -3649,29 +4924,7 @@
return (last_token = m_lexer.scan());
}
- /*!
- @throw parse_error.101 if expected token did not occur
- */
- bool expect(token_type t)
- {
- if (JSON_UNLIKELY(t != last_token))
- {
- errored = true;
- expected = t;
- if (allow_exceptions)
- {
- throw_exception();
- }
- else
- {
- return false;
- }
- }
-
- return true;
- }
-
- [[noreturn]] void throw_exception() const
+ std::string exception_message(const token_type expected)
{
std::string error_msg = "syntax error - ";
if (last_token == token_type::parse_error)
@@ -3689,22 +4942,16 @@
error_msg += "; expected " + std::string(lexer_t::token_type_name(expected));
}
- JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg));
+ return error_msg;
}
private:
- /// current level of recursion
- int depth = 0;
/// callback function
const parser_callback_t callback = nullptr;
/// the type of the last read token
token_type last_token = token_type::uninitialized;
/// the lexer
lexer_t m_lexer;
- /// whether a syntax error occurred
- bool errored = false;
- /// possible reason for the syntax error
- token_type expected = token_type::uninitialized;
/// whether to throw exceptions in case of errors
const bool allow_exceptions = true;
};
@@ -3801,7 +5048,7 @@
primitive_iterator_t const operator++(int) noexcept
{
auto result = *this;
- m_it++;
+ ++m_it;
return result;
}
@@ -3814,7 +5061,7 @@
primitive_iterator_t const operator--(int) noexcept
{
auto result = *this;
- m_it--;
+ --m_it;
return result;
}
@@ -3876,7 +5123,7 @@
// #include <nlohmann/detail/macro_scope.hpp>
-// #include <nlohmann/detail/meta.hpp>
+// #include <nlohmann/detail/meta/cpp_future.hpp>
// #include <nlohmann/detail/value_t.hpp>
@@ -3901,7 +5148,7 @@
@requirement The class satisfies the following concept requirements:
-
-[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator):
+[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
The iterator that can be moved can be moved in both directions (i.e.
incremented and decremented).
@@ -4453,7 +5700,7 @@
@brief return the key of an object iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
- typename object_t::key_type key() const
+ const typename object_t::key_type& key() const
{
assert(m_object != nullptr);
@@ -4485,105 +5732,6 @@
// #include <nlohmann/detail/iterators/iteration_proxy.hpp>
-
-#include <cstddef> // size_t
-#include <string> // string, to_string
-
-// #include <nlohmann/detail/value_t.hpp>
-
-
-namespace nlohmann
-{
-namespace detail
-{
-/// proxy class for the items() function
-template<typename IteratorType> class iteration_proxy
-{
- private:
- /// helper class for iteration
- class iteration_proxy_internal
- {
- private:
- /// the iterator
- IteratorType anchor;
- /// an index for arrays (used to create key names)
- std::size_t array_index = 0;
-
- public:
- explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {}
-
- /// dereference operator (needed for range-based for)
- iteration_proxy_internal& operator*()
- {
- return *this;
- }
-
- /// increment operator (needed for range-based for)
- iteration_proxy_internal& operator++()
- {
- ++anchor;
- ++array_index;
-
- return *this;
- }
-
- /// inequality operator (needed for range-based for)
- bool operator!=(const iteration_proxy_internal& o) const noexcept
- {
- return anchor != o.anchor;
- }
-
- /// return key of the iterator
- std::string key() const
- {
- assert(anchor.m_object != nullptr);
-
- switch (anchor.m_object->type())
- {
- // use integer array index as key
- case value_t::array:
- return std::to_string(array_index);
-
- // use key from the object
- case value_t::object:
- return anchor.key();
-
- // use an empty key for all primitive types
- default:
- return "";
- }
- }
-
- /// return value of the iterator
- typename IteratorType::reference value() const
- {
- return anchor.value();
- }
- };
-
- /// the container to iterate
- typename IteratorType::reference container;
-
- public:
- /// construct iteration proxy from a container
- explicit iteration_proxy(typename IteratorType::reference cont) noexcept
- : container(cont) {}
-
- /// return iterator begin (needed for range-based for)
- iteration_proxy_internal begin() noexcept
- {
- return iteration_proxy_internal(container.begin());
- }
-
- /// return iterator end (needed for range-based for)
- iteration_proxy_internal end() noexcept
- {
- return iteration_proxy_internal(container.end());
- }
-};
-}
-}
-
// #include <nlohmann/detail/iterators/json_reverse_iterator.hpp>
@@ -4608,10 +5756,10 @@
@requirement The class satisfies the following concept requirements:
-
-[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator):
+[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/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):
+- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator):
It is possible to write to the pointed-to element (only if @a Base is
@ref iterator).
@@ -4628,11 +5776,11 @@
using reference = typename Base::reference;
/// create reverse iterator from iterator
- json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept
+ explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept
: base_iterator(it) {}
/// create reverse iterator from base class
- json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {}
+ explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {}
/// post-increment (it++)
json_reverse_iterator const operator++(int)
@@ -4776,11 +5924,11 @@
};
/// output adapter for basic_string
-template<typename CharType>
+template<typename CharType, typename StringType = std::basic_string<CharType>>
class output_string_adapter : public output_adapter_protocol<CharType>
{
public:
- explicit output_string_adapter(std::basic_string<CharType>& s) : str(s) {}
+ explicit output_string_adapter(StringType& s) : str(s) {}
void write_character(CharType c) override
{
@@ -4793,10 +5941,10 @@
}
private:
- std::basic_string<CharType>& str;
+ StringType& str;
};
-template<typename CharType>
+template<typename CharType, typename StringType = std::basic_string<CharType>>
class output_adapter
{
public:
@@ -4806,8 +5954,8 @@
output_adapter(std::basic_ostream<CharType>& s)
: oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}
- output_adapter(std::basic_string<CharType>& s)
- : oa(std::make_shared<output_string_adapter<CharType>>(s)) {}
+ output_adapter(StringType& s)
+ : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}
operator output_adapter_t<CharType>()
{
@@ -4829,21 +5977,23 @@
#include <cmath> // ldexp
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
+#include <cstdio> // snprintf
#include <cstring> // memcpy
-#include <iomanip> // setw, setfill
-#include <ios> // hex
#include <iterator> // back_inserter
#include <limits> // numeric_limits
-#include <sstream> // stringstream
#include <string> // char_traits, string
#include <utility> // make_pair, move
// #include <nlohmann/detail/input/input_adapters.hpp>
+// #include <nlohmann/detail/input/json_sax.hpp>
+
// #include <nlohmann/detail/exceptions.hpp>
// #include <nlohmann/detail/macro_scope.hpp>
+// #include <nlohmann/detail/meta/is_sax.hpp>
+
// #include <nlohmann/detail/value_t.hpp>
@@ -4856,14 +6006,16 @@
///////////////////
/*!
-@brief deserialization of CBOR and MessagePack values
+@brief deserialization of CBOR, MessagePack, and UBJSON values
*/
-template<typename BasicJsonType>
+template<typename BasicJsonType, typename SAX = json_sax_dom_parser<BasicJsonType>>
class binary_reader
{
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
using string_t = typename BasicJsonType::string_t;
+ using json_sax_t = SAX;
public:
/*!
@@ -4873,70 +6025,63 @@
*/
explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter))
{
+ (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
assert(ia);
}
/*!
- @brief create a JSON value from CBOR input
-
+ @param[in] format the binary format to parse
+ @param[in] sax_ a SAX event processor
@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 or the end of file was
- not reached when @a strict was set to true
- @throw parse_error.112 if unsupported byte was read
+ @return
*/
- BasicJsonType parse_cbor(const bool strict)
+ bool sax_parse(const input_format_t format,
+ json_sax_t* sax_,
+ const bool strict = true)
{
- const auto res = parse_cbor_internal();
- if (strict)
+ sax = sax_;
+ bool result = false;
+
+ switch (format)
{
- get();
- expect_eof();
+ case input_format_t::cbor:
+ result = parse_cbor_internal();
+ break;
+
+ case input_format_t::msgpack:
+ result = parse_msgpack_internal();
+ break;
+
+ case input_format_t::ubjson:
+ result = parse_ubjson_internal();
+ break;
+
+ // LCOV_EXCL_START
+ default:
+ assert(false);
+ // LCOV_EXCL_STOP
}
- 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)
+ // strict mode: next byte must be EOF
+ if (result and strict)
{
- get();
- expect_eof();
+ if (format == input_format_t::ubjson)
+ {
+ get_ignore_noop();
+ }
+ else
+ {
+ get();
+ }
+
+ if (JSON_UNLIKELY(current != std::char_traits<char>::eof()))
+ {
+ return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, "expected end of input"));
+ }
}
- 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;
+ return result;
}
/*!
@@ -4956,14 +6101,16 @@
@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 whether a valid CBOR value was passed to the SAX parser
*/
- BasicJsonType parse_cbor_internal(const bool get_char = true)
+ bool parse_cbor_internal(const bool get_char = true)
{
switch (get_char ? get() : current)
{
// EOF
case std::char_traits<char>::eof():
- JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
+ return unexpect_eof();
// Integer 0x00..0x17 (0..23)
case 0x00:
@@ -4990,19 +6137,31 @@
case 0x15:
case 0x16:
case 0x17:
- return static_cast<number_unsigned_t>(current);
+ return sax->number_unsigned(static_cast<number_unsigned_t>(current));
case 0x18: // Unsigned integer (one-byte uint8_t follows)
- return get_number<uint8_t>();
+ {
+ uint8_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
case 0x19: // Unsigned integer (two-byte uint16_t follows)
- return get_number<uint16_t>();
+ {
+ uint16_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
case 0x1A: // Unsigned integer (four-byte uint32_t follows)
- return get_number<uint32_t>();
+ {
+ uint32_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
case 0x1B: // Unsigned integer (eight-byte uint64_t follows)
- return get_number<uint64_t>();
+ {
+ uint64_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
// Negative integer -1-0x00..-1-0x17 (-1..-24)
case 0x20:
@@ -5029,27 +6188,31 @@
case 0x35:
case 0x36:
case 0x37:
- return static_cast<int8_t>(0x20 - 1 - current);
+ return sax->number_integer(static_cast<int8_t>(0x20 - 1 - current));
case 0x38: // Negative integer (one-byte uint8_t follows)
{
- return static_cast<number_integer_t>(-1) - get_number<uint8_t>();
+ uint8_t number;
+ return get_number(number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
}
case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
{
- return static_cast<number_integer_t>(-1) - get_number<uint16_t>();
+ uint16_t number;
+ return get_number(number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
}
case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)
{
- return static_cast<number_integer_t>(-1) - get_number<uint32_t>();
+ uint32_t number;
+ return get_number(number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
}
case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
{
- return static_cast<number_integer_t>(-1) -
- static_cast<number_integer_t>(get_number<uint64_t>());
+ uint64_t number;
+ return get_number(number) and sax->number_integer(static_cast<number_integer_t>(-1)
+ - static_cast<number_integer_t>(number));
}
// UTF-8 string (0x00..0x17 bytes follow)
@@ -5083,7 +6246,8 @@
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
case 0x7F: // UTF-8 string (indefinite length)
{
- return get_cbor_string();
+ string_t s;
+ return get_cbor_string(s) and sax->string(s);
}
// array (0x00..0x17 data items follow)
@@ -5111,39 +6275,34 @@
case 0x95:
case 0x96:
case 0x97:
- {
- return get_cbor_array(current & 0x1F);
- }
+ return get_cbor_array(static_cast<std::size_t>(current & 0x1F));
case 0x98: // array (one-byte uint8_t for n follows)
{
- return get_cbor_array(get_number<uint8_t>());
+ uint8_t len;
+ return get_number(len) and get_cbor_array(static_cast<std::size_t>(len));
}
case 0x99: // array (two-byte uint16_t for n follow)
{
- return get_cbor_array(get_number<uint16_t>());
+ uint16_t len;
+ return get_number(len) and get_cbor_array(static_cast<std::size_t>(len));
}
case 0x9A: // array (four-byte uint32_t for n follow)
{
- return get_cbor_array(get_number<uint32_t>());
+ uint32_t len;
+ return get_number(len) and get_cbor_array(static_cast<std::size_t>(len));
}
case 0x9B: // array (eight-byte uint64_t for n follow)
{
- return get_cbor_array(get_number<uint64_t>());
+ uint64_t len;
+ return get_number(len) and get_cbor_array(static_cast<std::size_t>(len));
}
case 0x9F: // array (indefinite length)
- {
- BasicJsonType result = value_t::array;
- while (get() != 0xFF)
- {
- result.push_back(parse_cbor_internal(false));
- }
- return result;
- }
+ return get_cbor_array(std::size_t(-1));
// map (0x00..0x17 pairs of data items follow)
case 0xA0:
@@ -5170,62 +6329,56 @@
case 0xB5:
case 0xB6:
case 0xB7:
- {
- return get_cbor_object(current & 0x1F);
- }
+ return get_cbor_object(static_cast<std::size_t>(current & 0x1F));
case 0xB8: // map (one-byte uint8_t for n follows)
{
- return get_cbor_object(get_number<uint8_t>());
+ uint8_t len;
+ return get_number(len) and get_cbor_object(static_cast<std::size_t>(len));
}
case 0xB9: // map (two-byte uint16_t for n follow)
{
- return get_cbor_object(get_number<uint16_t>());
+ uint16_t len;
+ return get_number(len) and get_cbor_object(static_cast<std::size_t>(len));
}
case 0xBA: // map (four-byte uint32_t for n follow)
{
- return get_cbor_object(get_number<uint32_t>());
+ uint32_t len;
+ return get_number(len) and get_cbor_object(static_cast<std::size_t>(len));
}
case 0xBB: // map (eight-byte uint64_t for n follow)
{
- return get_cbor_object(get_number<uint64_t>());
+ uint64_t len;
+ return get_number(len) and get_cbor_object(static_cast<std::size_t>(len));
}
case 0xBF: // map (indefinite length)
- {
- BasicJsonType result = value_t::object;
- while (get() != 0xFF)
- {
- auto key = get_cbor_string();
- result[key] = parse_cbor_internal();
- }
- return result;
- }
+ return get_cbor_object(std::size_t(-1));
case 0xF4: // false
- {
- return false;
- }
+ return sax->boolean(false);
case 0xF5: // true
- {
- return true;
- }
+ return sax->boolean(true);
case 0xF6: // null
- {
- return value_t::null;
- }
+ return sax->null();
case 0xF9: // Half-Precision Float (two-byte IEEE 754)
{
const int byte1 = get();
- unexpect_eof();
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
const int byte2 = get();
- unexpect_eof();
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
// code from RFC 7049, Appendix D, Figure 3:
// As half-precision floating-point numbers were only added
@@ -5236,51 +6389,59 @@
// 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;
- double val;
- if (exp == 0)
+ const double val = [&half]
{
- val = std::ldexp(mant, -24);
- }
- else if (exp != 31)
- {
- val = std::ldexp(mant + 1024, exp - 25);
- }
- else
- {
- val = (mant == 0) ? std::numeric_limits<double>::infinity()
- : std::numeric_limits<double>::quiet_NaN();
- }
- return (half & 0x8000) != 0 ? -val : val;
+ const int exp = (half >> 10) & 0x1F;
+ const int mant = half & 0x3FF;
+ assert(0 <= exp and exp <= 32);
+ assert(0 <= mant and mant <= 1024);
+ switch (exp)
+ {
+ case 0:
+ return std::ldexp(mant, -24);
+ case 31:
+ return (mant == 0)
+ ? std::numeric_limits<double>::infinity()
+ : std::numeric_limits<double>::quiet_NaN();
+ default:
+ return std::ldexp(mant + 1024, exp - 25);
+ }
+ }();
+ return sax->number_float((half & 0x8000) != 0
+ ? static_cast<number_float_t>(-val)
+ : static_cast<number_float_t>(val), "");
}
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
{
- return get_number<float>();
+ float number;
+ return get_number(number) and sax->number_float(static_cast<number_float_t>(number), "");
}
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
{
- return get_number<double>();
+ double number;
+ return get_number(number) and sax->number_float(static_cast<number_float_t>(number), "");
}
default: // anything else (0xFF is handled inside the other types)
{
- 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 CBOR; last byte: 0x" + ss.str()));
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + last_token));
}
}
}
- BasicJsonType parse_msgpack_internal()
+ /*!
+ @return whether a valid MessagePack value was passed to the SAX parser
+ */
+ bool parse_msgpack_internal()
{
switch (get())
{
// EOF
case std::char_traits<char>::eof():
- JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
+ return unexpect_eof();
// positive fixint
case 0x00:
@@ -5411,7 +6572,7 @@
case 0x7D:
case 0x7E:
case 0x7F:
- return static_cast<number_unsigned_t>(current);
+ return sax->number_unsigned(static_cast<number_unsigned_t>(current));
// fixmap
case 0x80:
@@ -5430,9 +6591,7 @@
case 0x8D:
case 0x8E:
case 0x8F:
- {
- return get_msgpack_object(current & 0x0F);
- }
+ return get_msgpack_object(static_cast<std::size_t>(current & 0x0F));
// fixarray
case 0x90:
@@ -5451,9 +6610,7 @@
case 0x9D:
case 0x9E:
case 0x9F:
- {
- return get_msgpack_array(current & 0x0F);
- }
+ return get_msgpack_array(static_cast<std::size_t>(current & 0x0F));
// fixstr
case 0xA0:
@@ -5488,73 +6645,113 @@
case 0xBD:
case 0xBE:
case 0xBF:
- return get_msgpack_string();
+ {
+ string_t s;
+ return get_msgpack_string(s) and sax->string(s);
+ }
case 0xC0: // nil
- return value_t::null;
+ return sax->null();
case 0xC2: // false
- return false;
+ return sax->boolean(false);
case 0xC3: // true
- return true;
+ return sax->boolean(true);
case 0xCA: // float 32
- return get_number<float>();
+ {
+ float number;
+ return get_number(number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
case 0xCB: // float 64
- return get_number<double>();
+ {
+ double number;
+ return get_number(number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
case 0xCC: // uint 8
- return get_number<uint8_t>();
+ {
+ uint8_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
case 0xCD: // uint 16
- return get_number<uint16_t>();
+ {
+ uint16_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
case 0xCE: // uint 32
- return get_number<uint32_t>();
+ {
+ uint32_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
case 0xCF: // uint 64
- return get_number<uint64_t>();
+ {
+ uint64_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
case 0xD0: // int 8
- return get_number<int8_t>();
+ {
+ int8_t number;
+ return get_number(number) and sax->number_integer(number);
+ }
case 0xD1: // int 16
- return get_number<int16_t>();
+ {
+ int16_t number;
+ return get_number(number) and sax->number_integer(number);
+ }
case 0xD2: // int 32
- return get_number<int32_t>();
+ {
+ int32_t number;
+ return get_number(number) and sax->number_integer(number);
+ }
case 0xD3: // int 64
- return get_number<int64_t>();
+ {
+ int64_t number;
+ return get_number(number) and sax->number_integer(number);
+ }
case 0xD9: // str 8
case 0xDA: // str 16
case 0xDB: // str 32
- return get_msgpack_string();
+ {
+ string_t s;
+ return get_msgpack_string(s) and sax->string(s);
+ }
case 0xDC: // array 16
{
- return get_msgpack_array(get_number<uint16_t>());
+ uint16_t len;
+ return get_number(len) and get_msgpack_array(static_cast<std::size_t>(len));
}
case 0xDD: // array 32
{
- return get_msgpack_array(get_number<uint32_t>());
+ uint32_t len;
+ return get_number(len) and get_msgpack_array(static_cast<std::size_t>(len));
}
case 0xDE: // map 16
{
- return get_msgpack_object(get_number<uint16_t>());
+ uint16_t len;
+ return get_number(len) and get_msgpack_object(static_cast<std::size_t>(len));
}
case 0xDF: // map 32
{
- return get_msgpack_object(get_number<uint32_t>());
+ uint32_t len;
+ return get_number(len) and get_msgpack_object(static_cast<std::size_t>(len));
}
- // positive fixint
+ // negative fixint
case 0xE0:
case 0xE1:
case 0xE2:
@@ -5587,14 +6784,12 @@
case 0xFD:
case 0xFE:
case 0xFF:
- return static_cast<int8_t>(current);
+ return sax->number_integer(static_cast<int8_t>(current));
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 MessagePack; last byte: 0x" + ss.str()));
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, "error reading MessagePack; last byte: 0x" + last_token));
}
}
}
@@ -5603,8 +6798,10 @@
@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 whether a valid UBJSON value was passed to the SAX parser
*/
- BasicJsonType parse_ubjson_internal(const bool get_char = true)
+ bool parse_ubjson_internal(const bool get_char = true)
{
return get_ubjson_value(get_char ? get_ignore_noop() : current);
}
@@ -5642,23 +6839,26 @@
@brief read a number from the input
@tparam NumberType the type of the number
+ @param[out] result number of type @a NumberType
- @return number of type @a NumberType
+ @return whether conversion completed
@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.
-
- @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes
+ bytes in CBOR, MessagePack, and UBJSON are stored in network order
+ (big endian) and therefore need reordering on little endian systems.
*/
- template<typename NumberType> NumberType get_number()
+ template<typename NumberType>
+ bool get_number(NumberType& result)
{
// step 1: read input into array with system's byte order
std::array<uint8_t, sizeof(NumberType)> vec;
for (std::size_t i = 0; i < sizeof(NumberType); ++i)
{
get();
- unexpect_eof();
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
// reverse byte order prior to conversion if necessary
if (is_little_endian)
@@ -5672,35 +6872,37 @@
}
// step 2: convert array into number of type T and return
- NumberType result;
std::memcpy(&result, vec.data(), sizeof(NumberType));
- return result;
+ return true;
}
/*!
@brief create a string by reading characters from the input
- @param[in] len number of bytes to read
+ @tparam NumberType the type of the number
+ @param[in] len number of characters to read
+ @param[out] string created by reading @a len bytes
+
+ @return whether string creation completed
@note We can not reserve @a len bytes for the result, because @a len
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
*/
template<typename NumberType>
- string_t get_string(const NumberType len)
+ bool get_string(const NumberType len, string_t& result)
{
- string_t result;
- std::generate_n(std::back_inserter(result), len, [this]()
+ bool success = true;
+ std::generate_n(std::back_inserter(result), len, [this, &success]()
{
get();
- unexpect_eof();
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ success = false;
+ }
return static_cast<char>(current);
});
- return result;
+ return success;
}
/*!
@@ -5710,14 +6912,16 @@
string length and then copies this number of bytes into a string.
Additionally, CBOR's strings with indefinite lengths are supported.
- @return string
+ @param[out] result created string
- @throw parse_error.110 if input ended
- @throw parse_error.113 if an unexpected byte is read
+ @return whether string creation completed
*/
- string_t get_cbor_string()
+ bool get_cbor_string(string_t& result)
{
- unexpect_eof();
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
switch (current)
{
@@ -5747,73 +6951,137 @@
case 0x76:
case 0x77:
{
- return get_string(current & 0x1F);
+ return get_string(current & 0x1F, result);
}
case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
{
- return get_string(get_number<uint8_t>());
+ uint8_t len;
+ return get_number(len) and get_string(len, result);
}
case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
{
- return get_string(get_number<uint16_t>());
+ uint16_t len;
+ return get_number(len) and get_string(len, result);
}
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
{
- return get_string(get_number<uint32_t>());
+ uint32_t len;
+ return get_number(len) and get_string(len, result);
}
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
{
- return get_string(get_number<uint64_t>());
+ uint64_t len;
+ return get_number(len) and get_string(len, result);
}
case 0x7F: // UTF-8 string (indefinite length)
{
- string_t result;
while (get() != 0xFF)
{
- result.append(get_cbor_string());
+ string_t chunk;
+ if (not get_cbor_string(chunk))
+ {
+ return false;
+ }
+ result.append(chunk);
}
- return result;
+ return true;
}
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 CBOR string; last byte: 0x" + ss.str()));
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + last_token));
}
}
}
- template<typename NumberType>
- BasicJsonType get_cbor_array(const NumberType len)
+ /*!
+ @param[in] len the length of the array or std::size_t(-1) for an
+ array of indefinite size
+ @return whether array creation completed
+ */
+ bool get_cbor_array(const std::size_t len)
{
- BasicJsonType result = value_t::array;
- std::generate_n(std::back_inserter(*result.m_value.array), len, [this]()
+ if (JSON_UNLIKELY(not sax->start_array(len)))
{
- return parse_cbor_internal();
- });
- return result;
+ return false;
+ }
+
+ if (len != std::size_t(-1))
+ for (std::size_t i = 0; i < len; ++i)
+ {
+ if (JSON_UNLIKELY(not parse_cbor_internal()))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ while (get() != 0xFF)
+ {
+ if (JSON_UNLIKELY(not parse_cbor_internal(false)))
+ {
+ return false;
+ }
+ }
+ }
+
+ return sax->end_array();
}
- template<typename NumberType>
- BasicJsonType get_cbor_object(const NumberType len)
+ /*!
+ @param[in] len the length of the object or std::size_t(-1) for an
+ object of indefinite size
+ @return whether object creation completed
+ */
+ bool get_cbor_object(const std::size_t len)
{
- BasicJsonType result = value_t::object;
- std::generate_n(std::inserter(*result.m_value.object,
- result.m_value.object->end()),
- len, [this]()
+ if (not JSON_UNLIKELY(sax->start_object(len)))
{
- get();
- auto key = get_cbor_string();
- auto val = parse_cbor_internal();
- return std::make_pair(std::move(key), std::move(val));
- });
- return result;
+ return false;
+ }
+
+ string_t key;
+ if (len != std::size_t(-1))
+ {
+ for (std::size_t i = 0; i < len; ++i)
+ {
+ get();
+ if (JSON_UNLIKELY(not get_cbor_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+
+ if (JSON_UNLIKELY(not parse_cbor_internal()))
+ {
+ return false;
+ }
+ key.clear();
+ }
+ }
+ else
+ {
+ while (get() != 0xFF)
+ {
+ if (JSON_UNLIKELY(not get_cbor_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+
+ if (JSON_UNLIKELY(not parse_cbor_internal()))
+ {
+ return false;
+ }
+ key.clear();
+ }
+ }
+
+ return sax->end_object();
}
/*!
@@ -5822,14 +7090,16 @@
This function first reads starting bytes to determine the expected
string length and then copies this number of bytes into a string.
- @return string
+ @param[out] result created string
- @throw parse_error.110 if input ended
- @throw parse_error.113 if an unexpected byte is read
+ @return whether string creation completed
*/
- string_t get_msgpack_string()
+ bool get_msgpack_string(string_t& result)
{
- unexpect_eof();
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
switch (current)
{
@@ -5867,59 +7137,85 @@
case 0xBE:
case 0xBF:
{
- return get_string(current & 0x1F);
+ return get_string(current & 0x1F, result);
}
case 0xD9: // str 8
{
- return get_string(get_number<uint8_t>());
+ uint8_t len;
+ return get_number(len) and get_string(len, result);
}
case 0xDA: // str 16
{
- return get_string(get_number<uint16_t>());
+ uint16_t len;
+ return get_number(len) and get_string(len, result);
}
case 0xDB: // str 32
{
- return get_string(get_number<uint32_t>());
+ uint32_t len;
+ return get_number(len) and get_string(len, result);
}
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 MessagePack string; last byte: 0x" + ss.str()));
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, "expected a MessagePack string; last byte: 0x" + last_token));
}
}
}
- template<typename NumberType>
- BasicJsonType get_msgpack_array(const NumberType len)
+ /*!
+ @param[in] len the length of the array
+ @return whether array creation completed
+ */
+ bool get_msgpack_array(const std::size_t len)
{
- BasicJsonType result = value_t::array;
- std::generate_n(std::back_inserter(*result.m_value.array), len, [this]()
+ if (JSON_UNLIKELY(not sax->start_array(len)))
{
- return parse_msgpack_internal();
- });
- return result;
+ return false;
+ }
+
+ for (std::size_t i = 0; i < len; ++i)
+ {
+ if (JSON_UNLIKELY(not parse_msgpack_internal()))
+ {
+ return false;
+ }
+ }
+
+ return sax->end_array();
}
- template<typename NumberType>
- BasicJsonType get_msgpack_object(const NumberType len)
+ /*!
+ @param[in] len the length of the object
+ @return whether object creation completed
+ */
+ bool get_msgpack_object(const std::size_t len)
{
- BasicJsonType result = value_t::object;
- std::generate_n(std::inserter(*result.m_value.object,
- result.m_value.object->end()),
- len, [this]()
+ if (JSON_UNLIKELY(not sax->start_object(len)))
+ {
+ return false;
+ }
+
+ string_t key;
+ for (std::size_t i = 0; i < len; ++i)
{
get();
- auto key = get_msgpack_string();
- auto val = parse_msgpack_internal();
- return std::make_pair(std::move(key), std::move(val));
- });
- return result;
+ if (JSON_UNLIKELY(not get_msgpack_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+
+ if (JSON_UNLIKELY(not parse_msgpack_internal()))
+ {
+ return false;
+ }
+ key.clear();
+ }
+
+ return sax->end_object();
}
/*!
@@ -5929,41 +7225,131 @@
indicating a string, or in case of an object key where the 'S' byte can be
left out.
+ @param[out] result created string
@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
+ @return whether string creation completed
*/
- string_t get_ubjson_string(const bool get_char = true)
+ bool get_ubjson_string(string_t& result, const bool get_char = true)
{
if (get_char)
{
get(); // TODO: may we ignore N here?
}
- unexpect_eof();
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
switch (current)
{
case 'U':
- return get_string(get_number<uint8_t>());
+ {
+ uint8_t len;
+ return get_number(len) and get_string(len, result);
+ }
+
case 'i':
- return get_string(get_number<int8_t>());
+ {
+ int8_t len;
+ return get_number(len) and get_string(len, result);
+ }
+
case 'I':
- return get_string(get_number<int16_t>());
+ {
+ int16_t len;
+ return get_number(len) and get_string(len, result);
+ }
+
case 'l':
- return get_string(get_number<int32_t>());
+ {
+ int32_t len;
+ return get_number(len) and get_string(len, result);
+ }
+
case 'L':
- return get_string(get_number<int64_t>());
+ {
+ int64_t len;
+ return get_number(len) and get_string(len, result);
+ }
+
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()));
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, "expected a UBJSON string; last byte: 0x" + last_token));
+ }
+ }
+
+ /*!
+ @param[out] result determined size
+ @return whether size determination completed
+ */
+ bool get_ubjson_size_value(std::size_t& result)
+ {
+ switch (get_ignore_noop())
+ {
+ case 'U':
+ {
+ uint8_t number;
+ if (JSON_UNLIKELY(not get_number(number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ case 'i':
+ {
+ int8_t number;
+ if (JSON_UNLIKELY(not get_number(number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ case 'I':
+ {
+ int16_t number;
+ if (JSON_UNLIKELY(not get_number(number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ case 'l':
+ {
+ int32_t number;
+ if (JSON_UNLIKELY(not get_number(number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ case 'L':
+ {
+ int64_t number;
+ if (JSON_UNLIKELY(not get_number(number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ default:
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, "byte after '#' must denote a number type; last byte: 0x" + last_token));
+ }
}
}
@@ -5973,84 +7359,127 @@
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
+ @param[out] result pair of the size and the type
+
+ @return whether pair creation completed
*/
- std::pair<std::size_t, int> get_ubjson_size_type()
+ bool get_ubjson_size_type(std::pair<std::size_t, int>& result)
{
- std::size_t sz = string_t::npos;
- int tc = 0;
+ result.first = string_t::npos; // size
+ result.second = 0; // type
get_ignore_noop();
if (current == '$')
{
- tc = get(); // must not ignore 'N', because 'N' maybe the type
- unexpect_eof();
+ result.second = get(); // must not ignore 'N', because 'N' maybe the type
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
get_ignore_noop();
- if (current != '#')
+ if (JSON_UNLIKELY(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()));
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, "expected '#' after UBJSON type information; last byte: 0x" + last_token));
}
- sz = parse_ubjson_internal();
+
+ return get_ubjson_size_value(result.first);
}
else if (current == '#')
{
- sz = parse_ubjson_internal();
+ return get_ubjson_size_value(result.first);
}
-
- return std::make_pair(sz, tc);
+ return true;
}
- BasicJsonType get_ubjson_value(const int prefix)
+ /*!
+ @param prefix the previously read or set type prefix
+ @return whether value creation completed
+ */
+ bool get_ubjson_value(const int prefix)
{
switch (prefix)
{
case std::char_traits<char>::eof(): // EOF
- JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
+ return unexpect_eof();
case 'T': // true
- return true;
+ return sax->boolean(true);
case 'F': // false
- return false;
+ return sax->boolean(false);
case 'Z': // null
- return nullptr;
+ return sax->null();
case 'U':
- return get_number<uint8_t>();
+ {
+ uint8_t number;
+ return get_number(number) and sax->number_unsigned(number);
+ }
+
case 'i':
- return get_number<int8_t>();
+ {
+ int8_t number;
+ return get_number(number) and sax->number_integer(number);
+ }
+
case 'I':
- return get_number<int16_t>();
+ {
+ int16_t number;
+ return get_number(number) and sax->number_integer(number);
+ }
+
case 'l':
- return get_number<int32_t>();
+ {
+ int32_t number;
+ return get_number(number) and sax->number_integer(number);
+ }
+
case 'L':
- return get_number<int64_t>();
+ {
+ int64_t number;
+ return get_number(number) and sax->number_integer(number);
+ }
+
case 'd':
- return get_number<float>();
+ {
+ float number;
+ return get_number(number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
+
case 'D':
- return get_number<double>();
+ {
+ double number;
+ return get_number(number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
case 'C': // char
{
get();
- unexpect_eof();
+ if (JSON_UNLIKELY(not unexpect_eof()))
+ {
+ return false;
+ }
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()));
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token));
}
- return string_t(1, static_cast<char>(current));
+ string_t s(1, static_cast<char>(current));
+ return sax->string(s);
}
case 'S': // string
- return get_ubjson_string();
+ {
+ string_t s;
+ return get_ubjson_string(s) and sax->string(s);
+ }
case '[': // array
return get_ubjson_array();
@@ -6059,129 +7488,170 @@
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()));
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, "error reading UBJSON; last byte: 0x" + last_token));
+ }
}
}
- BasicJsonType get_ubjson_array()
+ /*!
+ @return whether array creation completed
+ */
+ bool get_ubjson_array()
{
- BasicJsonType result = value_t::array;
- const auto size_and_type = get_ubjson_size_type();
+ std::pair<std::size_t, int> size_and_type;
+ if (JSON_UNLIKELY(not get_ubjson_size_type(size_and_type)))
+ {
+ return false;
+ }
if (size_and_type.first != string_t::npos)
{
- if (JSON_UNLIKELY(size_and_type.first > result.max_size()))
+ if (JSON_UNLIKELY(not sax->start_array(size_and_type.first)))
{
- JSON_THROW(out_of_range::create(408,
- "excessive array size: " + std::to_string(size_and_type.first)));
+ return false;
}
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]()
+ for (std::size_t i = 0; i < size_and_type.first; ++i)
{
- return get_ubjson_value(size_and_type.second);
- });
+ if (JSON_UNLIKELY(not get_ubjson_value(size_and_type.second)))
+ {
+ return false;
+ }
+ }
}
}
else
{
- std::generate_n(std::back_inserter(*result.m_value.array),
- size_and_type.first, [this]()
+ for (std::size_t i = 0; i < size_and_type.first; ++i)
{
- return parse_ubjson_internal();
- });
+ if (JSON_UNLIKELY(not parse_ubjson_internal()))
+ {
+ return false;
+ }
+ }
}
}
else
{
+ if (JSON_UNLIKELY(not sax->start_array(std::size_t(-1))))
+ {
+ return false;
+ }
+
while (current != ']')
{
- result.push_back(parse_ubjson_internal(false));
+ if (JSON_UNLIKELY(not parse_ubjson_internal(false)))
+ {
+ return false;
+ }
get_ignore_noop();
}
}
- return result;
+ return sax->end_array();
}
- BasicJsonType get_ubjson_object()
+ /*!
+ @return whether object creation completed
+ */
+ bool get_ubjson_object()
{
- BasicJsonType result = value_t::object;
- const auto size_and_type = get_ubjson_size_type();
+ std::pair<std::size_t, int> size_and_type;
+ if (JSON_UNLIKELY(not get_ubjson_size_type(size_and_type)))
+ {
+ return false;
+ }
+ string_t key;
if (size_and_type.first != string_t::npos)
{
- if (JSON_UNLIKELY(size_and_type.first > result.max_size()))
+ if (JSON_UNLIKELY(not sax->start_object(size_and_type.first)))
{
- JSON_THROW(out_of_range::create(408,
- "excessive object size: " + std::to_string(size_and_type.first)));
+ return false;
}
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]()
+ for (std::size_t i = 0; i < size_and_type.first; ++i)
{
- auto key = get_ubjson_string();
- auto val = get_ubjson_value(size_and_type.second);
- return std::make_pair(std::move(key), std::move(val));
- });
+ if (JSON_UNLIKELY(not get_ubjson_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+ if (JSON_UNLIKELY(not get_ubjson_value(size_and_type.second)))
+ {
+ return false;
+ }
+ key.clear();
+ }
}
else
{
- std::generate_n(std::inserter(*result.m_value.object,
- result.m_value.object->end()),
- size_and_type.first, [this]()
+ for (std::size_t i = 0; i < size_and_type.first; ++i)
{
- auto key = get_ubjson_string();
- auto val = parse_ubjson_internal();
- return std::make_pair(std::move(key), std::move(val));
- });
+ if (JSON_UNLIKELY(not get_ubjson_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+ if (JSON_UNLIKELY(not parse_ubjson_internal()))
+ {
+ return false;
+ }
+ key.clear();
+ }
}
}
else
{
+ if (JSON_UNLIKELY(not sax->start_object(std::size_t(-1))))
+ {
+ return false;
+ }
+
while (current != '}')
{
- auto key = get_ubjson_string(false);
- result[std::move(key)] = parse_ubjson_internal();
+ if (JSON_UNLIKELY(not get_ubjson_string(key, false) or not sax->key(key)))
+ {
+ return false;
+ }
+ if (JSON_UNLIKELY(not parse_ubjson_internal()))
+ {
+ return false;
+ }
get_ignore_noop();
+ key.clear();
}
}
- return result;
+ return sax->end_object();
}
/*!
- @brief throw if end of input is not reached
- @throw parse_error.110 if input not ended
+ @return whether the last read character is not EOF
*/
- void expect_eof() const
- {
- if (JSON_UNLIKELY(current != std::char_traits<char>::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 unexpect_eof() const
+ bool unexpect_eof() const
{
if (JSON_UNLIKELY(current == std::char_traits<char>::eof()))
{
- JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
+ return sax->parse_error(chars_read, "<end of file>", parse_error::create(110, chars_read, "unexpected end of input"));
}
+ return true;
+ }
+
+ /*!
+ @return a string representation of the last read byte
+ */
+ std::string get_token_string() const
+ {
+ char cr[3];
+ snprintf(cr, 3, "%.2hhX", static_cast<unsigned char>(current));
+ return std::string{cr};
}
private:
@@ -6196,6 +7666,9 @@
/// whether we can assume little endianess
const bool is_little_endian = little_endianess();
+
+ /// the SAX parser
+ json_sax_t* sax = nullptr;
};
}
}
@@ -6354,9 +7827,9 @@
break;
}
- case value_t::number_float: // Double-Precision Float
+ case value_t::number_float:
{
- oa->write_character(static_cast<CharType>(0xFB));
+ oa->write_character(get_cbor_float_prefix(j.m_value.number_float));
write_number(j.m_value.number_float);
break;
}
@@ -6614,9 +8087,9 @@
break;
}
- case value_t::number_float: // float 64
+ case value_t::number_float:
{
- oa->write_character(static_cast<CharType>(0xCB));
+ oa->write_character(get_msgpack_float_prefix(j.m_value.number_float));
write_number(j.m_value.number_float);
break;
}
@@ -6793,7 +8266,7 @@
if (use_type and not j.m_value.array->empty())
{
assert(use_count);
- const char first_prefix = ubjson_prefix(j.front());
+ const CharType first_prefix = ubjson_prefix(j.front());
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
[this, first_prefix](const BasicJsonType & v)
{
@@ -6804,7 +8277,7 @@
{
prefix_required = false;
oa->write_character(static_cast<CharType>('$'));
- oa->write_character(static_cast<CharType>(first_prefix));
+ oa->write_character(first_prefix);
}
}
@@ -6838,7 +8311,7 @@
if (use_type and not j.m_value.object->empty())
{
assert(use_count);
- const char first_prefix = ubjson_prefix(j.front());
+ const CharType first_prefix = ubjson_prefix(j.front());
const bool same_prefix = std::all_of(j.begin(), j.end(),
[this, first_prefix](const BasicJsonType & v)
{
@@ -6849,7 +8322,7 @@
{
prefix_required = false;
oa->write_character(static_cast<CharType>('$'));
- oa->write_character(static_cast<CharType>(first_prefix));
+ oa->write_character(first_prefix);
}
}
@@ -6909,116 +8382,126 @@
oa->write_characters(vec.data(), sizeof(NumberType));
}
- template<typename NumberType>
+ // UBJSON: write number (floating point)
+ template<typename NumberType, typename std::enable_if<
+ std::is_floating_point<NumberType>::value, int>::type = 0>
void write_number_with_ubjson_prefix(const NumberType n,
const bool add_prefix)
{
- if (std::is_floating_point<NumberType>::value)
+ if (add_prefix)
+ {
+ oa->write_character(get_ubjson_float_prefix(n));
+ }
+ write_number(n);
+ }
+
+ // UBJSON: write number (unsigned integer)
+ template<typename NumberType, typename std::enable_if<
+ std::is_unsigned<NumberType>::value, int>::type = 0>
+ void write_number_with_ubjson_prefix(const NumberType n,
+ const bool add_prefix)
+ {
+ if (n <= static_cast<uint64_t>((std::numeric_limits<int8_t>::max)()))
{
if (add_prefix)
{
- oa->write_character(static_cast<CharType>('D')); // float64
+ oa->write_character(static_cast<CharType>('i')); // int8
}
- write_number(n);
+ write_number(static_cast<uint8_t>(n));
}
- else if (std::is_unsigned<NumberType>::value)
+ else if (n <= (std::numeric_limits<uint8_t>::max)())
{
- if (n <= (std::numeric_limits<int8_t>::max)())
+ if (add_prefix)
{
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('i')); // int8
- }
- write_number(static_cast<uint8_t>(n));
+ oa->write_character(static_cast<CharType>('U')); // uint8
}
- else if (n <= (std::numeric_limits<uint8_t>::max)())
+ write_number(static_cast<uint8_t>(n));
+ }
+ else if (n <= static_cast<uint64_t>((std::numeric_limits<int16_t>::max)()))
+ {
+ if (add_prefix)
{
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('U')); // uint8
- }
- write_number(static_cast<uint8_t>(n));
+ oa->write_character(static_cast<CharType>('I')); // int16
}
- else if (n <= (std::numeric_limits<int16_t>::max)())
+ write_number(static_cast<int16_t>(n));
+ }
+ else if (n <= static_cast<uint64_t>((std::numeric_limits<int32_t>::max)()))
+ {
+ if (add_prefix)
{
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('I')); // int16
- }
- write_number(static_cast<int16_t>(n));
+ oa->write_character(static_cast<CharType>('l')); // int32
}
- else if (n <= (std::numeric_limits<int32_t>::max)())
+ write_number(static_cast<int32_t>(n));
+ }
+ else if (n <= static_cast<uint64_t>((std::numeric_limits<int64_t>::max)()))
+ {
+ if (add_prefix)
{
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('l')); // int32
- }
- write_number(static_cast<int32_t>(n));
+ oa->write_character(static_cast<CharType>('L')); // int64
}
- else if (n <= (std::numeric_limits<int64_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('L')); // int64
- }
- write_number(static_cast<int64_t>(n));
- }
- else
- {
- JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n)));
- }
+ write_number(static_cast<int64_t>(n));
}
else
{
- if ((std::numeric_limits<int8_t>::min)() <= n and n <= (std::numeric_limits<int8_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('i')); // int8
- }
- write_number(static_cast<int8_t>(n));
- }
- else if ((std::numeric_limits<uint8_t>::min)() <= n and n <= (std::numeric_limits<uint8_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('U')); // uint8
- }
- write_number(static_cast<uint8_t>(n));
- }
- else if ((std::numeric_limits<int16_t>::min)() <= n and n <= (std::numeric_limits<int16_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('I')); // int16
- }
- write_number(static_cast<int16_t>(n));
- }
- else if ((std::numeric_limits<int32_t>::min)() <= n and n <= (std::numeric_limits<int32_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('l')); // int32
- }
- write_number(static_cast<int32_t>(n));
- }
- else if ((std::numeric_limits<int64_t>::min)() <= n and n <= (std::numeric_limits<int64_t>::max)())
- {
- if (add_prefix)
- {
- oa->write_character(static_cast<CharType>('L')); // int64
- }
- write_number(static_cast<int64_t>(n));
- }
- // LCOV_EXCL_START
- else
- {
- JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n)));
- }
- // LCOV_EXCL_STOP
+ JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n)));
}
}
+ // UBJSON: write number (signed integer)
+ template<typename NumberType, typename std::enable_if<
+ std::is_signed<NumberType>::value and
+ not std::is_floating_point<NumberType>::value, int>::type = 0>
+ void write_number_with_ubjson_prefix(const NumberType n,
+ const bool add_prefix)
+ {
+ if ((std::numeric_limits<int8_t>::min)() <= n and n <= (std::numeric_limits<int8_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(static_cast<CharType>('i')); // int8
+ }
+ write_number(static_cast<int8_t>(n));
+ }
+ else if (static_cast<int64_t>((std::numeric_limits<uint8_t>::min)()) <= n and n <= static_cast<int64_t>((std::numeric_limits<uint8_t>::max)()))
+ {
+ if (add_prefix)
+ {
+ oa->write_character(static_cast<CharType>('U')); // uint8
+ }
+ write_number(static_cast<uint8_t>(n));
+ }
+ else if ((std::numeric_limits<int16_t>::min)() <= n and n <= (std::numeric_limits<int16_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(static_cast<CharType>('I')); // int16
+ }
+ write_number(static_cast<int16_t>(n));
+ }
+ else if ((std::numeric_limits<int32_t>::min)() <= n and n <= (std::numeric_limits<int32_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(static_cast<CharType>('l')); // int32
+ }
+ write_number(static_cast<int32_t>(n));
+ }
+ else if ((std::numeric_limits<int64_t>::min)() <= n and n <= (std::numeric_limits<int64_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(static_cast<CharType>('L')); // int64
+ }
+ write_number(static_cast<int64_t>(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
@@ -7028,7 +8511,7 @@
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
+ CharType ubjson_prefix(const BasicJsonType& j) const noexcept
{
switch (j.type())
{
@@ -7087,7 +8570,7 @@
}
case value_t::number_float:
- return 'D';
+ return get_ubjson_float_prefix(j.m_value.number_float);
case value_t::string:
return 'S';
@@ -7103,6 +8586,36 @@
}
}
+ static constexpr CharType get_cbor_float_prefix(float)
+ {
+ return static_cast<CharType>(0xFA); // Single-Precision Float
+ }
+
+ static constexpr CharType get_cbor_float_prefix(double)
+ {
+ return static_cast<CharType>(0xFB); // Double-Precision Float
+ }
+
+ static constexpr CharType get_msgpack_float_prefix(float)
+ {
+ return static_cast<CharType>(0xCA); // float 32
+ }
+
+ static constexpr CharType get_msgpack_float_prefix(double)
+ {
+ return static_cast<CharType>(0xCB); // float 64
+ }
+
+ static constexpr CharType get_ubjson_float_prefix(float)
+ {
+ return 'd'; // float 32
+ }
+
+ static constexpr CharType get_ubjson_float_prefix(double)
+ {
+ return 'D'; // float 64
+ }
+
private:
/// whether we can assume little endianess
const bool is_little_endian = binary_reader<BasicJsonType>::little_endianess();
@@ -7125,11 +8638,8 @@
#include <cstddef> // size_t, ptrdiff_t
#include <cstdint> // uint8_t
#include <cstdio> // snprintf
-#include <iomanip> // setfill
-#include <iterator> // next
#include <limits> // numeric_limits
#include <string> // string
-#include <sstream> // stringstream
#include <type_traits> // is_same
// #include <nlohmann/detail/exceptions.hpp>
@@ -8024,7 +9534,7 @@
// 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)
+ // The documentation for 'std::to_chars' (https://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.
@@ -8233,7 +9743,7 @@
// #include <nlohmann/detail/macro_scope.hpp>
-// #include <nlohmann/detail/meta.hpp>
+// #include <nlohmann/detail/meta/cpp_future.hpp>
// #include <nlohmann/detail/output/output_adapters.hpp>
@@ -8606,9 +10116,9 @@
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<int>(byte);
- JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str()));
+ std::string sn(3, '\0');
+ snprintf(&sn[0], sn.size(), "%.2X", byte);
+ JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn));
}
default: // decode found yet incomplete multi-byte code point
@@ -8634,9 +10144,9 @@
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<int>(static_cast<uint8_t>(s.back()));
- JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str()));
+ std::string sn(3, '\0');
+ snprintf(&sn[0], sn.size(), "%.2X", static_cast<uint8_t>(s.back()));
+ JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn));
}
}
@@ -9698,42 +11208,42 @@
@requirement The class satisfies the following concept requirements:
- Basic
- - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible):
+ - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible):
JSON values can be default constructed. The result will be a JSON null
value.
- - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible):
+ - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible):
A JSON value can be constructed from an rvalue argument.
- - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible):
+ - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible):
A JSON value can be copy-constructed from an lvalue expression.
- - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable):
+ - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable):
A JSON value van be assigned from an rvalue argument.
- - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable):
+ - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable):
A JSON value can be copy-assigned from an lvalue expression.
- - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible):
+ - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible):
JSON values can be destructed.
- Layout
- - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType):
+ - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType):
JSON values have
- [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout):
+ [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout):
All non-static data members are private and standard layout types, the
class has no virtual functions or (virtual) base classes.
- Library-wide
- - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable):
+ - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable):
JSON values can be compared with `==`, see @ref
operator==(const_reference,const_reference).
- - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable):
+ - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable):
JSON values can be compared with `<`, see @ref
operator<(const_reference,const_reference).
- - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable):
+ - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable):
Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of
other compatible types, using unqualified function call @ref swap().
- - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer):
+ - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer):
JSON values can be compared against `std::nullptr_t` objects which are used
to model the `null` value.
- Container
- - [Container](http://en.cppreference.com/w/cpp/concept/Container):
+ - [Container](https://en.cppreference.com/w/cpp/named_req/Container):
JSON values can be used like STL containers and provide iterator access.
- - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer);
+ - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer);
JSON values can be used like STL containers and provide reverse iterator
access.
@@ -9767,8 +11277,12 @@
friend class ::nlohmann::detail::iter_impl;
template<typename BasicJsonType, typename CharType>
friend class ::nlohmann::detail::binary_writer;
- template<typename BasicJsonType>
+ template<typename BasicJsonType, typename SAX>
friend class ::nlohmann::detail::binary_reader;
+ template<typename BasicJsonType>
+ friend class ::nlohmann::detail::json_sax_dom_parser;
+ template<typename BasicJsonType>
+ friend class ::nlohmann::detail::json_sax_dom_callback_parser;
/// workaround type for MSVC
using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
@@ -9796,13 +11310,17 @@
public:
using value_t = detail::value_t;
- /// @copydoc nlohmann::json_pointer
+ /// JSON Pointer, see @ref nlohmann::json_pointer
using json_pointer = ::nlohmann::json_pointer<basic_json>;
template<typename T, typename SFINAE>
using json_serializer = JSONSerializer<T, SFINAE>;
/// helper type for initializer lists of basic_json values
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
+ using input_format_t = detail::input_format_t;
+ /// SAX interface type, see @ref nlohmann::json_sax
+ using json_sax_t = json_sax<basic_json>;
+
////////////////
// exceptions //
////////////////
@@ -10535,7 +12053,7 @@
object = nullptr; // silence warning, see #821
if (JSON_UNLIKELY(t == value_t::null))
{
- JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.1")); // LCOV_EXCL_LINE
+ JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.2.0")); // LCOV_EXCL_LINE
}
break;
}
@@ -10703,7 +12221,6 @@
*/
using parser_callback_t = typename parser::parser_callback_t;
-
//////////////////
// constructors //
//////////////////
@@ -10805,6 +12322,7 @@
- @a CompatibleType is not derived from `std::istream`,
- @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move
constructors),
+ - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments)
- @a CompatibleType is not a @ref basic_json nested type (e.g.,
@ref json_pointer, @ref iterator, etc ...)
- @ref @ref json_serializer<U> has a
@@ -10841,6 +12359,78 @@
}
/*!
+ @brief create a JSON value from an existing one
+
+ This is a constructor for existing @ref basic_json types.
+ It does not hijack copy/move constructors, since the parameter has different
+ template arguments than the current ones.
+
+ The constructor tries to convert the internal @ref m_value of the parameter.
+
+ @tparam BasicJsonType a type such that:
+ - @a BasicJsonType is a @ref basic_json type.
+ - @a BasicJsonType has different template arguments than @ref basic_json_t.
+
+ @param[in] val the @ref basic_json value to be converted.
+
+ @complexity Usually linear in the size of the passed @a val, also
+ depending on the implementation of the called `to_json()`
+ method.
+
+ @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.
+
+ @since version 3.2.0
+ */
+ template <typename BasicJsonType,
+ detail::enable_if_t<
+ detail::is_basic_json<BasicJsonType>::value and not std::is_same<basic_json, BasicJsonType>::value, int> = 0>
+ basic_json(const BasicJsonType& val)
+ {
+ using other_boolean_t = typename BasicJsonType::boolean_t;
+ using other_number_float_t = typename BasicJsonType::number_float_t;
+ using other_number_integer_t = typename BasicJsonType::number_integer_t;
+ using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using other_string_t = typename BasicJsonType::string_t;
+ using other_object_t = typename BasicJsonType::object_t;
+ using other_array_t = typename BasicJsonType::array_t;
+
+ switch (val.type())
+ {
+ case value_t::boolean:
+ JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
+ break;
+ case value_t::number_float:
+ JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
+ break;
+ case value_t::number_integer:
+ JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
+ break;
+ case value_t::number_unsigned:
+ JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
+ break;
+ case value_t::string:
+ JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
+ break;
+ case value_t::object:
+ JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
+ break;
+ case value_t::array:
+ JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
+ break;
+ case value_t::null:
+ *this = nullptr;
+ break;
+ case value_t::discarded:
+ m_type = value_t::discarded;
+ break;
+ }
+ assert_invariant();
+ }
+
+ /*!
@brief create a container (array or object) from an initializer list
Creates a JSON value of type array or object from the passed initializer
@@ -11112,7 +12702,7 @@
@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
+ See https://en.cppreference.com/w/cpp/error/assert for more
information.
@throw invalid_iterator.201 if iterators @a first and @a last are not
@@ -11252,7 +12842,7 @@
changes to any JSON value.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is linear.
- As postcondition, it holds: `other == basic_json(other)`.
@@ -11337,7 +12927,7 @@
exceptions.
@requirement This function helps `basic_json` satisfying the
- [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible)
+ [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible)
requirements.
@liveexample{The code below shows the move constructor explicitly called
@@ -11371,7 +12961,7 @@
@complexity Linear.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is linear.
@@ -11408,7 +12998,7 @@
@complexity Linear.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is linear.
- All stored elements are destroyed and all memory is freed.
@@ -11472,7 +13062,7 @@
const bool ensure_ascii = false) const
{
string_t result;
- serializer s(detail::output_adapter<char>(result), indent_char);
+ serializer s(detail::output_adapter<char, string_t>(result), indent_char);
if (indent >= 0)
{
@@ -12013,11 +13603,34 @@
}
/*!
+ @brief get special-case overload
+
+ This overloads converts the current @ref basic_json in a different
+ @ref basic_json type
+
+ @tparam BasicJsonType == @ref basic_json
+
+ @return a copy of *this, converted into @tparam BasicJsonType
+
+ @complexity Depending on the implementation of the called `from_json()`
+ method.
+
+ @since version 3.2.0
+ */
+ template<typename BasicJsonType, detail::enable_if_t<
+ not std::is_same<BasicJsonType, basic_json>::value and
+ detail::is_basic_json<BasicJsonType>::value, int> = 0>
+ BasicJsonType get() const
+ {
+ return *this;
+ }
+
+ /*!
@brief get a value (explicit)
Explicit type conversion between the JSON value and a compatible value
- which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible)
- and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible).
+ which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)
+ and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).
The value is converted by calling the @ref json_serializer<ValueType>
`from_json()` method.
@@ -12053,7 +13666,7 @@
*/
template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>,
detail::enable_if_t <
- not std::is_same<basic_json_t, ValueType>::value and
+ not detail::is_basic_json<ValueType>::value and
detail::has_from_json<basic_json_t, ValueType>::value and
not detail::has_non_default_from_json<basic_json_t, ValueType>::value,
int> = 0>
@@ -12077,8 +13690,8 @@
@brief get a value (explicit); special case
Explicit type conversion between the JSON value and a compatible value
- which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible)
- and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible).
+ which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)
+ and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).
The value is converted by calling the @ref json_serializer<ValueType>
`from_json()` method.
@@ -12319,13 +13932,14 @@
template < typename ValueType, typename std::enable_if <
not std::is_pointer<ValueType>::value and
not std::is_same<ValueType, detail::json_ref<basic_json>>::value and
- not std::is_same<ValueType, typename string_t::value_type>::value
+ not std::is_same<ValueType, typename string_t::value_type>::value and
+ not detail::is_basic_json<ValueType>::value
#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015
and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value
-#endif
-#if defined(JSON_HAS_CPP_17)
+#if defined(JSON_HAS_CPP_17) && defined(_MSC_VER) and _MSC_VER <= 1914
and not std::is_same<ValueType, typename std::string_view>::value
#endif
+#endif
, int >::type = 0 >
operator ValueType() const
{
@@ -12906,7 +14520,7 @@
@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 case,
+ @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.
@@ -12930,7 +14544,7 @@
{
return ptr.get_checked(this);
}
- JSON_CATCH (out_of_range&)
+ JSON_INTERNAL_CATCH (out_of_range&)
{
return default_value;
}
@@ -13441,7 +15055,7 @@
@complexity Constant.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is constant.
@@ -13480,7 +15094,7 @@
@complexity Constant.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is constant.
- Has the semantics of `const_cast<const basic_json&>(*this).begin()`.
@@ -13512,7 +15126,7 @@
@complexity Constant.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is constant.
@@ -13551,7 +15165,7 @@
@complexity Constant.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is constant.
- Has the semantics of `const_cast<const basic_json&>(*this).end()`.
@@ -13581,7 +15195,7 @@
@complexity Constant.
@requirement This function helps `basic_json` satisfying the
- [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
+ [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
requirements:
- The complexity is constant.
- Has the semantics of `reverse_iterator(end())`.
@@ -13618,7 +15232,7 @@
@complexity Constant.
@requirement This function helps `basic_json` satisfying the
- [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
+ [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
requirements:
- The complexity is constant.
- Has the semantics of `reverse_iterator(begin())`.
@@ -13655,7 +15269,7 @@
@complexity Constant.
@requirement This function helps `basic_json` satisfying the
- [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
+ [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
requirements:
- The complexity is constant.
- Has the semantics of `const_cast<const basic_json&>(*this).rbegin()`.
@@ -13684,7 +15298,7 @@
@complexity Constant.
@requirement This function helps `basic_json` satisfying the
- [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer)
+ [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
requirements:
- The complexity is constant.
- Has the semantics of `const_cast<const basic_json&>(*this).rend()`.
@@ -13825,7 +15439,7 @@
@complexity Constant.
- @since version 3.x.x.
+ @since version 3.1.0.
*/
iteration_proxy<iterator> items() noexcept
{
@@ -13882,7 +15496,7 @@
false in the case of a string.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is constant.
- Has the semantics of `begin() == end()`.
@@ -13953,7 +15567,7 @@
the case of a string.
@requirement This function helps `basic_json` satisfying the
- [Container](http://en.cppreference.com/w/cpp/concept/Container)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is constant.
- Has the semantics of `std::distance(begin(), end())`.
@@ -14023,7 +15637,7 @@
@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)
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
requirements:
- The complexity is constant.
- Has the semantics of returning `b.size()` where `b` is the largest
@@ -14777,7 +16391,7 @@
// passed iterators must belong to objects
if (JSON_UNLIKELY(not first.m_object->is_object()
- or not first.m_object->is_object()))
+ or not last.m_object->is_object()))
{
JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
}
@@ -15492,7 +17106,7 @@
@since version 2.0.3 (contiguous containers)
*/
- static basic_json parse(detail::input_adapter i,
+ static basic_json parse(detail::input_adapter&& i,
const parser_callback_t cb = nullptr,
const bool allow_exceptions = true)
{
@@ -15501,26 +17115,80 @@
return result;
}
+ static bool accept(detail::input_adapter&& i)
+ {
+ return parser(i).accept(true);
+ }
+
/*!
- @copydoc basic_json parse(detail::input_adapter, const parser_callback_t)
+ @brief generate SAX events
+
+ The SAX event lister must follow the interface of @ref json_sax.
+
+ This function reads from a compatible input. Examples are:
+ - an array of 1-byte values
+ - strings with character/literal type with size of 1 byte
+ - input streams
+ - container with contiguous storage of 1-byte values. Compatible container
+ types include `std::vector`, `std::string`, `std::array`,
+ `std::valarray`, and `std::initializer_list`. Furthermore, C-style
+ arrays can be used with `std::begin()`/`std::end()`. User-defined
+ containers can be used as long as they implement random-access iterators
+ and a contiguous storage.
+
+ @pre Each element of the container has a size of 1 byte. Violating this
+ precondition yields undefined behavior. **This precondition is enforced
+ with a static assertion.**
+
+ @pre The container storage is contiguous. Violating this precondition
+ yields undefined behavior. **This precondition is enforced with an
+ assertion.**
+ @pre Each element of the container has a size of 1 byte. Violating this
+ precondition yields undefined behavior. **This precondition is enforced
+ with a static assertion.**
+
+ @warning There is no way to enforce all preconditions at compile-time. If
+ the function is called with a noncompliant container and with
+ assertions switched off, the behavior is undefined and will most
+ likely yield segmentation violation.
+
+ @param[in] i input to read from
+ @param[in,out] sax SAX event listener
+ @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON)
+ @param[in] strict whether the input has to be consumed completely
+
+ @return return value of the last processed SAX event
+
+ @throw parse_error.101 if a parse error occurs; example: `""unexpected end
+ of input; expected string literal""`
+ @throw parse_error.102 if to_unicode fails or surrogate error
+ @throw parse_error.103 if to_unicode fails
+
+ @complexity Linear in the length of the input. The parser is a predictive
+ LL(1) parser. The complexity can be higher if the SAX consumer @a sax has
+ a super-linear complexity.
+
+ @note A UTF-8 byte order mark is silently ignored.
+
+ @liveexample{The example below demonstrates the `sax_parse()` function
+ reading from string and processing the events with a user-defined SAX
+ event consumer.,sax_parse}
+
+ @since version 3.2.0
*/
- static basic_json parse(detail::input_adapter& i,
- const parser_callback_t cb = nullptr,
- const bool allow_exceptions = true)
+ template <typename SAX>
+ static bool sax_parse(detail::input_adapter&& i, SAX* sax,
+ input_format_t format = input_format_t::json,
+ const bool strict = true)
{
- basic_json result;
- parser(i, cb, allow_exceptions).parse(true, result);
- return result;
- }
-
- static bool accept(detail::input_adapter i)
- {
- return parser(i).accept(true);
- }
-
- static bool accept(detail::input_adapter& i)
- {
- return parser(i).accept(true);
+ assert(sax);
+ switch (format)
+ {
+ case input_format_t::json:
+ return parser(std::move(i)).sax_parse(sax, strict);
+ default:
+ return detail::binary_reader<basic_json, SAX>(std::move(i)).sax_parse(format, sax, strict);
+ }
}
/*!
@@ -15592,6 +17260,15 @@
return parser(detail::input_adapter(first, last)).accept(true);
}
+ template<class IteratorType, class SAX, typename std::enable_if<
+ std::is_base_of<
+ std::random_access_iterator_tag,
+ typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
+ static bool sax_parse(IteratorType first, IteratorType last, SAX* sax)
+ {
+ return parser(detail::input_adapter(first, last)).sax_parse(sax);
+ }
+
/*!
@brief deserialize from stream
@deprecated This stream operator is deprecated and will be removed in
@@ -16088,6 +17765,9 @@
@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)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
@return deserialized JSON value
@throw parse_error.110 if the given input ends prematurely or the end of
@@ -16103,29 +17783,39 @@
@sa http://cbor.io
@sa @ref to_cbor(const basic_json&) for the analogous serialization
- @sa @ref from_msgpack(detail::input_adapter, const bool) for the
+ @sa @ref from_msgpack(detail::input_adapter, const bool, const bool) for the
related MessagePack format
- @sa @ref from_ubjson(detail::input_adapter, const bool) for the related
- UBJSON format
+ @sa @ref from_ubjson(detail::input_adapter, const bool, const bool) for the
+ related UBJSON format
@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
+ @a strict parameter since 3.0.0; added @allow_exceptions parameter
+ since 3.2.0
*/
- static basic_json from_cbor(detail::input_adapter i,
- const bool strict = true)
+ static basic_json from_cbor(detail::input_adapter&& i,
+ const bool strict = true,
+ const bool allow_exceptions = true)
{
- return binary_reader(i).parse_cbor(strict);
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::cbor, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
}
/*!
- @copydoc from_cbor(detail::input_adapter, const bool)
+ @copydoc from_cbor(detail::input_adapter, const bool, const bool)
*/
template<typename A1, typename A2,
detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
- static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true)
+ static basic_json from_cbor(A1 && a1, A2 && a2,
+ const bool strict = true,
+ const bool allow_exceptions = true)
{
- return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_cbor(strict);
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::cbor, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
}
/*!
@@ -16178,6 +17868,10 @@
adapter
@param[in] strict whether to expect the input to be consumed until EOF
(true by default)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
+ @return deserialized JSON value
@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
@@ -16192,29 +17886,39 @@
@sa http://msgpack.org
@sa @ref to_msgpack(const basic_json&) for the analogous serialization
- @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
+ @sa @ref from_cbor(detail::input_adapter, const bool, const bool) for the
+ related CBOR format
+ @sa @ref from_ubjson(detail::input_adapter, const bool, const bool) for
+ the related UBJSON format
@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
+ @a strict parameter since 3.0.0; added @allow_exceptions parameter
+ since 3.2.0
*/
- static basic_json from_msgpack(detail::input_adapter i,
- const bool strict = true)
+ static basic_json from_msgpack(detail::input_adapter&& i,
+ const bool strict = true,
+ const bool allow_exceptions = true)
{
- return binary_reader(i).parse_msgpack(strict);
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::msgpack, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
}
/*!
- @copydoc from_msgpack(detail::input_adapter, const bool)
+ @copydoc from_msgpack(detail::input_adapter, const bool, const bool)
*/
template<typename A1, typename A2,
detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
- static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true)
+ static basic_json from_msgpack(A1 && a1, A2 && a2,
+ const bool strict = true,
+ const bool allow_exceptions = true)
{
- return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_msgpack(strict);
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::msgpack, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
}
/*!
@@ -16249,6 +17953,10 @@
@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)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
+ @return deserialized JSON value
@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
@@ -16263,24 +17971,36 @@
@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
+ @sa @ref from_cbor(detail::input_adapter, const bool, const bool) for the
+ related CBOR format
+ @sa @ref from_msgpack(detail::input_adapter, const bool, const bool) for
+ the related MessagePack format
- @since version 3.1.0
+ @since version 3.1.0; added @allow_exceptions parameter since 3.2.0
*/
- static basic_json from_ubjson(detail::input_adapter i,
- const bool strict = true)
+ static basic_json from_ubjson(detail::input_adapter&& i,
+ const bool strict = true,
+ const bool allow_exceptions = true)
{
- return binary_reader(i).parse_ubjson(strict);
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::ubjson, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
}
+ /*!
+ @copydoc from_ubjson(detail::input_adapter, const bool, const bool)
+ */
template<typename A1, typename A2,
detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
- static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true)
+ static basic_json from_ubjson(A1 && a1, A2 && a2,
+ const bool strict = true,
+ const bool allow_exceptions = true)
{
- return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_ubjson(strict);
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::ubjson, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
}
/// @}
@@ -16657,11 +18377,13 @@
break;
}
+ // LCOV_EXCL_START
default:
{
// if there exists a parent it cannot be primitive
- assert(false); // LCOV_EXCL_LINE
+ assert(false);
}
+ // LCOV_EXCL_STOP
}
}
};
@@ -16803,7 +18525,7 @@
// the "path" location must exist - use at()
success = (result.at(ptr) == get_value("test", "value", false));
}
- JSON_CATCH (out_of_range&)
+ JSON_INTERNAL_CATCH (out_of_range&)
{
// ignore out of range errors: success remains false
}
@@ -17081,11 +18803,10 @@
@since version 1.0.0
*/
template<>
-inline void swap(nlohmann::json& j1,
- nlohmann::json& j2) noexcept(
- is_nothrow_move_constructible<nlohmann::json>::value and
- is_nothrow_move_assignable<nlohmann::json>::value
- )
+inline void swap<nlohmann::json>(nlohmann::json& j1, nlohmann::json& j2) noexcept(
+ is_nothrow_move_constructible<nlohmann::json>::value and
+ is_nothrow_move_assignable<nlohmann::json>::value
+)
{
j1.swap(j2);
}
@@ -17174,6 +18895,7 @@
#endif
// clean up
+#undef JSON_INTERNAL_CATCH
#undef JSON_CATCH
#undef JSON_THROW
#undef JSON_TRY
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 862ca41..f048191 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -1,68 +1,70 @@
-cmake_minimum_required(VERSION 3.1)
-
##
## HUNTER
##
option(HUNTER_ENABLED "Use hunter to manage dependencies" OFF)
if(HUNTER_ENABLED)
- include("../cmake/HunterGate.cmake")
- HunterGate(
- URL "https://github.com/ruslo/hunter/archive/v0.19.156.tar.gz"
- SHA1 "8d5e4635b137365e0d1ade4d60accf4e2bb41f0d"
- )
+ include("../cmake/HunterGate.cmake")
+ HunterGate(
+ URL "https://github.com/ruslo/hunter/archive/v0.19.156.tar.gz"
+ SHA1 "8d5e4635b137365e0d1ade4d60accf4e2bb41f0d"
+ )
endif()
-##
-## PROJECT
-## name and version
-##
-project(inja-testing LANGUAGES CXX VERSION ${INJA_VERSION})
##
## TARGETS
##
-set(UNITTEST_TARGET_NAME "inja_unit")
-file(GLOB TEST_SOURCES "src/*.cpp")
-add_executable(${UNITTEST_TARGET_NAME} ${TEST_SOURCES})
+add_executable(inja_test
+ src/unit-files.cpp
+ src/unit-renderer.cpp
+ src/unit-string-helper.cpp
+ src/unit.cpp
+)
+
##
## INCLUDES
##
if(HUNTER_ENABLED) # Use Hunter to manage dependencies
- # Add Catch framework
- hunter_add_package(Catch)
- find_package(Catch CONFIG REQUIRED)
- # Add JSON package
- hunter_add_package(nlohmann_json)
- find_package(nlohmann_json CONFIG REQUIRED)
- # Add dependencies to target
- target_link_libraries(${UNITTEST_TARGET_NAME} Catch::Catch)
- target_link_libraries(${UNITTEST_TARGET_NAME} nlohmann_json)
- target_link_libraries(${UNITTEST_TARGET_NAME} inja)
+ # Add Catch framework
+ hunter_add_package(Catch)
+ find_package(Catch CONFIG REQUIRED)
+
+ # Add JSON package
+ hunter_add_package(nlohmann_json)
+ find_package(nlohmann_json CONFIG REQUIRED)
+
+ # Add dependencies to target
+ target_link_libraries(inja_test Catch::Catch nlohmann_json inja)
else() # Manage dependencies manually
- # Prepare "Catch" library for other executables
- add_library(Catch INTERFACE)
- target_include_directories(Catch INTERFACE "thirdparty")
- # Prepare "JSON" library for other executables
- add_library(JSON INTERFACE)
- target_include_directories(JSON INTERFACE "thirdparty")
- # Add dependencies to target
- target_link_libraries(${UNITTEST_TARGET_NAME} Catch)
- target_link_libraries(${UNITTEST_TARGET_NAME} JSON)
- target_link_libraries(${UNITTEST_TARGET_NAME} inja)
+ # Prepare "Catch" library for other executables
+ add_library(Catch INTERFACE)
+ target_include_directories(Catch INTERFACE "thirdparty")
+
+ # Prepare "JSON" library for other executables
+ add_library(JSON INTERFACE)
+ target_include_directories(JSON INTERFACE "thirdparty")
+
+ # Add dependencies to target
+ target_link_libraries(inja_test Catch JSON inja)
endif()
-# Copy test files to build directory
+
+##
+## Copy test files to build directory
+##
add_custom_command(
- TARGET ${UNITTEST_TARGET_NAME} POST_BUILD
+ TARGET inja_test POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/test/data
${CMAKE_CURRENT_BINARY_DIR}/data
)
+
+
##
## Add tests to make
##
-add_test(NAME "${UNITTEST_TARGET_NAME}_default"
- COMMAND ${UNITTEST_TARGET_NAME}
+add_test(NAME inja_test
+ COMMAND inja_test
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
diff --git a/test/src/catch/catch.hpp b/test/src/catch/catch.hpp
index 882699e..bdc2f74 100644
--- a/test/src/catch/catch.hpp
+++ b/test/src/catch/catch.hpp
@@ -1,6 +1,6 @@
/*
- * Catch v2.1.2
- * Generated: 2018-02-09 17:05:21.506253
+ * Catch v2.3.0
+ * Generated: 2018-07-23 10:09:14.936841
* ----------------------------------------------------------
* This file has been merged from multiple headers. Please don't edit it directly
* Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved.
@@ -14,8 +14,8 @@
#define CATCH_VERSION_MAJOR 2
-#define CATCH_VERSION_MINOR 1
-#define CATCH_VERSION_PATCH 2
+#define CATCH_VERSION_MINOR 3
+#define CATCH_VERSION_PATCH 0
#ifdef __clang__
# pragma clang system_header
@@ -30,16 +30,18 @@
# pragma warning(push)
# pragma warning(disable: 161 1682)
# else // __ICC
-# pragma clang diagnostic ignored "-Wunused-variable"
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wpadded"
# pragma clang diagnostic ignored "-Wswitch-enum"
# pragma clang diagnostic ignored "-Wcovered-switch-default"
# endif
#elif defined __GNUC__
-# pragma GCC diagnostic ignored "-Wunused-variable"
+ // GCC likes to warn on REQUIREs, and we cannot suppress them
+ // locally because g++'s support for _Pragma is lacking in older,
+ // still supported, versions
# pragma GCC diagnostic ignored "-Wparentheses"
# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wunused-variable"
# pragma GCC diagnostic ignored "-Wpadded"
#endif
// end catch_suppress_warnings.h
@@ -55,7 +57,9 @@
# if defined(CATCH_CONFIG_DISABLE_MATCHERS)
# undef CATCH_CONFIG_DISABLE_MATCHERS
# endif
-# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
+# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+# endif
#endif
#if !defined(CATCH_CONFIG_IMPL_ONLY)
@@ -72,7 +76,7 @@
#elif defined(linux) || defined(__linux) || defined(__linux__)
# define CATCH_PLATFORM_LINUX
-#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
+#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
# define CATCH_PLATFORM_WINDOWS
#endif
@@ -145,16 +149,24 @@
# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
_Pragma( "clang diagnostic pop" )
+# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+ _Pragma( "clang diagnostic push" ) \
+ _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" )
+# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \
+ _Pragma( "clang diagnostic pop" )
+
#endif // __clang__
////////////////////////////////////////////////////////////////////////////////
+// Assume that non-Windows platforms support posix signals by default
+#if !defined(CATCH_PLATFORM_WINDOWS)
+ #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
// We know some environments not to support full POSIX signals
-#if defined(__CYGWIN__) || defined(__QNX__)
-
-# if !defined(CATCH_CONFIG_POSIX_SIGNALS)
-# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
-# endif
-
+#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__)
+ #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
#endif
#ifdef __OS400__
@@ -163,6 +175,24 @@
#endif
////////////////////////////////////////////////////////////////////////////////
+// Android somehow still does not support std::to_string
+#if defined(__ANDROID__)
+# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Not all Windows environments support SEH properly
+#if defined(__MINGW32__)
+# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// PS4
+#if defined(__ORBIS__)
+# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
// Cygwin
#ifdef __CYGWIN__
@@ -192,6 +222,13 @@
////////////////////////////////////////////////////////////////////////////////
+// DJGPP
+#ifdef __DJGPP__
+# define CATCH_INTERNAL_CONFIG_NO_WCHAR
+#endif // __DJGPP__
+
+////////////////////////////////////////////////////////////////////////////////
+
// Use of __COUNTER__ is suppressed during code analysis in
// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly
// handled by it.
@@ -204,18 +241,34 @@
#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)
# define CATCH_CONFIG_COUNTER
#endif
-#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH)
+#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH)
# define CATCH_CONFIG_WINDOWS_SEH
#endif
// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.
-#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)
+#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)
# define CATCH_CONFIG_POSIX_SIGNALS
#endif
+// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions.
+#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR)
+# define CATCH_CONFIG_WCHAR
+#endif
-#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_INTERNAL_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
+#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING)
+# define CATCH_CONFIG_CPP11_TO_STRING
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
#endif
+#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
+# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE)
+# define CATCH_CONFIG_NEW_CAPTURE
+#endif
+
#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS
@@ -224,6 +277,10 @@
# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
#endif
+#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS)
+# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS
+# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
+#endif
// end catch_compiler_capabilities.h
#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
@@ -443,16 +500,20 @@
public: // substrings and searches
auto substr( size_type start, size_type size ) const noexcept -> StringRef;
+ // Returns the current start pointer.
+ // Note that the pointer can change when if the StringRef is a substring
+ auto currentData() const noexcept -> char const*;
+
private: // ownership queries - may not be consistent between calls
auto isOwned() const noexcept -> bool;
auto isSubstring() const noexcept -> bool;
- auto data() const noexcept -> char const*;
};
auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string;
auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string;
auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string;
+ auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&;
auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&;
inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef {
@@ -484,24 +545,29 @@
}
struct NameAndTags {
- NameAndTags( StringRef name_ = StringRef(), StringRef tags_ = StringRef() ) noexcept;
+ NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept;
StringRef name;
StringRef tags;
};
struct AutoReg : NonCopyable {
- AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept;
+ AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept;
~AutoReg();
};
} // end namespace Catch
+#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param)
+#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__
+#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__
+#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF
+
#if defined(CATCH_CONFIG_DISABLE)
#define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \
static void TestName()
#define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \
namespace{ \
- struct TestName : ClassName { \
+ struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \
void test(); \
}; \
} \
@@ -513,7 +579,7 @@
#define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
static void TestName(); \
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
- namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
static void TestName()
#define INTERNAL_CATCH_TESTCASE( ... ) \
@@ -529,7 +595,7 @@
#define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
namespace{ \
- struct TestName : ClassName{ \
+ struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \
void test(); \
}; \
Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
@@ -542,7 +608,7 @@
///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
- Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
+ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
// end catch_test_registry.h
@@ -753,14 +819,37 @@
std::string convertUnknownEnumToString( E e );
template<typename T>
- typename std::enable_if<!std::is_enum<T>::value, std::string>::type convertUnstreamable( T const& ) {
+ typename std::enable_if<
+ !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,
+ std::string>::type convertUnstreamable( T const& ) {
return Detail::unprintableString;
}
template<typename T>
- typename std::enable_if<std::is_enum<T>::value, std::string>::type convertUnstreamable( T const& value ) {
+ typename std::enable_if<
+ !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,
+ std::string>::type convertUnstreamable(T const& ex) {
+ return ex.what();
+ }
+
+ template<typename T>
+ typename std::enable_if<
+ std::is_enum<T>::value
+ , std::string>::type convertUnstreamable( T const& value ) {
return convertUnknownEnumToString( value );
}
+#if defined(_MANAGED)
+ //! Convert a CLR string to a utf8 std::string
+ template<typename T>
+ std::string clrReferenceToString( T^ ref ) {
+ if (ref == nullptr)
+ return std::string("null");
+ auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString());
+ cli::pin_ptr<System::Byte> p = &bytes[0];
+ return std::string(reinterpret_cast<char const *>(p), bytes->Length);
+ }
+#endif
+
} // namespace Detail
// If we decide for C++14, change these to enable_if_ts
@@ -771,7 +860,9 @@
typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type
convert(const Fake& value) {
ReusableStringStream rss;
- rss << value;
+ // NB: call using the function-like syntax to avoid ambiguity with
+ // user-defined templated operator<< under clang.
+ rss.operator<<(value);
return rss.str();
}
@@ -779,7 +870,11 @@
static
typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type
convert( const Fake& value ) {
- return Detail::convertUnstreamable( value );
+#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)
+ return Detail::convertUnstreamable(value);
+#else
+ return CATCH_CONFIG_FALLBACK_STRINGIFIER(value);
+#endif
}
};
@@ -797,6 +892,13 @@
return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e));
}
+#if defined(_MANAGED)
+ template <typename T>
+ std::string stringify( T^ e ) {
+ return ::Catch::StringMaker<T^>::convert(e);
+ }
+#endif
+
} // namespace Detail
// Some predefined specializations
@@ -805,10 +907,12 @@
struct StringMaker<std::string> {
static std::string convert(const std::string& str);
};
+#ifdef CATCH_CONFIG_WCHAR
template<>
struct StringMaker<std::wstring> {
static std::string convert(const std::wstring& wstr);
};
+#endif
template<>
struct StringMaker<char const *> {
@@ -818,6 +922,8 @@
struct StringMaker<char *> {
static std::string convert(char * str);
};
+
+#ifdef CATCH_CONFIG_WCHAR
template<>
struct StringMaker<wchar_t const *> {
static std::string convert(wchar_t const * str);
@@ -826,23 +932,26 @@
struct StringMaker<wchar_t *> {
static std::string convert(wchar_t * str);
};
+#endif
+ // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer,
+ // while keeping string semantics?
template<int SZ>
struct StringMaker<char[SZ]> {
- static std::string convert(const char* str) {
+ static std::string convert(char const* str) {
return ::Catch::Detail::stringify(std::string{ str });
}
};
template<int SZ>
struct StringMaker<signed char[SZ]> {
- static std::string convert(const char* str) {
- return ::Catch::Detail::stringify(std::string{ str });
+ static std::string convert(signed char const* str) {
+ return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) });
}
};
template<int SZ>
struct StringMaker<unsigned char[SZ]> {
- static std::string convert(const char* str) {
- return ::Catch::Detail::stringify(std::string{ str });
+ static std::string convert(unsigned char const* str) {
+ return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) });
}
};
@@ -926,6 +1035,15 @@
}
};
+#if defined(_MANAGED)
+ template <typename T>
+ struct StringMaker<T^> {
+ static std::string convert( T^ ref ) {
+ return ::Catch::Detail::clrReferenceToString(ref);
+ }
+ };
+#endif
+
namespace Detail {
template<typename InputIterator>
std::string rangeToString(InputIterator first, InputIterator last) {
@@ -1054,6 +1172,13 @@
!std::is_same<decltype(end(std::declval<T>())), not_this_one>::value;
};
+#if defined(_MANAGED) // Managed types are never ranges
+ template <typename T>
+ struct is_range<T^> {
+ static const bool value = false;
+ };
+#endif
+
template<typename Range>
std::string rangeToString( Range const& range ) {
return ::Catch::Detail::rangeToString( begin( range ), end( range ) );
@@ -1679,7 +1804,7 @@
///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \
do { \
- Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
+ Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \
catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \
INTERNAL_CATCH_REACT( catchAssertionHandler ) \
} while( false )
@@ -1739,6 +1864,7 @@
Totals delta( Totals const& prevTotals ) const;
+ int error = 0;
Counts assertions;
Counts testCases;
};
@@ -1752,17 +1878,20 @@
struct SectionInfo {
SectionInfo
( SourceLineInfo const& _lineInfo,
+ std::string const& _name );
+
+ // Deprecated
+ SectionInfo
+ ( SourceLineInfo const& _lineInfo,
std::string const& _name,
- std::string const& _description = std::string() );
+ std::string const& ) : SectionInfo( _lineInfo, _name ) {}
std::string name;
- std::string description;
+ std::string description; // !Deprecated: this will always be empty
SourceLineInfo lineInfo;
};
struct SectionEndInfo {
- SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds );
-
SectionInfo sectionInfo;
Counts prevAssertions;
double durationInSeconds;
@@ -1816,8 +1945,15 @@
} // end namespace Catch
- #define INTERNAL_CATCH_SECTION( ... ) \
- if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) )
+#define INTERNAL_CATCH_SECTION( ... ) \
+ CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \
+ CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
+
+#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \
+ CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \
+ CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
// end catch_section.h
// start catch_benchmark.h
@@ -2004,6 +2140,8 @@
static Approx custom();
+ Approx operator-() const;
+
template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
Approx operator()( T const& value ) {
Approx approx( static_cast<double>(value) );
@@ -2099,7 +2237,12 @@
double m_scale;
double m_value;
};
-}
+} // end namespace Detail
+
+namespace literals {
+ Detail::Approx operator "" _a(long double val);
+ Detail::Approx operator "" _a(unsigned long long val);
+} // end namespace literals
template<>
struct StringMaker<Catch::Detail::Approx> {
@@ -2330,6 +2473,54 @@
} // namespace Catch
// end catch_matchers_floating.h
+// start catch_matchers_generic.hpp
+
+#include <functional>
+#include <string>
+
+namespace Catch {
+namespace Matchers {
+namespace Generic {
+
+namespace Detail {
+ std::string finalizeDescription(const std::string& desc);
+}
+
+template <typename T>
+class PredicateMatcher : public MatcherBase<T> {
+ std::function<bool(T const&)> m_predicate;
+ std::string m_description;
+public:
+
+ PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr)
+ :m_predicate(std::move(elem)),
+ m_description(Detail::finalizeDescription(descr))
+ {}
+
+ bool match( T const& item ) const override {
+ return m_predicate(item);
+ }
+
+ std::string describe() const override {
+ return m_description;
+ }
+};
+
+} // namespace Generic
+
+ // The following functions create the actual matcher objects.
+ // The user has to explicitly specify type to the function, because
+ // infering std::function<bool(T const&)> is hard (but possible) and
+ // requires a lot of TMP.
+ template<typename T>
+ Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = "") {
+ return Generic::PredicateMatcher<T>(predicate, description);
+ }
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_generic.hpp
// start catch_matchers_string.h
#include <string>
@@ -2696,7 +2887,7 @@
class TestCase : public TestCaseInfo {
public:
- TestCase( ITestInvoker* testCase, TestCaseInfo const& info );
+ TestCase( ITestInvoker* testCase, TestCaseInfo&& info );
TestCase withName( std::string const& _newName ) const;
@@ -2713,8 +2904,7 @@
TestCase makeTestCase( ITestInvoker* testCase,
std::string const& className,
- std::string const& name,
- std::string const& description,
+ NameAndTags const& nameAndTags,
SourceLineInfo const& lineInfo );
}
@@ -2817,7 +3007,7 @@
std::string desc = Detail::getAnnotation( cls, "Description", testCaseName );
const char* className = class_getName( cls );
- getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo("",0) ) );
+ getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) );
noTestMethods++;
}
}
@@ -3144,7 +3334,8 @@
struct WarnAbout { enum What {
Nothing = 0x00,
- NoAssertions = 0x01
+ NoAssertions = 0x01,
+ NoTests = 0x02
}; };
struct ShowDurations { enum OrNot {
@@ -3181,10 +3372,12 @@
virtual bool includeSuccessfulResults() const = 0;
virtual bool shouldDebugBreak() const = 0;
virtual bool warnAboutMissingAssertions() const = 0;
+ virtual bool warnAboutNoTests() const = 0;
virtual int abortAfter() const = 0;
virtual bool showInvisibles() const = 0;
virtual ShowDurations::OrNot showDurations() const = 0;
virtual TestSpec const& testSpec() const = 0;
+ virtual bool hasTestFilters() const = 0;
virtual RunTests::InWhatOrder runOrder() const = 0;
virtual unsigned int rngSeed() const = 0;
virtual int benchmarkResolutionMultiple() const = 0;
@@ -3239,8 +3432,12 @@
std::string outputFilename;
std::string name;
std::string processName;
+#ifndef CATCH_CONFIG_DEFAULT_REPORTER
+#define CATCH_CONFIG_DEFAULT_REPORTER "console"
+#endif
+ std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER;
+#undef CATCH_CONFIG_DEFAULT_REPORTER
- std::vector<std::string> reporterNames;
std::vector<std::string> testsOrTags;
std::vector<std::string> sectionsToRun;
};
@@ -3260,11 +3457,13 @@
bool listReporters() const;
std::string getProcessName() const;
+ std::string const& getReporterName() const;
- std::vector<std::string> const& getReporterNames() const;
+ std::vector<std::string> const& getTestsOrTags() const;
std::vector<std::string> const& getSectionsToRun() const override;
virtual TestSpec const& testSpec() const override;
+ bool hasTestFilters() const override;
bool showHelp() const;
@@ -3274,6 +3473,7 @@
std::string name() const override;
bool includeSuccessfulResults() const override;
bool warnAboutMissingAssertions() const override;
+ bool warnAboutNoTests() const override;
ShowDurations::OrNot showDurations() const override;
RunTests::InWhatOrder runOrder() const override;
unsigned int rngSeed() const override;
@@ -3291,6 +3491,7 @@
std::unique_ptr<IStream const> m_stream;
TestSpec m_testSpec;
+ bool m_hasTestFilters = false;
};
} // end namespace Catch
@@ -3430,6 +3631,7 @@
struct ReporterPreferences {
bool shouldRedirectStdOut = false;
+ bool shouldReportAllAssertions = false;
};
template<typename T>
@@ -3614,8 +3816,6 @@
virtual Listeners const& getListeners() const = 0;
};
- void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter );
-
} // end namespace Catch
// end catch_interfaces_reporter.h
@@ -3623,7 +3823,7 @@
#include <cstring>
#include <cfloat>
#include <cstdio>
-#include <assert.h>
+#include <cassert>
#include <memory>
#include <ostream>
@@ -4522,6 +4722,12 @@
return Approx( 0 );
}
+ Approx Approx::operator-() const {
+ auto temp(*this);
+ temp.m_value = -temp.m_value;
+ return temp;
+ }
+
std::string Approx::toString() const {
ReusableStringStream rss;
rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )";
@@ -4536,6 +4742,15 @@
} // end namespace Detail
+namespace literals {
+ Detail::Approx operator "" _a(long double val) {
+ return Detail::Approx(val);
+ }
+ Detail::Approx operator "" _a(unsigned long long val) {
+ return Detail::Approx(val);
+ }
+} // end namespace literals
+
std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) {
return value.toString();
}
@@ -4638,9 +4853,6 @@
// start catch_fatal_condition.h
-#include <string>
-
-#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
// start catch_windows_h_proxy.h
@@ -4671,16 +4883,7 @@
#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
+#if defined( CATCH_CONFIG_WINDOWS_SEH )
namespace Catch {
@@ -4699,19 +4902,7 @@
} // 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
+#elif defined ( CATCH_CONFIG_POSIX_SIGNALS )
#include <signal.h>
@@ -4720,7 +4911,7 @@
struct FatalConditionHandler {
static bool isSet;
- static struct sigaction oldSigActions[];// [sizeof(signalDefs) / sizeof(SignalDefs)];
+ static struct sigaction oldSigActions[];
static stack_t oldSigStack;
static char altStackMem[];
@@ -4733,9 +4924,15 @@
} // namespace Catch
-# endif // CATCH_CONFIG_POSIX_SIGNALS
+#else
-#endif // not Windows
+namespace Catch {
+ struct FatalConditionHandler {
+ void reset();
+ };
+}
+
+#endif
// end catch_fatal_condition.h
#include <string>
@@ -4815,7 +5012,7 @@
public:
// !TBD We need to do this another way!
- bool aborting() const override;
+ bool aborting() const final;
private:
@@ -4862,9 +5059,11 @@
// end catch_run_context.h
namespace Catch {
- auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& {
- expr.streamReconstructedExpression( os );
- return os;
+ namespace {
+ auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& {
+ expr.streamReconstructedExpression( os );
+ return os;
+ }
}
LazyExpression::LazyExpression( bool isNegated )
@@ -5016,9 +5215,9 @@
expr = m_info.capturedExpression;
else {
expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 );
- expr += m_info.macroName.c_str();
+ expr += m_info.macroName;
expr += "( ";
- expr += m_info.capturedExpression.c_str();
+ expr += m_info.capturedExpression;
expr += " )";
}
return expr;
@@ -5081,7 +5280,7 @@
using StringMatcher = Matchers::Impl::MatcherBase<std::string>;
// This is the general overload that takes a any string matcher
- // There is another overload, in catch_assertinhandler.h/.cpp, that only takes a string and infers
+ // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers
// the Equals matcher (so the header does not mention matchers)
void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) {
std::string exceptionMessage = Catch::translateActiveException();
@@ -5119,7 +5318,7 @@
//
// See https://github.com/philsquared/Clara for more details
-// Clara v1.1.2
+// Clara v1.1.4
#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH
@@ -5130,6 +5329,15 @@
#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH
#endif
+#ifndef CLARA_CONFIG_OPTIONAL_TYPE
+#ifdef __has_include
+#if __has_include(<optional>) && __cplusplus >= 201703L
+#include <optional>
+#define CLARA_CONFIG_OPTIONAL_TYPE std::optional
+#endif
+#endif
+#endif
+
// ----------- #included from clara_textflow.hpp -----------
// TextFlowCpp
@@ -5495,11 +5703,9 @@
std::vector<std::string> m_args;
public:
- Args( int argc, char *argv[] ) {
- m_exeName = argv[0];
- for( int i = 1; i < argc; ++i )
- m_args.push_back( argv[i] );
- }
+ Args( int argc, char const* const* argv )
+ : m_exeName(argv[0]),
+ m_args(argv + 1, argv + argc) {}
Args( std::initializer_list<std::string> args )
: m_exeName( *args.begin() ),
@@ -5685,15 +5891,13 @@
protected:
void enforceOk() const override {
- // !TBD: If no exceptions, std::terminate here or something
- switch( m_type ) {
- case ResultBase::LogicError:
- throw std::logic_error( m_errorMessage );
- case ResultBase::RuntimeError:
- throw std::runtime_error( m_errorMessage );
- case ResultBase::Ok:
- break;
- }
+
+ // Errors shouldn't reach this point, but if they do
+ // the actual error message will be in m_errorMessage
+ assert( m_type != ResultBase::LogicError );
+ assert( m_type != ResultBase::RuntimeError );
+ if( m_type != ResultBase::Ok )
+ std::abort();
}
std::string m_errorMessage; // Only populated if resultType is an error
@@ -5763,6 +5967,16 @@
return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" );
return ParserResult::ok( ParseResultType::Matched );
}
+#ifdef CLARA_CONFIG_OPTIONAL_TYPE
+ template<typename T>
+ inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) -> ParserResult {
+ T temp;
+ auto result = convertInto( source, temp );
+ if( result )
+ target = std::move(temp);
+ return result;
+ }
+#endif // CLARA_CONFIG_OPTIONAL_TYPE
struct NonCopyable {
NonCopyable() = default;
@@ -6366,9 +6580,19 @@
using namespace clara;
auto const setWarning = [&]( std::string const& warning ) {
- if( warning != "NoAssertions" )
+ auto warningSet = [&]() {
+ if( warning == "NoAssertions" )
+ return WarnAbout::NoAssertions;
+
+ if ( warning == "NoTests" )
+ return WarnAbout::NoTests;
+
+ return WarnAbout::Nothing;
+ }();
+
+ if (warningSet == WarnAbout::Nothing)
return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" );
- config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
+ config.warnings = static_cast<WarnAbout::What>( config.warnings | warningSet );
return ParserResult::ok( ParseResultType::Matched );
};
auto const loadTestNamesFromFile = [&]( std::string const& filename ) {
@@ -6466,7 +6690,7 @@
| Opt( config.outputFilename, "filename" )
["-o"]["--out"]
( "output filename" )
- | Opt( config.reporterNames, "name" )
+ | Opt( config.reporterName, "name" )
["-r"]["--reporter"]
( "reporter to use (defaults to console)" )
| Opt( config.name, "name" )
@@ -6569,10 +6793,9 @@
// start catch_enforce.h
#include <stdexcept>
-#include <iosfwd>
#define CATCH_PREPARE_EXCEPTION( type, msg ) \
- type( static_cast<std::ostringstream&&>( Catch::ReusableStringStream().get() << msg ).str() )
+ type( ( Catch::ReusableStringStream() << msg ).str() )
#define CATCH_INTERNAL_ERROR( msg ) \
throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg);
#define CATCH_ERROR( msg ) \
@@ -6587,12 +6810,16 @@
: m_data( data ),
m_stream( openStream() )
{
- if( !data.testsOrTags.empty() ) {
- TestSpecParser parser( ITagAliasRegistry::get() );
+ TestSpecParser parser(ITagAliasRegistry::get());
+ if (data.testsOrTags.empty()) {
+ parser.parse("~[.]"); // All not hidden tests
+ }
+ else {
+ m_hasTestFilters = true;
for( auto const& testOrTags : data.testsOrTags )
parser.parse( testOrTags );
- m_testSpec = parser.testSpec();
}
+ m_testSpec = parser.testSpec();
}
std::string const& Config::getFilename() const {
@@ -6605,11 +6832,13 @@
bool Config::listReporters() const { return m_data.listReporters; }
std::string Config::getProcessName() const { return m_data.processName; }
+ std::string const& Config::getReporterName() const { return m_data.reporterName; }
- std::vector<std::string> const& Config::getReporterNames() const { return m_data.reporterNames; }
+ std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }
TestSpec const& Config::testSpec() const { return m_testSpec; }
+ bool Config::hasTestFilters() const { return m_hasTestFilters; }
bool Config::showHelp() const { return m_data.showHelp; }
@@ -6618,7 +6847,8 @@
std::ostream& Config::stream() const { return m_stream->stream(); }
std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; }
- bool Config::warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; }
+ bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); }
+ bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); }
ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; }
RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; }
unsigned int Config::rngSeed() const { return m_data.rngSeed; }
@@ -6802,7 +7032,12 @@
#ifdef CATCH_PLATFORM_MAC
!isDebuggerActive() &&
#endif
- isatty(STDOUT_FILENO);
+#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__))
+ isatty(STDOUT_FILENO)
+#else
+ false
+#endif
+ ;
}
IColourImpl* platformColourInstance() {
ErrnoGuard guard;
@@ -6936,13 +7171,16 @@
::OutputDebugStringA( text.c_str() );
}
}
+
#else
+
namespace Catch {
void writeToDebugConsole( std::string const& text ) {
// !TBD: Need a version for Mac/ XCode and other IDEs
Catch::cout() << text;
}
}
+
#endif // Platform
// end catch_debug_console.cpp
// start catch_debugger.cpp
@@ -7165,24 +7403,18 @@
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
-#if (defined(CATCH_PLATFORM_WINDOWS) && defined(CATCH_CONFIG_WINDOWS_SEH)) || defined(CATCH_CONFIG_POSIX_SIGNALS)
+#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
+
namespace {
// Report the error condition
void reportFatal( char const * const message ) {
Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );
}
}
-#endif
-#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+#endif // signals/SEH handling
-# if !defined ( CATCH_CONFIG_WINDOWS_SEH )
-
-namespace Catch {
- void FatalConditionHandler::reset() {}
-}
-
-# else // CATCH_CONFIG_WINDOWS_SEH is defined
+#if defined( CATCH_CONFIG_WINDOWS_SEH )
namespace Catch {
struct SignalDefs { DWORD id; const char* name; };
@@ -7222,7 +7454,6 @@
void FatalConditionHandler::reset() {
if (isSet) {
- // Unregister handler and restore the old guarantee
RemoveVectoredExceptionHandler(exceptionHandlerHandle);
SetThreadStackGuarantee(&guaranteeSize);
exceptionHandlerHandle = nullptr;
@@ -7240,19 +7471,7 @@
} // namespace Catch
-# endif // CATCH_CONFIG_WINDOWS_SEH
-
-#else // Not Windows - assumed to be POSIX compatible //////////////////////////
-
-# if !defined(CATCH_CONFIG_POSIX_SIGNALS)
-
-namespace Catch {
- void FatalConditionHandler::reset() {}
-}
-
-# else // CATCH_CONFIG_POSIX_SIGNALS is defined
-
-#include <signal.h>
+#elif defined( CATCH_CONFIG_POSIX_SIGNALS )
namespace Catch {
@@ -7260,6 +7479,11 @@
int id;
const char* name;
};
+
+ // 32kb for the alternate stack seems to be sufficient. However, this value
+ // is experimentally determined, so that's not guaranteed.
+ constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ;
+
static SignalDefs signalDefs[] = {
{ SIGINT, "SIGINT - Terminal interrupt signal" },
{ SIGILL, "SIGILL - Illegal instruction signal" },
@@ -7286,7 +7510,7 @@
isSet = true;
stack_t sigStack;
sigStack.ss_sp = altStackMem;
- sigStack.ss_size = SIGSTKSZ;
+ sigStack.ss_size = sigStackSize;
sigStack.ss_flags = 0;
sigaltstack(&sigStack, &oldSigStack);
struct sigaction sa = { };
@@ -7317,13 +7541,17 @@
bool FatalConditionHandler::isSet = false;
struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {};
stack_t FatalConditionHandler::oldSigStack = {};
- char FatalConditionHandler::altStackMem[SIGSTKSZ] = {};
+ char FatalConditionHandler::altStackMem[sigStackSize] = {};
} // namespace Catch
-# endif // CATCH_CONFIG_POSIX_SIGNALS
+#else
-#endif // not Windows
+namespace Catch {
+ void FatalConditionHandler::reset() {}
+}
+
+#endif // signals/SEH handling
#if defined(__GNUC__)
# pragma GCC diagnostic pop
@@ -7357,16 +7585,21 @@
// end catch_interfaces_registry_hub.cpp
// start catch_interfaces_reporter.cpp
-// start catch_reporter_multi.h
+// start catch_reporter_listening.h
namespace Catch {
- class MultipleReporters : public IStreamingReporter {
+ class ListeningReporter : public IStreamingReporter {
using Reporters = std::vector<IStreamingReporterPtr>;
- Reporters m_reporters;
+ Reporters m_listeners;
+ IStreamingReporterPtr m_reporter = nullptr;
+ ReporterPreferences m_preferences;
public:
- void add( IStreamingReporterPtr&& reporter );
+ ListeningReporter();
+
+ void addListener( IStreamingReporterPtr&& listener );
+ void addReporter( IStreamingReporterPtr&& reporter );
public: // IStreamingReporter
@@ -7399,7 +7632,7 @@
} // end namespace Catch
-// end catch_reporter_multi.h
+// end catch_reporter_listening.h
namespace Catch {
ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig )
@@ -7500,27 +7733,6 @@
IReporterFactory::~IReporterFactory() = default;
IReporterRegistry::~IReporterRegistry() = default;
- void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ) {
-
- if( !existingReporter ) {
- existingReporter = std::move( additionalReporter );
- return;
- }
-
- MultipleReporters* multi = nullptr;
-
- if( existingReporter->isMulti() ) {
- multi = static_cast<MultipleReporters*>( existingReporter.get() );
- }
- else {
- auto newMulti = std::unique_ptr<MultipleReporters>( new MultipleReporters );
- newMulti->add( std::move( existingReporter ) );
- multi = newMulti.get();
- existingReporter = std::move( newMulti );
- }
- multi->add( std::move( additionalReporter ) );
- }
-
} // end namespace Catch
// end catch_interfaces_reporter.cpp
// start catch_interfaces_runner.cpp
@@ -7605,11 +7817,10 @@
std::size_t listTests( Config const& config ) {
TestSpec testSpec = config.testSpec();
- if( config.testSpec().hasFilters() )
+ if( config.hasTestFilters() )
Catch::cout() << "Matching test cases:\n";
else {
Catch::cout() << "All available test cases:\n";
- testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
}
auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
@@ -7631,7 +7842,7 @@
Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n";
}
- if( !config.testSpec().hasFilters() )
+ if( !config.hasTestFilters() )
Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl;
else
Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl;
@@ -7640,8 +7851,6 @@
std::size_t listTestsNamesOnly( Config const& config ) {
TestSpec testSpec = config.testSpec();
- if( !config.testSpec().hasFilters() )
- testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
std::size_t matchedTests = 0;
std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
for( auto const& testCaseInfo : matchedTestCases ) {
@@ -7671,11 +7880,10 @@
std::size_t listTags( Config const& config ) {
TestSpec testSpec = config.testSpec();
- if( config.testSpec().hasFilters() )
+ if( config.hasTestFilters() )
Catch::cout() << "Tags for matching test cases:\n";
else {
Catch::cout() << "All available tags:\n";
- testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
}
std::map<std::string, TagInfo> tagCounts;
@@ -7766,6 +7974,24 @@
// end catch_matchers.cpp
// start catch_matchers_floating.cpp
+// start catch_to_string.hpp
+
+#include <string>
+
+namespace Catch {
+ template <typename T>
+ std::string to_string(T const& t) {
+#if defined(CATCH_CONFIG_CPP11_TO_STRING)
+ return std::to_string(t);
+#else
+ ReusableStringStream rss;
+ rss << t;
+ return rss.str();
+#endif
+ }
+} // end namespace Catch
+
+// end catch_to_string.hpp
#include <cstdlib>
#include <cstdint>
#include <cstring>
@@ -7845,7 +8071,7 @@
// 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);
+ return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
}
std::string WithinAbsMatcher::describe() const {
@@ -7871,7 +8097,7 @@
}
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" : "");
+ return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : "");
}
}// namespace Floating
@@ -7892,6 +8118,16 @@
} // namespace Catch
// end catch_matchers_floating.cpp
+// start catch_matchers_generic.cpp
+
+std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) {
+ if (desc.empty()) {
+ return "matches undescribed predicate";
+ } else {
+ return "matches predicate: \"" + desc + '"';
+ }
+}
+// end catch_matchers_generic.cpp
// start catch_matchers_string.cpp
#include <regex>
@@ -8048,58 +8284,260 @@
}
} // end namespace Catch
// end catch_message.cpp
+// start catch_output_redirect.cpp
+
+// start catch_output_redirect.h
+#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+
+#include <cstdio>
+#include <iosfwd>
+#include <string>
+
+namespace Catch {
+
+ class RedirectedStream {
+ std::ostream& m_originalStream;
+ std::ostream& m_redirectionStream;
+ std::streambuf* m_prevBuf;
+
+ public:
+ RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream );
+ ~RedirectedStream();
+ };
+
+ class RedirectedStdOut {
+ ReusableStringStream m_rss;
+ RedirectedStream m_cout;
+ public:
+ RedirectedStdOut();
+ auto str() const -> std::string;
+ };
+
+ // StdErr has two constituent streams in C++, std::cerr and std::clog
+ // This means that we need to redirect 2 streams into 1 to keep proper
+ // order of writes
+ class RedirectedStdErr {
+ ReusableStringStream m_rss;
+ RedirectedStream m_cerr;
+ RedirectedStream m_clog;
+ public:
+ RedirectedStdErr();
+ auto str() const -> std::string;
+ };
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+
+ // Windows's implementation of std::tmpfile is terrible (it tries
+ // to create a file inside system folder, thus requiring elevated
+ // privileges for the binary), so we have to use tmpnam(_s) and
+ // create the file ourselves there.
+ class TempFile {
+ public:
+ TempFile(TempFile const&) = delete;
+ TempFile& operator=(TempFile const&) = delete;
+ TempFile(TempFile&&) = delete;
+ TempFile& operator=(TempFile&&) = delete;
+
+ TempFile();
+ ~TempFile();
+
+ std::FILE* getFile();
+ std::string getContents();
+
+ private:
+ std::FILE* m_file = nullptr;
+ #if defined(_MSC_VER)
+ char m_buffer[L_tmpnam] = { 0 };
+ #endif
+ };
+
+ class OutputRedirect {
+ public:
+ OutputRedirect(OutputRedirect const&) = delete;
+ OutputRedirect& operator=(OutputRedirect const&) = delete;
+ OutputRedirect(OutputRedirect&&) = delete;
+ OutputRedirect& operator=(OutputRedirect&&) = delete;
+
+ OutputRedirect(std::string& stdout_dest, std::string& stderr_dest);
+ ~OutputRedirect();
+
+ private:
+ int m_originalStdout = -1;
+ int m_originalStderr = -1;
+ TempFile m_stdoutFile;
+ TempFile m_stderrFile;
+ std::string& m_stdoutDest;
+ std::string& m_stderrDest;
+ };
+
+#endif
+
+} // end namespace Catch
+
+#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+// end catch_output_redirect.h
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+ #if defined(_MSC_VER)
+ #include <io.h> //_dup and _dup2
+ #define dup _dup
+ #define dup2 _dup2
+ #define fileno _fileno
+ #else
+ #include <unistd.h> // dup and dup2
+ #endif
+#endif
+
+namespace Catch {
+
+ RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream )
+ : m_originalStream( originalStream ),
+ m_redirectionStream( redirectionStream ),
+ m_prevBuf( m_originalStream.rdbuf() )
+ {
+ m_originalStream.rdbuf( m_redirectionStream.rdbuf() );
+ }
+
+ RedirectedStream::~RedirectedStream() {
+ m_originalStream.rdbuf( m_prevBuf );
+ }
+
+ RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {}
+ auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }
+
+ RedirectedStdErr::RedirectedStdErr()
+ : m_cerr( Catch::cerr(), m_rss.get() ),
+ m_clog( Catch::clog(), m_rss.get() )
+ {}
+ auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+
+#if defined(_MSC_VER)
+ TempFile::TempFile() {
+ if (tmpnam_s(m_buffer)) {
+ throw std::runtime_error("Could not get a temp filename");
+ }
+ if (fopen_s(&m_file, m_buffer, "w")) {
+ char buffer[100];
+ if (strerror_s(buffer, errno)) {
+ throw std::runtime_error("Could not translate errno to string");
+ }
+ throw std::runtime_error("Could not open the temp file: " + std::string(m_buffer) + buffer);
+ }
+ }
+#else
+ TempFile::TempFile() {
+ m_file = std::tmpfile();
+ if (!m_file) {
+ throw std::runtime_error("Could not create a temp file.");
+ }
+ }
+
+#endif
+
+ TempFile::~TempFile() {
+ // TBD: What to do about errors here?
+ std::fclose(m_file);
+ // We manually create the file on Windows only, on Linux
+ // it will be autodeleted
+#if defined(_MSC_VER)
+ std::remove(m_buffer);
+#endif
+ }
+
+ FILE* TempFile::getFile() {
+ return m_file;
+ }
+
+ std::string TempFile::getContents() {
+ std::stringstream sstr;
+ char buffer[100] = {};
+ std::rewind(m_file);
+ while (std::fgets(buffer, sizeof(buffer), m_file)) {
+ sstr << buffer;
+ }
+ return sstr.str();
+ }
+
+ OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) :
+ m_originalStdout(dup(1)),
+ m_originalStderr(dup(2)),
+ m_stdoutDest(stdout_dest),
+ m_stderrDest(stderr_dest) {
+ dup2(fileno(m_stdoutFile.getFile()), 1);
+ dup2(fileno(m_stderrFile.getFile()), 2);
+ }
+
+ OutputRedirect::~OutputRedirect() {
+ Catch::cout() << std::flush;
+ fflush(stdout);
+ // Since we support overriding these streams, we flush cerr
+ // even though std::cerr is unbuffered
+ Catch::cerr() << std::flush;
+ Catch::clog() << std::flush;
+ fflush(stderr);
+
+ dup2(m_originalStdout, 1);
+ dup2(m_originalStderr, 2);
+
+ m_stdoutDest += m_stdoutFile.getContents();
+ m_stderrDest += m_stderrFile.getContents();
+ }
+
+#endif // CATCH_CONFIG_NEW_CAPTURE
+
+} // namespace Catch
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+ #if defined(_MSC_VER)
+ #undef dup
+ #undef dup2
+ #undef fileno
+ #endif
+#endif
+// end catch_output_redirect.cpp
// start catch_random_number_generator.cpp
// start catch_random_number_generator.h
#include <algorithm>
+#include <random>
namespace Catch {
struct IConfig;
+ std::mt19937& rng();
void seedRng( IConfig const& config );
-
unsigned int rngSeed();
- struct RandomNumberGenerator {
- using result_type = unsigned int;
-
- static constexpr result_type (min)() { return 0; }
- static constexpr result_type (max)() { return 1000000; }
-
- result_type operator()( result_type n ) const;
- result_type operator()() const;
-
- template<typename V>
- static void shuffle( V& vector ) {
- RandomNumberGenerator rng;
- std::shuffle( vector.begin(), vector.end(), rng );
- }
- };
-
}
// end catch_random_number_generator.h
-#include <cstdlib>
-
namespace Catch {
- void seedRng( IConfig const& config ) {
- if( config.rngSeed() != 0 )
- std::srand( config.rngSeed() );
+ std::mt19937& rng() {
+ static std::mt19937 s_rng;
+ return s_rng;
}
+
+ void seedRng( IConfig const& config ) {
+ if( config.rngSeed() != 0 ) {
+ std::srand( config.rngSeed() );
+ rng().seed( config.rngSeed() );
+ }
+ }
+
unsigned int rngSeed() {
return getCurrentContext().getConfig()->rngSeed();
}
-
- RandomNumberGenerator::result_type RandomNumberGenerator::operator()( result_type n ) const {
- return std::rand() % n;
- }
- RandomNumberGenerator::result_type RandomNumberGenerator::operator()() const {
- return std::rand() % (max)();
- }
-
}
// end catch_random_number_generator.cpp
// start catch_registry_hub.cpp
@@ -8151,7 +8589,7 @@
void invoke() const override;
};
- std::string extractClassName( std::string const& classOrQualifiedMethodName );
+ std::string extractClassName( StringRef const& classOrQualifiedMethodName );
///////////////////////////////////////////////////////////////////////////
@@ -8376,54 +8814,13 @@
namespace Catch {
- class RedirectedStream {
- std::ostream& m_originalStream;
- std::ostream& m_redirectionStream;
- std::streambuf* m_prevBuf;
-
- public:
- 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 );
- }
- };
-
- 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
- class RedirectedStdErr {
- ReusableStringStream m_rss;
- RedirectedStream m_cerr;
- RedirectedStream m_clog;
- public:
- RedirectedStdErr()
- : m_cerr( Catch::cerr(), m_rss.get() ),
- m_clog( Catch::clog(), m_rss.get() )
- {}
- auto str() const -> std::string { return m_rss.str(); }
- };
-
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_includeSuccessfulResults( m_config->includeSuccessfulResults() )
+ m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal },
+ m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )
{
m_context.setRunner(this);
m_context.setConfig(m_config);
@@ -8449,7 +8846,7 @@
std::string redirectedCout;
std::string redirectedCerr;
- TestCaseInfo testInfo = testCase.getTestCaseInfo();
+ auto const& testInfo = testCase.getTestCaseInfo();
m_reporter->testCaseStarting(testInfo);
@@ -8613,7 +9010,7 @@
// Recreate section for test case (as we will lose the one that was in scope)
auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
- SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description);
+ SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
Counts assertions;
assertions.failed = 1;
@@ -8651,25 +9048,31 @@
void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) {
auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
- SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description);
+ SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
m_reporter->sectionStarting(testCaseSection);
Counts prevAssertions = m_totals.assertions;
double duration = 0;
m_shouldReportUnexpected = true;
- m_lastAssertionInfo = { "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal };
+ m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal };
seedRng(*m_config);
Timer timer;
try {
if (m_reporter->getPreferences().shouldRedirectStdOut) {
+#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
RedirectedStdOut redirectedStdOut;
RedirectedStdErr redirectedStdErr;
+
timer.start();
invokeActiveTestCase();
redirectedCout += redirectedStdOut.str();
redirectedCerr += redirectedStdErr.str();
-
+#else
+ OutputRedirect r(redirectedCout, redirectedCerr);
+ timer.start();
+ invokeActiveTestCase();
+#endif
} else {
timer.start();
invokeActiveTestCase();
@@ -8840,7 +9243,7 @@
Section::~Section() {
if( m_sectionIncluded ) {
- SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() );
+ SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() };
if( uncaught_exceptions() )
getResultCapture().sectionEndedEarly( endInfo );
else
@@ -8861,17 +9264,11 @@
SectionInfo::SectionInfo
( SourceLineInfo const& _lineInfo,
- std::string const& _name,
- std::string const& _description )
+ std::string const& _name )
: name( _name ),
- description( _description ),
lineInfo( _lineInfo )
{}
- SectionEndInfo::SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds )
- : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds )
- {}
-
} // end namespace Catch
// end catch_section_info.cpp
// start catch_session.cpp
@@ -8891,12 +9288,12 @@
void showHelp() const;
void libIdentify();
- int applyCommandLine( int argc, char* argv[] );
+ int applyCommandLine( int argc, char const * const * argv );
void useConfigData( ConfigData const& configData );
int run( int argc, char* argv[] );
- #if defined(WIN32) && defined(UNICODE)
+ #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE)
int run( int argc, wchar_t* const argv[] );
#endif
int run();
@@ -8963,32 +9360,25 @@
return reporter;
}
-#ifndef CATCH_CONFIG_DEFAULT_REPORTER
-#define CATCH_CONFIG_DEFAULT_REPORTER "console"
-#endif
-
IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) {
- auto const& reporterNames = config->getReporterNames();
- if (reporterNames.empty())
- return createReporter(CATCH_CONFIG_DEFAULT_REPORTER, config);
+ if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) {
+ return createReporter(config->getReporterName(), config);
+ }
- IStreamingReporterPtr reporter;
- for (auto const& name : reporterNames)
- addReporter(reporter, createReporter(name, config));
- return reporter;
- }
+ auto multi = std::unique_ptr<ListeningReporter>(new ListeningReporter);
-#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)));
+ for (auto const& listener : listeners) {
+ multi->addListener(listener->create(Catch::ReporterConfig(config)));
+ }
+ multi->addReporter(createReporter(config->getReporterName(), config));
+ return std::move(multi);
}
Catch::Totals runTests(std::shared_ptr<Config> const& config) {
- IStreamingReporterPtr reporter = makeReporter(config);
- addListeners(reporter, config);
+ // FixMe: Add listeners in order first, then add reporters.
+
+ auto reporter = makeReporter(config);
RunContext context(config, std::move(reporter));
@@ -8997,8 +9387,6 @@
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) {
@@ -9008,6 +9396,20 @@
context.reporter().skipTest(testCase);
}
+ if (config->warnAboutNoTests() && totals.testCases.total() == 0) {
+ ReusableStringStream testConfig;
+
+ bool first = true;
+ for (const auto& input : config->getTestsOrTags()) {
+ if (!first) { testConfig << ' '; }
+ first = false;
+ testConfig << input;
+ }
+
+ context.reporter().noMatchingTestCases(testConfig.str());
+ totals.error = -1;
+ }
+
context.testGroupEnded(config->name(), totals, 1, 1);
return totals;
}
@@ -9047,7 +9449,7 @@
if ( !exceptions.empty() ) {
m_startupExceptions = true;
Colour colourGuard( Colour::Red );
- Catch::cerr() << "Errors occured during startup!" << '\n';
+ Catch::cerr() << "Errors occurred during startup!" << '\n';
// iterate over all exceptions and notify user
for ( const auto& ex_ptr : exceptions ) {
try {
@@ -9079,7 +9481,7 @@
<< std::left << std::setw(16) << "version: " << libraryVersion() << std::endl;
}
- int Session::applyCommandLine( int argc, char* argv[] ) {
+ int Session::applyCommandLine( int argc, char const * const * argv ) {
if( m_startupExceptions )
return 1;
@@ -9116,7 +9518,7 @@
return returnCode;
}
-#if defined(WIN32) && defined(UNICODE)
+#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE)
int Session::run( int argc, wchar_t* const argv[] ) {
char **utf8Argv = new char *[ argc ];
@@ -9187,10 +9589,11 @@
if( Option<std::size_t> listed = list( config() ) )
return static_cast<int>( *listed );
+ auto totals = runTests( m_config );
// Note that on unices only the lower 8 bits are usually used, clamping
// the return value to 255 prevents false negative when some multiple
// of 256 tests has failed
- return (std::min)( MaxExitCode, static_cast<int>( runTests( m_config ).assertions.failed ) );
+ return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed)));
}
catch( std::exception& ex ) {
Catch::cerr() << ex.what() << std::endl;
@@ -9426,6 +9829,12 @@
namespace Catch {
+ namespace {
+ char toLowerCh(char c) {
+ return static_cast<char>( std::tolower( c ) );
+ }
+ }
+
bool startsWith( std::string const& s, std::string const& prefix ) {
return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());
}
@@ -9441,9 +9850,6 @@
bool contains( std::string const& s, std::string const& infix ) {
return s.find( infix ) != std::string::npos;
}
- char toLowerCh(char c) {
- return static_cast<char>( std::tolower( c ) );
- }
void toLowerInPlace( std::string& s ) {
std::transform( s.begin(), s.end(), s.begin(), toLowerCh );
}
@@ -9525,7 +9931,7 @@
const_cast<StringRef*>( this )->takeOwnership();
return m_start;
}
- auto StringRef::data() const noexcept -> char const* {
+ auto StringRef::currentData() const noexcept -> char const* {
return m_start;
}
@@ -9594,7 +10000,12 @@
}
auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& {
- return os << str.c_str();
+ return os.write(str.currentData(), str.size());
+ }
+
+ auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& {
+ lhs.append(rhs.currentData(), rhs.size());
+ return lhs;
}
} // namespace Catch
@@ -9680,37 +10091,38 @@
namespace Catch {
- TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
- if( startsWith( tag, '.' ) ||
- tag == "!hide" )
- return TestCaseInfo::IsHidden;
- else if( tag == "!throws" )
- return TestCaseInfo::Throws;
- else if( tag == "!shouldfail" )
- return TestCaseInfo::ShouldFail;
- else if( tag == "!mayfail" )
- return TestCaseInfo::MayFail;
- else if( tag == "!nonportable" )
- return TestCaseInfo::NonPortable;
- else if( tag == "!benchmark" )
- return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden );
- else
- return TestCaseInfo::None;
- }
- bool isReservedTag( std::string const& tag ) {
- return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] );
- }
- void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
- CATCH_ENFORCE( !isReservedTag(tag),
- "Tag name: [" << tag << "] is not allowed.\n"
- << "Tag names starting with non alpha-numeric characters are reserved\n"
- << _lineInfo );
+ namespace {
+ TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
+ if( startsWith( tag, '.' ) ||
+ tag == "!hide" )
+ return TestCaseInfo::IsHidden;
+ else if( tag == "!throws" )
+ return TestCaseInfo::Throws;
+ else if( tag == "!shouldfail" )
+ return TestCaseInfo::ShouldFail;
+ else if( tag == "!mayfail" )
+ return TestCaseInfo::MayFail;
+ else if( tag == "!nonportable" )
+ return TestCaseInfo::NonPortable;
+ else if( tag == "!benchmark" )
+ return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden );
+ else
+ return TestCaseInfo::None;
+ }
+ bool isReservedTag( std::string const& tag ) {
+ return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) );
+ }
+ void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
+ CATCH_ENFORCE( !isReservedTag(tag),
+ "Tag name: [" << tag << "] is not allowed.\n"
+ << "Tag names starting with non alpha-numeric characters are reserved\n"
+ << _lineInfo );
+ }
}
TestCase makeTestCase( ITestInvoker* _testCase,
std::string const& _className,
- std::string const& _name,
- std::string const& _descOrTags,
+ NameAndTags const& nameAndTags,
SourceLineInfo const& _lineInfo )
{
bool isHidden = false;
@@ -9719,6 +10131,7 @@
std::vector<std::string> tags;
std::string desc, tag;
bool inTag = false;
+ std::string _descOrTags = nameAndTags.tags;
for (char c : _descOrTags) {
if( !inTag ) {
if( c == '[' )
@@ -9746,8 +10159,8 @@
tags.push_back( "." );
}
- TestCaseInfo info( _name, _className, desc, tags, _lineInfo );
- return TestCase( _testCase, info );
+ TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo );
+ return TestCase( _testCase, std::move(info) );
}
void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) {
@@ -9807,7 +10220,7 @@
return ret;
}
- TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {}
+ TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {}
TestCase TestCase::withName( std::string const& _newName ) const {
TestCase other( *this );
@@ -9852,7 +10265,7 @@
break;
case RunTests::InRandomOrder:
seedRng( config );
- RandomNumberGenerator::shuffle( sorted );
+ std::shuffle( sorted.begin(), sorted.end(), rng() );
break;
case RunTests::InDeclarationOrder:
// already in declaration order
@@ -9918,7 +10331,7 @@
m_testAsFunction();
}
- std::string extractClassName( std::string const& classOrQualifiedMethodName ) {
+ std::string extractClassName( StringRef const& classOrQualifiedMethodName ) {
std::string className = classOrQualifiedMethodName;
if( startsWith( className, '&' ) )
{
@@ -9936,7 +10349,7 @@
// start catch_test_case_tracker.cpp
#include <algorithm>
-#include <assert.h>
+#include <cassert>
#include <stdexcept>
#include <memory>
#include <sstream>
@@ -9995,8 +10408,8 @@
TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {}
bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const {
return
- tracker->nameAndLocation().name == m_nameAndLocation.name &&
- tracker->nameAndLocation().location == m_nameAndLocation.location;
+ tracker->nameAndLocation().location == m_nameAndLocation.location &&
+ tracker->nameAndLocation().name == m_nameAndLocation.name;
}
TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
@@ -10215,17 +10628,16 @@
return new(std::nothrow) TestInvokerAsFunction( testAsFunction );
}
- NameAndTags::NameAndTags( StringRef name_ , StringRef tags_ ) noexcept : name( name_ ), tags( tags_ ) {}
+ NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {}
- AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept {
+ AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept {
try {
getMutableRegistryHub()
.registerTest(
makeTestCase(
invoker,
extractClassName( classOrMethod ),
- nameAndTags.name,
- nameAndTags.tags,
+ nameAndTags,
lineInfo));
} catch (...) {
// Do not throw when constructing global objects, instead register the exception to be processed later
@@ -10373,32 +10785,44 @@
#include <chrono>
+static const uint64_t nanosecondsInSecond = 1000000000;
+
namespace Catch {
auto getCurrentNanosecondsSinceEpoch() -> uint64_t {
return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
}
- auto estimateClockResolution() -> uint64_t {
- uint64_t sum = 0;
- static const uint64_t iterations = 1000000;
+ namespace {
+ auto estimateClockResolution() -> uint64_t {
+ uint64_t sum = 0;
+ static const uint64_t iterations = 1000000;
- for( std::size_t i = 0; i < iterations; ++i ) {
+ auto startTime = getCurrentNanosecondsSinceEpoch();
- uint64_t ticks;
- uint64_t baseTicks = getCurrentNanosecondsSinceEpoch();
- do {
- ticks = getCurrentNanosecondsSinceEpoch();
+ for( std::size_t i = 0; i < iterations; ++i ) {
+
+ uint64_t ticks;
+ uint64_t baseTicks = getCurrentNanosecondsSinceEpoch();
+ do {
+ ticks = getCurrentNanosecondsSinceEpoch();
+ } while( ticks == baseTicks );
+
+ auto delta = ticks - baseTicks;
+ sum += delta;
+
+ // If we have been calibrating for over 3 seconds -- the clock
+ // is terrible and we should move on.
+ // TBD: How to signal that the measured resolution is probably wrong?
+ if (ticks > startTime + 3 * nanosecondsInSecond) {
+ return sum / i;
+ }
}
- while( ticks == baseTicks );
- auto delta = ticks - baseTicks;
- sum += delta;
+ // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers
+ // - and potentially do more iterations if there's a high variance.
+ return sum/iterations;
}
-
- // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers
- // - and potentially do more iterations if there's a high variance.
- return sum/iterations;
}
auto getEstimatedClockResolution() -> uint64_t {
static auto s_resolution = estimateClockResolution();
@@ -10529,6 +10953,7 @@
return s;
}
+#ifdef CATCH_CONFIG_WCHAR
std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) {
std::string s;
s.reserve(wstr.size());
@@ -10537,6 +10962,7 @@
}
return ::Catch::Detail::stringify(s);
}
+#endif
std::string StringMaker<char const*>::convert(char const* str) {
if (str) {
@@ -10552,6 +10978,7 @@
return{ "{null string}" };
}
}
+#ifdef CATCH_CONFIG_WCHAR
std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) {
if (str) {
return ::Catch::Detail::stringify(std::wstring{ str });
@@ -10566,6 +10993,7 @@
return{ "{null string}" };
}
}
+#endif
std::string StringMaker<int>::convert(int value) {
return ::Catch::Detail::stringify(static_cast<long long>(value));
@@ -10638,8 +11066,8 @@
std::string ratio_string<std::atto>::symbol() { return "a"; }
std::string ratio_string<std::femto>::symbol() { return "f"; }
-std::string ratio_string<std::pico>::symbol() { return "p"; }
-std::string ratio_string<std::nano>::symbol() { return "n"; }
+std::string ratio_string<std::pico>::symbol() { return "p"; }
+std::string ratio_string<std::nano>::symbol() { return "n"; }
std::string ratio_string<std::micro>::symbol() { return "u"; }
std::string ratio_string<std::milli>::symbol() { return "m"; }
@@ -10751,7 +11179,7 @@
}
Version const& libraryVersion() {
- static Version version( 2, 1, 2, "", 0 );
+ static Version version( 2, 3, 0, "", 0 );
return version;
}
@@ -10802,49 +11230,141 @@
#include <iomanip>
+using uchar = unsigned char;
+
namespace Catch {
+namespace {
+
+ size_t trailingBytes(unsigned char c) {
+ if ((c & 0xE0) == 0xC0) {
+ return 2;
+ }
+ if ((c & 0xF0) == 0xE0) {
+ return 3;
+ }
+ if ((c & 0xF8) == 0xF0) {
+ return 4;
+ }
+ CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+ }
+
+ uint32_t headerValue(unsigned char c) {
+ if ((c & 0xE0) == 0xC0) {
+ return c & 0x1F;
+ }
+ if ((c & 0xF0) == 0xE0) {
+ return c & 0x0F;
+ }
+ if ((c & 0xF8) == 0xF0) {
+ return c & 0x07;
+ }
+ CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+ }
+
+ void hexEscapeChar(std::ostream& os, unsigned char c) {
+ os << "\\x"
+ << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+ << static_cast<int>(c);
+ }
+
+} // anonymous namespace
+
XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
: m_str( str ),
m_forWhat( forWhat )
{}
void XmlEncode::encodeTo( std::ostream& os ) const {
-
// Apostrophe escaping not necessary if we always use " to write attributes
// (see: http://www.w3.org/TR/xml/#syntax)
- for( std::size_t i = 0; i < m_str.size(); ++ i ) {
- char c = m_str[i];
- switch( c ) {
- case '<': os << "<"; break;
- case '&': os << "&"; break;
+ for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
+ uchar c = m_str[idx];
+ switch (c) {
+ case '<': os << "<"; break;
+ case '&': os << "&"; break;
- case '>':
- // See: http://www.w3.org/TR/xml/#syntax
- if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' )
- os << ">";
- else
- os << c;
+ case '>':
+ // See: http://www.w3.org/TR/xml/#syntax
+ if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
+ os << ">";
+ else
+ os << c;
+ break;
+
+ case '\"':
+ if (m_forWhat == ForAttributes)
+ os << """;
+ else
+ os << c;
+ break;
+
+ default:
+ // Check for control characters and invalid utf-8
+
+ // Escape control characters in standard ascii
+ // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+ if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
+ hexEscapeChar(os, c);
break;
+ }
- case '\"':
- if( m_forWhat == ForAttributes )
- os << """;
- else
- os << c;
+ // Plain ASCII: Write it to stream
+ if (c < 0x7F) {
+ os << c;
break;
+ }
- default:
- // Escape control chars - based on contribution by @espenalb in PR #465 and
- // by @mrpi PR #588
- if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) {
- // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
- os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
- << static_cast<int>( c );
- }
- else
- os << c;
+ // UTF-8 territory
+ // Check if the encoding is valid and if it is not, hex escape bytes.
+ // Important: We do not check the exact decoded values for validity, only the encoding format
+ // First check that this bytes is a valid lead byte:
+ // This means that it is not encoded as 1111 1XXX
+ // Or as 10XX XXXX
+ if (c < 0xC0 ||
+ c >= 0xF8) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ auto encBytes = trailingBytes(c);
+ // Are there enough bytes left to avoid accessing out-of-bounds memory?
+ if (idx + encBytes - 1 >= m_str.size()) {
+ hexEscapeChar(os, c);
+ break;
+ }
+ // The header is valid, check data
+ // The next encBytes bytes must together be a valid utf-8
+ // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
+ bool valid = true;
+ uint32_t value = headerValue(c);
+ for (std::size_t n = 1; n < encBytes; ++n) {
+ uchar nc = m_str[idx + n];
+ valid &= ((nc & 0xC0) == 0x80);
+ value = (value << 6) | (nc & 0x3F);
+ }
+
+ if (
+ // Wrong bit pattern of following bytes
+ (!valid) ||
+ // Overlong encodings
+ (value < 0x80) ||
+ (0x80 <= value && value < 0x800 && encBytes > 2) ||
+ (0x800 < value && value < 0x10000 && encBytes > 3) ||
+ // Encoded value out of range
+ (value >= 0x110000)
+ ) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ // If we got here, this is in fact a valid(ish) utf-8 sequence
+ for (std::size_t n = 0; n < encBytes; ++n) {
+ os << m_str[idx + n];
+ }
+ idx += encBytes - 1;
+ break;
}
}
}
@@ -10986,7 +11506,7 @@
#include <cstring>
#include <cfloat>
#include <cstdio>
-#include <assert.h>
+#include <cassert>
#include <memory>
namespace Catch {
@@ -11261,9 +11781,7 @@
}
ReporterPreferences CompactReporter::getPreferences() const {
- ReporterPreferences prefs;
- prefs.shouldRedirectStdOut = false;
- return prefs;
+ return m_reporterPrefs;
}
void CompactReporter::noMatchingTestCases( std::string const& spec ) {
@@ -11929,7 +12447,7 @@
// end catch_reporter_console.cpp
// start catch_reporter_junit.cpp
-#include <assert.h>
+#include <cassert>
#include <sstream>
#include <ctime>
#include <algorithm>
@@ -11978,6 +12496,7 @@
xml( _config.stream() )
{
m_reporterPrefs.shouldRedirectStdOut = true;
+ m_reporterPrefs.shouldReportAllAssertions = true;
}
JunitReporter::~JunitReporter() {}
@@ -12162,100 +12681,139 @@
} // end namespace Catch
// end catch_reporter_junit.cpp
-// start catch_reporter_multi.cpp
+// start catch_reporter_listening.cpp
+
+#include <cassert>
namespace Catch {
- void MultipleReporters::add( IStreamingReporterPtr&& reporter ) {
- m_reporters.push_back( std::move( reporter ) );
+ ListeningReporter::ListeningReporter() {
+ // We will assume that listeners will always want all assertions
+ m_preferences.shouldReportAllAssertions = true;
}
- ReporterPreferences MultipleReporters::getPreferences() const {
- return m_reporters[0]->getPreferences();
+ void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) {
+ m_listeners.push_back( std::move( listener ) );
}
- std::set<Verbosity> MultipleReporters::getSupportedVerbosities() {
+ void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) {
+ assert(!m_reporter && "Listening reporter can wrap only 1 real reporter");
+ m_reporter = std::move( reporter );
+ m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut;
+ }
+
+ ReporterPreferences ListeningReporter::getPreferences() const {
+ return m_preferences;
+ }
+
+ std::set<Verbosity> ListeningReporter::getSupportedVerbosities() {
return std::set<Verbosity>{ };
}
- void MultipleReporters::noMatchingTestCases( std::string const& spec ) {
- for( auto const& reporter : m_reporters )
- reporter->noMatchingTestCases( spec );
+ void ListeningReporter::noMatchingTestCases( std::string const& spec ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->noMatchingTestCases( spec );
+ }
+ m_reporter->noMatchingTestCases( spec );
}
- void MultipleReporters::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {
- for( auto const& reporter : m_reporters )
- reporter->benchmarkStarting( benchmarkInfo );
+ void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->benchmarkStarting( benchmarkInfo );
+ }
+ m_reporter->benchmarkStarting( benchmarkInfo );
}
- void MultipleReporters::benchmarkEnded( BenchmarkStats const& benchmarkStats ) {
- for( auto const& reporter : m_reporters )
- reporter->benchmarkEnded( benchmarkStats );
+ void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->benchmarkEnded( benchmarkStats );
+ }
+ m_reporter->benchmarkEnded( benchmarkStats );
}
- void MultipleReporters::testRunStarting( TestRunInfo const& testRunInfo ) {
- for( auto const& reporter : m_reporters )
- reporter->testRunStarting( testRunInfo );
+ void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->testRunStarting( testRunInfo );
+ }
+ m_reporter->testRunStarting( testRunInfo );
}
- void MultipleReporters::testGroupStarting( GroupInfo const& groupInfo ) {
- for( auto const& reporter : m_reporters )
- reporter->testGroupStarting( groupInfo );
+ void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->testGroupStarting( groupInfo );
+ }
+ m_reporter->testGroupStarting( groupInfo );
}
- void MultipleReporters::testCaseStarting( TestCaseInfo const& testInfo ) {
- for( auto const& reporter : m_reporters )
- reporter->testCaseStarting( testInfo );
+ void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->testCaseStarting( testInfo );
+ }
+ m_reporter->testCaseStarting( testInfo );
}
- void MultipleReporters::sectionStarting( SectionInfo const& sectionInfo ) {
- for( auto const& reporter : m_reporters )
- reporter->sectionStarting( sectionInfo );
+ void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->sectionStarting( sectionInfo );
+ }
+ m_reporter->sectionStarting( sectionInfo );
}
- void MultipleReporters::assertionStarting( AssertionInfo const& assertionInfo ) {
- for( auto const& reporter : m_reporters )
- reporter->assertionStarting( assertionInfo );
+ void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->assertionStarting( assertionInfo );
+ }
+ m_reporter->assertionStarting( assertionInfo );
}
// The return value indicates if the messages buffer should be cleared:
- bool MultipleReporters::assertionEnded( AssertionStats const& assertionStats ) {
- bool clearBuffer = false;
- for( auto const& reporter : m_reporters )
- clearBuffer |= reporter->assertionEnded( assertionStats );
- return clearBuffer;
+ bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) {
+ for( auto const& listener : m_listeners ) {
+ static_cast<void>( listener->assertionEnded( assertionStats ) );
+ }
+ return m_reporter->assertionEnded( assertionStats );
}
- void MultipleReporters::sectionEnded( SectionStats const& sectionStats ) {
- for( auto const& reporter : m_reporters )
- reporter->sectionEnded( sectionStats );
+ void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->sectionEnded( sectionStats );
+ }
+ m_reporter->sectionEnded( sectionStats );
}
- void MultipleReporters::testCaseEnded( TestCaseStats const& testCaseStats ) {
- for( auto const& reporter : m_reporters )
- reporter->testCaseEnded( testCaseStats );
+ void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->testCaseEnded( testCaseStats );
+ }
+ m_reporter->testCaseEnded( testCaseStats );
}
- void MultipleReporters::testGroupEnded( TestGroupStats const& testGroupStats ) {
- for( auto const& reporter : m_reporters )
- reporter->testGroupEnded( testGroupStats );
+ void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->testGroupEnded( testGroupStats );
+ }
+ m_reporter->testGroupEnded( testGroupStats );
}
- void MultipleReporters::testRunEnded( TestRunStats const& testRunStats ) {
- for( auto const& reporter : m_reporters )
- reporter->testRunEnded( testRunStats );
+ void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->testRunEnded( testRunStats );
+ }
+ m_reporter->testRunEnded( testRunStats );
}
- void MultipleReporters::skipTest( TestCaseInfo const& testInfo ) {
- for( auto const& reporter : m_reporters )
- reporter->skipTest( testInfo );
+ void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) {
+ for ( auto const& listener : m_listeners ) {
+ listener->skipTest( testInfo );
+ }
+ m_reporter->skipTest( testInfo );
}
- bool MultipleReporters::isMulti() const {
+ bool ListeningReporter::isMulti() const {
return true;
}
} // end namespace Catch
-// end catch_reporter_multi.cpp
+// end catch_reporter_listening.cpp
// start catch_reporter_xml.cpp
#if defined(_MSC_VER)
@@ -12271,6 +12829,7 @@
m_xml(_config.stream())
{
m_reporterPrefs.shouldRedirectStdOut = true;
+ m_reporterPrefs.shouldReportAllAssertions = true;
}
XmlReporter::~XmlReporter() = default;
@@ -12327,8 +12886,7 @@
StreamingReporterBase::sectionStarting( sectionInfo );
if( m_sectionDepth++ > 0 ) {
m_xml.startElement( "Section" )
- .writeAttribute( "name", trim( sectionInfo.name ) )
- .writeAttribute( "description", sectionInfo.description );
+ .writeAttribute( "name", trim( sectionInfo.name ) );
writeSourceInfo( sectionInfo.lineInfo );
m_xml.ensureTagClosed();
}
@@ -12485,7 +13043,7 @@
#ifndef __OBJC__
-#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
+#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
// Standard C/C++ Win32 Unicode wmain entry point
extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) {
#else
@@ -12570,6 +13128,7 @@
#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
@@ -12579,11 +13138,11 @@
// "BDD-style" convenience wrappers
#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ )
#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
-#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc )
-#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc )
-#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc )
-#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc )
-#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc )
+#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc )
+#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc )
+#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And when: " << desc )
+#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc )
+#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc )
// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
#else
@@ -12628,6 +13187,7 @@
#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
@@ -12641,15 +13201,16 @@
#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
-#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc )
-#define WHEN( desc ) SECTION( std::string(" When: ") + desc )
-#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc )
-#define THEN( desc ) SECTION( std::string(" Then: ") + desc )
-#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc )
+#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc )
+#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc )
+#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And when: " << desc )
+#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc )
+#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc )
using Catch::Detail::Approx;
-#else
+#else // CATCH_CONFIG_DISABLE
+
//////
// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
#ifdef CATCH_CONFIG_PREFIX_ALL
@@ -12694,6 +13255,7 @@
#define CATCH_METHOD_AS_TEST_CASE( method, ... )
#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0)
#define CATCH_SECTION( ... )
+#define CATCH_DYNAMIC_SECTION( ... )
#define CATCH_FAIL( ... ) (void)(0)
#define CATCH_FAIL_CHECK( ... ) (void)(0)
#define CATCH_SUCCEED( ... ) (void)(0)
@@ -12752,6 +13314,7 @@
#define METHOD_AS_TEST_CASE( method, ... )
#define REGISTER_TEST_CASE( Function, ... ) (void)(0)
#define SECTION( ... )
+#define DYNAMIC_SECTION( ... )
#define FAIL( ... ) (void)(0)
#define FAIL_CHECK( ... ) (void)(0)
#define SUCCEED( ... ) (void)(0)
diff --git a/test/src/unit-renderer.cpp b/test/src/unit-renderer.cpp
index 19ee0bc..57bf2a7 100644
--- a/test/src/unit-renderer.cpp
+++ b/test/src/unit-renderer.cpp
@@ -257,7 +257,7 @@
return number1 * number2 * number3;
});
- env.add_callback("multiply", 0, [&env](inja::Parsed::Arguments args, json data) {
+ env.add_callback("multiply", 0, [](inja::Parsed::Arguments args, json data) {
return 1.0;
});