diff --git a/.gitignore b/.gitignore index 10cd6ac7..e569ff54 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ cmake-build-*/ .idea/ /doc/html/ /doc/latex/ + +.PVS-Studio/ +*.PVS-Studio.* diff --git a/CMakeLists.txt b/CMakeLists.txt index 25058978..01f25ff5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,60 +10,53 @@ set(CMAKE_C_STANDARD 11) cmake_policy(SET CMP0135 NEW) -if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) set(DXFC_IS_ROOT_PROJECT ON) -else() +else () set(DXFC_ROOT_PROJECT OFF) -endif() +endif () option(DXFC_BUILD_UNIT_TESTS "" ON) -option(DXFC_USE_DXFEED_GRAAL_NATIVE_API_GITHUB "" OFF) -set(DXFEED_GRAAL_NATIVE_API_GITHUB_VERSION "v1.0.5" CACHE STRING "") -set(DXFEED_GRAAL_NATIVE_API_GITHUB_ARCHIVE_VERSION "1.0.0" CACHE STRING "") -set(DXFEED_GRAAL_NATIVE_API_GITHUB_BASE_URL "https://github.com/ttldtor/dxfeed-graal-native-api/releases/download/" CACHE STRING "") - -option(DXFC_USE_DXFEED_GRAAL_NATIVE_API_JFROG "" ON) -set(DXFEED_GRAAL_NATIVE_API_VERSION "0.4.0" CACHE STRING "") -set(DXFEED_GRAAL_NATIVE_API_JFROG_BASE_URL "https://dxfeed.jfrog.io/artifactory/maven-open/com/dxfeed/graal-native-api/" CACHE STRING "") +option(DXFC_USE_DXFEED_GRAAL_NATIVE_SDK_JFROG "" ON) +set(DXFEED_GRAAL_NATIVE_SDK_VERSION "1.0.0" CACHE STRING "") +set(DXFEED_GRAAL_NATIVE_SDK_JFROG_BASE_URL "https://dxfeed.jfrog.io/artifactory/maven-open/com/dxfeed/graal-native-sdk/" CACHE STRING "") include(FetchContent) -if (DEFINED ENV{DXFEED_GRAAL_NATIVE_API_URL}) - FetchContent_Declare(DxFeedGraalNativeApi URL $ENV{DXFEED_GRAAL_NATIVE_API_URL}) +if (DEFINED ENV{DXFEED_GRAAL_NATIVE_SDK_URL}) + FetchContent_Declare(DxFeedGraalNativeSdk URL $ENV{DXFEED_GRAAL_NATIVE_SDK_URL}) else () - if (DXFC_USE_DXFEED_GRAAL_NATIVE_API_GITHUB) - set(DXFEED_GRAAL_NATIVE_API_URL "${DXFEED_GRAAL_NATIVE_API_GITHUB_BASE_URL}${DXFEED_GRAAL_NATIVE_API_GITHUB_VERSION}/dxfeed-graal-native-api-${DXFEED_GRAAL_NATIVE_API_GITHUB_ARCHIVE_VERSION}") - elseif (DXFC_USE_DXFEED_GRAAL_NATIVE_API_JFROG) - set(DXFEED_GRAAL_NATIVE_API_URL "${DXFEED_GRAAL_NATIVE_API_JFROG_BASE_URL}${DXFEED_GRAAL_NATIVE_API_VERSION}/graal-native-api-${DXFEED_GRAAL_NATIVE_API_VERSION}") + if (DXFC_USE_DXFEED_GRAAL_NATIVE_SDK_JFROG) + set(DXFEED_GRAAL_NATIVE_SDK_URL "${DXFEED_GRAAL_NATIVE_SDK_JFROG_BASE_URL}${DXFEED_GRAAL_NATIVE_SDK_VERSION}/graal-native-sdk-${DXFEED_GRAAL_NATIVE_SDK_VERSION}") endif () if (WIN32) - set(DXFEED_GRAAL_NATIVE_API_URL ${DXFEED_GRAAL_NATIVE_API_URL}-amd64-windows.zip) + set(DXFEED_GRAAL_NATIVE_SDK_URL ${DXFEED_GRAAL_NATIVE_SDK_URL}-amd64-windows.zip) elseif (APPLE) if (${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL "arm64") - set(DXFEED_GRAAL_NATIVE_API_URL ${DXFEED_GRAAL_NATIVE_API_URL}-aarch64-osx.zip) + set(DXFEED_GRAAL_NATIVE_SDK_URL ${DXFEED_GRAAL_NATIVE_SDK_URL}-aarch64-osx.zip) else () - set(DXFEED_GRAAL_NATIVE_API_URL ${DXFEED_GRAAL_NATIVE_API_URL}-x86_64-osx.zip) + set(DXFEED_GRAAL_NATIVE_SDK_URL ${DXFEED_GRAAL_NATIVE_SDK_URL}-x86_64-osx.zip) endif () elseif (UNIX) - set(DXFEED_GRAAL_NATIVE_API_URL ${DXFEED_GRAAL_NATIVE_API_URL}-amd64-linux.zip) + set(DXFEED_GRAAL_NATIVE_SDK_URL ${DXFEED_GRAAL_NATIVE_SDK_URL}-amd64-linux.zip) else () message(ERROR "Unknown platform!") endif () - FetchContent_Declare(DxFeedGraalNativeApi URL ${DXFEED_GRAAL_NATIVE_API_URL}) + FetchContent_Declare(DxFeedGraalNativeSdk URL ${DXFEED_GRAAL_NATIVE_SDK_URL}) endif () -FetchContent_MakeAvailable(DxFeedGraalNativeApi) -# DxFeedGraalNativeApi_SOURCE_DIR +FetchContent_MakeAvailable(DxFeedGraalNativeSdk) +# DxFeedGraalNativeSdk_SOURCE_DIR if (DXFC_USE_CONAN) include(${CMAKE_BINARY_DIR}/conan_paths.cmake) endif () add_subdirectory(third_party/utfcpp-3.2.3) -add_subdirectory(third_party/fmt-9.1.0) +add_subdirectory(third_party/fmt-10.0.0) # find_package(utf8cpp) # find_package(fmt) @@ -71,15 +64,36 @@ add_subdirectory(third_party/fmt-9.1.0) set(dxFeedNativeAPIInternalSources src/internal/CEntryPointErrors.cpp src/internal/Isolate.cpp + src/internal/JavaObjectHandler.cpp + src/internal/EventClassList.cpp + src/internal/SymbolList.cpp src/internal/Common.cpp ) +set(dxFeedNativeAPIInternalUtilsSources + src/internal/utils/StringUtils.cpp + ) + +set(dxFeedNativeAPIInternalUtilsDebugSources + src/internal/utils/debug/Debug.cpp + ) + set(dxFeedNativeAPIAPISources src/api/DXEndpoint.cpp src/api/DXFeed.cpp src/api/DXFeedSubscription.cpp ) +set(dxFeedNativeAPIAPIOsubSources + src/api/osub/WildcardSymbol.cpp + ) + +set(dxFeedNativeAPISymbolsSources + src/symbols/SymbolMapper.cpp + src/symbols/StringSymbol.cpp + src/symbols/SymbolWrapper.cpp + ) + set(dxFeedNativeAPISystemSources src/system/System.cpp ) @@ -118,7 +132,11 @@ set(dxFeedNativeAPIEventOptionSources add_library(dxFeedGraalCxxApi ${dxFeedNativeAPIInternalSources} + ${dxFeedNativeAPIInternalUtilsSources} + ${dxFeedNativeAPIInternalUtilsDebugSources} ${dxFeedNativeAPIAPISources} + ${dxFeedNativeAPIAPIOsubSources} + ${dxFeedNativeAPISymbolsSources} ${dxFeedNativeAPISystemSources} ${dxFeedNativeAPIEventSources} ${dxFeedNativeAPIEventMarketSources} @@ -126,11 +144,18 @@ add_library(dxFeedGraalCxxApi src/api.cpp ) +add_library(dxFeedGraalCxxApi_Clang INTERFACE) + +if ((${CMAKE_CXX_COMPILER_ID} MATCHES "AppleClang") OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")) + # Ignore undefined template var warning + target_compile_options(dxFeedGraalCxxApi_Clang INTERFACE -Wno-undefined-var-template) +endif () + target_include_directories(dxFeedGraalCxxApi PUBLIC include) -target_link_libraries(dxFeedGraalCxxApi PUBLIC DxFeedGraalNativeApi utf8cpp fmt::fmt) +target_link_libraries(dxFeedGraalCxxApi PUBLIC DxFeedGraalNativeSdk utf8cpp fmt::fmt PUBLIC dxFeedGraalCxxApi_Clang) add_custom_command(TARGET dxFeedGraalCxxApi POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ $) + $ $) if (DXFC_BUILD_UNIT_TESTS) include(CTest) diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 73de6297..8e1a3ad9 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -2,13 +2,12 @@ ## Compile-time -- [dxFeed Graal Native SDK](https://github.com/dxFeed/dxfeed-graal-native-sdk) v0.4.0 +- [dxFeed Graal Native SDK](https://github.com/dxFeed/dxfeed-graal-native-sdk) v1.0.0 - [utfcpp](https://github.com/nemtrif/utfcpp) v3.2.3 -- [fmt](https://github.com/fmtlib/fmt) v9.1.0 +- [fmt](https://github.com/fmtlib/fmt) v10.0.0 - [doctest](https://github.com/doctest/doctest) v2.4.11 (Tests) ## Run-time -- [dxFeed Graal Native SDK](https://github.com/dxFeed/dxfeed-graal-native-sdk) v0.4.0 -- [fmt](https://github.com/fmtlib/fmt) v9.1.0 (C++) +- [dxFeed Graal Native SDK](https://github.com/dxFeed/dxfeed-graal-native-sdk) v1.0.0 - [doctest](https://github.com/doctest/doctest) v2.4.11 (Tests) \ No newline at end of file diff --git a/README.md b/README.md index 86011ac5..c0186e8a 100644 --- a/README.md +++ b/README.md @@ -266,9 +266,9 @@ be downloaded from [Release](https://github.com/dxFeed/dxfeed-graal-cxx-api/rele by [dxPrice](http://www.devexperts.com/en/products/price.html) model-free computation - [x] [Underlying](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/event/option/Underlying.html) is a snapshot of computed values available for an option underlying symbol based on the market’s option prices -- [x] [Configuration](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/event/misc/Configuration.html) is an event with an +- [ ] [Configuration](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/event/misc/Configuration.html) is an event with an application-specific attachment -- [x] [Message](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/event/misc/Message.html) is an event with an +- [ ] [Message](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/event/misc/Message.html) is an event with an application-specific attachment ### Subscription Symbols @@ -278,7 +278,7 @@ be downloaded from [Release](https://github.com/dxFeed/dxfeed-graal-cxx-api/rele represents subscription to time-series events - [ ] [IndexedSubscriptionSymbol](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/api/osub/IndexedEventSubscriptionSymbol.html) - represents subscription to a specific source of indexed events -- [ ] [WildcardSymbol.ALL](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/api/osub/WildcardSymbol.html) - represents a +- [x] [WildcardSymbol.ALL](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/api/osub/WildcardSymbol.html) - represents a *wildcard* subscription to all events of the specific event type - [ ] [CandleSymbol](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/event/candle/CandleSymbol.html) - symbol used with [DXFeedSubscription](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/api/DXFeedSubscription.html) class to diff --git a/include/dxfeed_graal_cpp_api/api.hpp b/include/dxfeed_graal_cpp_api/api.hpp index c5ddefa9..097cbf81 100644 --- a/include/dxfeed_graal_cpp_api/api.hpp +++ b/include/dxfeed_graal_cpp_api/api.hpp @@ -3,17 +3,29 @@ #pragma once -#include "dxfeed_graal_cpp_api/internal/managers/ErrorHandlingManager.hpp" #include "internal/CEntryPointErrors.hpp" #include "internal/Common.hpp" #include "internal/Enum.hpp" +#include "internal/EventClassList.hpp" +#include "internal/Id.hpp" +#include "internal/JavaObjectHandler.hpp" +#include "internal/NonCopyable.hpp" +#include "internal/RawListWrapper.hpp" +#include "internal/SymbolList.hpp" #include "internal/context/ApiContext.hpp" #include "internal/managers/DXEndpointManager.hpp" #include "internal/managers/DXFeedSubscriptionManager.hpp" #include "internal/managers/EntityManager.hpp" +#include "internal/managers/ErrorHandlingManager.hpp" +#include "internal/utils/StringUtils.hpp" +#include "internal/utils/debug/Debug.hpp" #include "api/DXEndpoint.hpp" #include "api/DXFeed.hpp" #include "api/DXFeedSubscription.hpp" +#include "api/osub/WildcardSymbol.hpp" #include "event/DXEvent.hpp" +#include "symbols/StringSymbol.hpp" +#include "symbols/SymbolMapper.hpp" +#include "symbols/SymbolWrapper.hpp" #include "system/System.hpp" diff --git a/include/dxfeed_graal_cpp_api/api/DXEndpoint.hpp b/include/dxfeed_graal_cpp_api/api/DXEndpoint.hpp index 43e1ad5f..7176f197 100644 --- a/include/dxfeed_graal_cpp_api/api/DXEndpoint.hpp +++ b/include/dxfeed_graal_cpp_api/api/DXEndpoint.hpp @@ -19,7 +19,7 @@ namespace dxfcpp { -struct DXPublisher : std::enable_shared_from_this { +struct DXPublisher : SharedEntity { virtual ~DXPublisher() = default; }; @@ -176,7 +176,7 @@ struct DXFeed; * * [Javadoc.](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/api/DXEndpoint.html) */ -struct DXEndpoint : std::enable_shared_from_this { +struct DXEndpoint : SharedEntity { /** * Defines property for endpoint name that is used to distinguish multiple endpoints * in the same process in logs and in other diagnostic means. @@ -397,7 +397,7 @@ struct DXEndpoint : std::enable_shared_from_this { LOCAL_HUB }; - static auto roleToString(Role role) { + static std::string roleToString(Role role) { switch (role) { case Role::FEED: return "FEED"; @@ -463,12 +463,12 @@ struct DXEndpoint : std::enable_shared_from_this { static inline std::mutex MTX{}; static std::unordered_map> INSTANCES; - handler_utils::JavaObjectHandler handler_; + JavaObjectHandler handler_; Role role_ = Role::FEED; std::string name_{}; std::shared_ptr feed_{}; std::shared_ptr publisher_{}; - handler_utils::JavaObjectHandler stateChangeListenerHandler_; + JavaObjectHandler stateChangeListenerHandler_; Handler onStateChange_{}; static std::shared_ptr create(void *endpointHandle, Role role, @@ -480,15 +480,15 @@ struct DXEndpoint : std::enable_shared_from_this { protected: DXEndpoint() : handler_{}, role_{}, feed_{}, publisher_{}, stateChangeListenerHandler_{}, onStateChange_{} { - if constexpr (isDebug) { - debug("DXEndpoint()"); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint()"); } } public: virtual ~DXEndpoint() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::~DXEndpoint()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + " }::~DXEndpoint()"); } } @@ -506,8 +506,8 @@ struct DXEndpoint : std::enable_shared_from_this { * @see #getInstance(Role) */ static std::shared_ptr getInstance() { - if constexpr (isDebug) { - debug("DXEndpoint::getInstance()"); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::getInstance()"); } return getInstance(Role::FEED); @@ -533,8 +533,8 @@ struct DXEndpoint : std::enable_shared_from_this { * @return The DXEndpoint instance */ static std::shared_ptr getInstance(Role role) { - if constexpr (isDebug) { - debug("DXEndpoint::getInstance(role = {})", roleToString(role)); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::getInstance(role = " + roleToString(role) + ")"); } std::lock_guard lock(MTX); @@ -555,8 +555,8 @@ struct DXEndpoint : std::enable_shared_from_this { * @return the created endpoint. */ static std::shared_ptr create() { - if constexpr (isDebug) { - debug("DXEndpoint::create()"); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::create()"); } return newBuilder()->build(); @@ -572,8 +572,8 @@ struct DXEndpoint : std::enable_shared_from_this { * @return the created endpoint. */ static std::shared_ptr create(Role role) { - if constexpr (isDebug) { - debug("DXEndpoint::create(role = {})", roleToString(role)); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::create(role = " + roleToString(role) + ")"); } return newBuilder()->withRole(role)->build(); @@ -617,9 +617,11 @@ struct DXEndpoint : std::enable_shared_from_this { */ template std::size_t addStateChangeListener(StateChangeListener &&listener) noexcept +#if __cpp_concepts requires requires { - { listener(State{}, State{}) } -> std::same_as; - } + { listener(State{}, State{}) } -> std::same_as; + } +#endif { return onStateChange_ += listener; } @@ -746,8 +748,8 @@ struct DXEndpoint : std::enable_shared_from_this { * [Javadoc.](https://docs.dxfeed.com/dxfeed/api/com/dxfeed/api/DXEndpoint.html#close--) */ void close() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::close()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::close()"); } closeImpl(); @@ -805,14 +807,14 @@ struct DXEndpoint : std::enable_shared_from_this { class Builder : public std::enable_shared_from_this { friend DXEndpoint; -// mutable std::recursive_mutex mtx_{}; - handler_utils::JavaObjectHandler handler_; + // mutable std::recursive_mutex mtx_{}; + JavaObjectHandler handler_; Role role_ = Role::FEED; std::unordered_map properties_; Builder() : handler_{}, properties_{} { - if constexpr (isDebug) { - debug("DXEndpoint::Builder::Builder()"); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder::Builder()"); } } @@ -835,8 +837,8 @@ struct DXEndpoint : std::enable_shared_from_this { public: /// Releases the GraalVM handle virtual ~Builder() { - if constexpr (isDebug) { - debug("DXEndpoint::Builder{{{}}}::~Builder()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder{" + handler_.toString() + "}::~Builder()"); } } @@ -850,9 +852,9 @@ struct DXEndpoint : std::enable_shared_from_this { * @return `this` endpoint builder. */ std::shared_ptr withName(const std::string &name) { - //TODO: check invalid utf-8 - if constexpr (isDebug) { - debug("DXEndpoint::Builder{{{}}}::withName(name = {})", handler_.toString(), name); + // TODO: check invalid utf-8 + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder{" + handler_.toString() + "}::withName(name = " + name + ")"); } return withProperty(NAME_PROPERTY, name); @@ -889,9 +891,9 @@ struct DXEndpoint : std::enable_shared_from_this { * @see ::withProperty(const std::string&, const std::string&) */ template std::shared_ptr withProperties(Properties &&properties) { - if constexpr (isDebug) { - debug("DXEndpoint::Builder{{{}}}::withProperties(properties[{}])", handler_.toString(), - properties.size()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder{" + handler_.toString() + "}::withProperties(properties[" + + std::to_string(properties.size()) + "])"); } for (auto &&[k, v] : properties) { @@ -926,15 +928,13 @@ struct DXEndpoint : std::enable_shared_from_this { * @return the created endpoint builder. */ static std::shared_ptr newBuilder() noexcept { - if constexpr (isDebug) { - debug("DXEndpoint::newBuilder()"); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::newBuilder()"); } return Builder::create(); } - std::string toString() const { - return fmt::format("DXEndpoint{{{}}}", handler_.toString()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/api/DXFeed.hpp b/include/dxfeed_graal_cpp_api/api/DXFeed.hpp index 59bcb7c6..93ff3ede 100644 --- a/include/dxfeed_graal_cpp_api/api/DXFeed.hpp +++ b/include/dxfeed_graal_cpp_api/api/DXFeed.hpp @@ -23,11 +23,11 @@ class EventTypeEnum; /** * Main entry class for dxFeed API (read it first). */ -struct DXFeed : std::enable_shared_from_this { +struct DXFeed : SharedEntity { friend struct DXEndpoint; private: - handler_utils::JavaObjectHandler handler_; + JavaObjectHandler handler_; std::unordered_set> subscriptions_{}; @@ -35,15 +35,15 @@ struct DXFeed : std::enable_shared_from_this { protected: DXFeed() noexcept : handler_{} { - if constexpr (isDebug) { - debug("DXFeed()"); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeed()"); } } public: virtual ~DXFeed() noexcept { - if constexpr (isDebug) { - debug("{}::~DXFeed()", toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeed{" + handler_.toString() + "}::~DXFeed()"); } } @@ -67,8 +67,8 @@ struct DXFeed : std::enable_shared_from_this { template std::shared_ptr createSubscription(EventTypeIt begin, EventTypeIt end) noexcept { - if constexpr (isDebug) { - debug("{}::createSubscription(eventTypes = {})", namesToString(begin, end)); + if constexpr (Debugger::isDebug) { + Debugger::debug("{}::createSubscription(eventTypes = " + namesToString(begin, end) + ")"); } auto sub = DXFeedSubscription::create(begin, end); @@ -82,11 +82,13 @@ struct DXFeed : std::enable_shared_from_this { template std::shared_ptr createSubscription(EventTypesCollection &&eventTypes) noexcept - requires requires { ElementTypeIs; } +#if __cpp_concepts + requires ElementTypeIs +#endif { - if constexpr (isDebug) { - debug("{}::createSubscription(eventTypes = {})", toString(), - namesToString(std::begin(eventTypes), std::end(eventTypes))); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::createSubscription(eventTypes = " + + namesToString(std::begin(eventTypes), std::end(eventTypes)) + ")"); } auto sub = DXFeedSubscription::create(eventTypes); @@ -96,9 +98,7 @@ struct DXFeed : std::enable_shared_from_this { return sub; } - std::string toString() const { - return fmt::format("DXFeed{{{}}}", handler_.toString()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/api/DXFeedSubscription.hpp b/include/dxfeed_graal_cpp_api/api/DXFeedSubscription.hpp index 9f05fd58..20e83f1e 100644 --- a/include/dxfeed_graal_cpp_api/api/DXFeedSubscription.hpp +++ b/include/dxfeed_graal_cpp_api/api/DXFeedSubscription.hpp @@ -7,6 +7,13 @@ #include "../event/DXEvent.hpp" #include "../internal/Common.hpp" +#include "../symbols/StringSymbol.hpp" +#include "../symbols/SymbolWrapper.hpp" +#include "dxfeed_graal_cpp_api/api/osub/WildcardSymbol.hpp" + +#if __cpp_lib_parallel_algorithm +# include +#endif #include @@ -14,46 +21,44 @@ namespace dxfcpp { struct DXFeed; -class DXFeedSubscription : public std::enable_shared_from_this { +/** + * Subscription for a set of symbols and event types. + */ +class DXFeedSubscription : public SharedEntity { friend struct DXFeed; mutable std::recursive_mutex mtx_{}; - handler_utils::JavaObjectHandler handler_; - handler_utils::JavaObjectHandler eventListenerHandler_; + + std::unordered_set eventTypes_; + JavaObjectHandler handler_; + JavaObjectHandler eventListenerHandler_; Handler> &)> onEvent_{1}; explicit DXFeedSubscription(const EventTypeEnum &eventType) noexcept; - static handler_utils::JavaObjectHandler - createSubscriptionHandlerFromEventClassList(const std::unique_ptr &list) noexcept; + static JavaObjectHandler + createSubscriptionHandlerFromEventClassList(const std::unique_ptr &list) noexcept; void setEventListenerHandler(Id id) noexcept; template - DXFeedSubscription(EventTypeIt begin, EventTypeIt end) noexcept - : mtx_{}, handler_{}, eventListenerHandler_{}, onEvent_{} { - if constexpr (isDebug) { - debug("DXFeedSubscription(eventTypes = {})", namesToString(begin, end)); +#if __cpp_concepts + requires requires(EventTypeIt iter) { + { *iter } -> std::convertible_to; } - - auto size = std::distance(begin, end); - - if (size <= 0) { - return; +#endif + DXFeedSubscription(EventTypeIt begin, EventTypeIt end) noexcept + : mtx_{}, eventTypes_(begin, end), handler_{}, eventListenerHandler_{}, onEvent_{} { + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeedSubscription(eventTypes = " + namesToString(begin, end) + ")"); } - auto list = handler_utils::EventClassList::create(size); + auto list = EventClassList::create(eventTypes_.begin(), eventTypes_.end()); - if (list->isEmpty()) { + if (!list) { return; } - std::size_t i = 0; - - for (auto it = begin; it != end; it++, i++) { - list->set(i, it->getId()); - } - handler_ = createSubscriptionHandlerFromEventClassList(list); } @@ -62,22 +67,30 @@ class DXFeedSubscription : public std::enable_shared_from_this explicit DXFeedSubscription(EventTypesCollection &&eventTypes) noexcept - requires requires { ElementTypeIs; } +#if __cpp_concepts + requires ElementTypeIs +#endif : DXFeedSubscription(std::begin(std::forward(eventTypes)), - std::end(std::forward(eventTypes))) {} + std::end(std::forward(eventTypes))) { + } + + void closeImpl() const noexcept; + + void clearImpl() const noexcept; - void closeImpl() noexcept; + bool isClosedImpl() const noexcept; - void clearImpl() noexcept; + void addSymbolImpl(void *graalSymbol) const noexcept; - bool isClosedImpl() noexcept; + void removeSymbolImpl(void *graalSymbol) const noexcept; public: - std::string toString() const { return fmt::format("DXFeedSubscription{{{}}}", handler_.toString()); } + /// + std::string toString() const noexcept override; - virtual ~DXFeedSubscription() { - if constexpr (isDebug) { - debug("{}::~DXFeedSubscription()", toString()); + ~DXFeedSubscription() override { + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeedSubscription{" + handler_.toString() + "}::~DXFeedSubscription()"); } tryCallWithLock(mtx_, [this] { closeImpl(); }); @@ -86,11 +99,16 @@ class DXFeedSubscription : public std::enable_shared_from_thisdetached subscription for a single event type. * + * Example: + * ```cpp + * auto sub = dxfcpp::DXFeedSubscription(Quote::Type); + * ``` + * * @param eventType the event type. */ static std::shared_ptr create(const EventTypeEnum &eventType) noexcept { - if constexpr (isDebug) { - debug("DXFeedSubscription::create(eventType = {})", eventType.getName()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeedSubscription::create(eventType = " + eventType.getName() + ")"); } auto sub = std::shared_ptr(new DXFeedSubscription(eventType)); @@ -104,15 +122,26 @@ class DXFeedSubscription : public std::enable_shared_from_thisdetached subscription for the given collection of event types. * + * Example: + * ```cpp + * auto eventTypes = {dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}; + * auto sub = dxfcpp::DXFeedSubscription::create(eventTypes.begin(), eventTypes.end()); + * ``` + * * @tparam EventTypeIt The collection's iterator type * @param begin The beginning of the collection of event types. * @param end The end of event type collection. * @return The new detached subscription for the given collection of event types. */ template +#if __cpp_concepts + requires requires(EventTypeIt iter) { + { *iter } -> std::convertible_to; + } +#endif static std::shared_ptr create(EventTypeIt begin, EventTypeIt end) noexcept { - if constexpr (isDebug) { - debug("DXFeedSubscription::create(eventTypes = {})", namesToString(begin, end)); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeedSubscription::create(eventTypes = " + namesToString(begin, end) + ")"); } auto sub = std::shared_ptr(new DXFeedSubscription(begin, end)); @@ -126,6 +155,11 @@ class DXFeedSubscription : public std::enable_shared_from_thisdetached subscription for the given collection of event types. * + * Example: + * ```cpp + * auto sub = dxfcpp::DXFeedSubscription::create({dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}); + * ``` + * * @param eventTypes The event type collection. * @return The new detached subscription for the given collection of event types. */ @@ -141,13 +175,20 @@ class DXFeedSubscription : public std::enable_shared_from_thisdetached subscription for the given collection of event types. * + * Example: + * ```cpp + * auto sub = dxfcpp::DXFeedSubscription::create(std::unordered_set{dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}); + * ``` + * * @tparam EventTypesCollection The type of the collection of event types * @param eventTypes The event type collection. * @return The new detached subscription for the given collection of event types. */ template static std::shared_ptr create(EventTypesCollection &&eventTypes) noexcept - requires requires { ElementTypeIs; } +#if __cpp_concepts + requires ElementTypeIs +#endif { auto sub = std::shared_ptr(new DXFeedSubscription(std::forward(eventTypes))); @@ -180,8 +221,8 @@ class DXFeedSubscription : public std::enable_shared_from_thisgetFeed()->createSubscription({dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}); + * + * sub->addEventListener([](auto &&events) { + * for (const auto &e : events) { + * if (auto quote = e->template sharedAs(); quote) { + * std::cout << "Q : " + quote->toString() << std::endl; + * } else if (auto tns = e->template sharedAs(); tns) { + * std::cout << "TnS : " + tns->toString() << std::endl; + * } + * } + * }); + * + * sub->addSymbols({"$TOP10L/Q", "$SP500#45", "$TICK", "SPX"}); + * ``` + * + * @tparam EventListener The listener type. Listener can be callable with signature: `void(const + * std::vector&)` + * @param listener The event listener + * @return The listener id + */ template std::size_t addEventListener(EventListener &&listener) noexcept +#if __cpp_concepts requires requires { { listener(std::vector>{}) } -> std::same_as; } +#endif { return onEvent_ += listener; } + /** + * Adds typed listener for events. + * Event lister can be added only when subscription is not producing any events. + * The subscription must be either empty + * (its set of @ref ::getSymbols() "symbols" is empty or not @ref ::attach() "attached" to any feed + * (its set of change listeners is empty). + * + * This method does nothing if this subscription is closed. + * + * Example: + * ```cpp + * auto sub = endpoint->getFeed()->createSubscription({dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}); + * + * sub->addEventListener(std::function([](const std::vector> "es) -> void { + * for (const auto &q : quotes) { + * std::cout << "Q : " + q->toString() << std::endl; + * } + * })); + * + * sub->addEventListener([](const auto &timeAndSales) -> void { + * for (const auto &tns : timeAndSales) { + * std::cout << "TnS : " + tns->toString() << std::endl; + * } + * }); + * + * sub->addSymbols({"$TOP10L/Q", "AAPL", "$TICK", "SPX"}); + * ``` + * + * @tparam EventT The event type (EventType's child with field Type, convertible to EventTypeEnum + * @param listener The listener. Listener can be callable with signature: `void(const std::vector&)` + * @return The listener id + */ + template + std::size_t addEventListener(std::function> &)> &&listener) noexcept +#if __cpp_concepts + requires std::is_base_of_v && requires { + { EventT::Type } -> std::convertible_to; + } +#endif + { + if (!containsEventType(EventT::Type)) { + return onEvent_ += [](auto) {}; + } + + return onEvent_ += [l = listener](auto &&events) { + std::vector> filteredEvents{}; + + filteredEvents.reserve(events.size()); + + for (const auto &e : events) { + if (auto expected = e->template sharedAs(); expected) { + filteredEvents.emplace_back(expected); + } + } + + l(filteredEvents); + }; + } + + /** + * Removes listener for events. + * + * Example: + * ```cpp + * auto id = sub->addEventListener([](auto){}); + * + * sub->removeEventListener(id); + * ``` + * + * @param listenerId The listener id + */ void removeEventListener(std::size_t listenerId) noexcept { onEvent_ -= listenerId; } - const auto &onEvent() noexcept { return onEvent_; } + /** + * Returns a reference to an incoming events' handler (delegate), to which listeners can be added and removed. + * Listener can be callable with signature: `void(const std::vector&)` + * + * Example: + * ```cpp + * auto sub = endpoint->getFeed()->createSubscription({dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}); + * auto id = sub->onEvent() += [](auto &&events) { + * for (const auto &e : events) { + * if (auto quote = e->template sharedAs(); quote) { + * std::cout << "Q : " + quote->toString() << std::endl; + * } else if (auto tns = e->template sharedAs(); tns) { + * std::cout << "TnS : " + tns->toString() << std::endl; + * } + * } + * }; + * + * sub->addSymbols({"$TOP10L/Q", "$SP500#45", "$TICK", "SPX"}); + * sub->onEvent() -= id; + * ``` + * + * @return The incoming events' handler (delegate) + */ + auto &onEvent() noexcept { return onEvent_; } + + /** + * Adds the specified symbol to the set of subscribed symbols. + * This is a convenience method to subscribe to one symbol at a time that has a return fast-path for a case when + * the symbol is already in the set. + * When subscribing to multiple symbols at once it is preferable to use @ref ::addSymbols(const SymbolsCollection + * &collection) "addSymbols(symbols)" method. + * + * Example: + * ```cpp + * sub->addSymbols("TSLA"); + * sub->addSymbols("XBT/USD:GDAX"s); + * sub->addSymbols("BTC/EUR:CXBITF"sv); + * ``` + * + * @param symbolWrapper The symbol. + */ + void addSymbols(const SymbolWrapper &symbolWrapper) noexcept { + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::addSymbols(symbolWrapper = " + toStringAny(symbolWrapper) + ")"); + } + + addSymbolImpl(symbolWrapper.toGraal()); + } + + /** + * Removes the specified symbol from the set of subscribed symbols. + * To conveniently remove one or few symbols you can use @ref ::removeSymbols(const SymbolsCollection &collection) + * "removeSymbols(symbols)" method. + * + * Example: + * ```cpp + * sub->removeSymbols("TSLA"); + * sub->removeSymbols("XBT/USD:GDAX"s); + * sub->removeSymbols("BTC/EUR:CXBITF"sv); + * ``` + * + * @param symbolWrapper The symbol. + */ + void removeSymbols(const SymbolWrapper &symbolWrapper) noexcept { + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::removeSymbols(symbolWrapper = " + toStringAny(symbolWrapper) + ")"); + } + + removeSymbolImpl(symbolWrapper.toGraal()); + } + + /** + * Adds the specified collection (using iterators) of symbols to the set of subscribed symbols. + * + * Example: + * ```cpp + * auto v = std::vector{"XBT/USD:GDAX"s, "BTC/EUR:CXBITF"sv, "TSLA", "GOOG"_s}; + * + * sub->addSymbols(v.begin(), v.end()); + * ``` + * + * @tparam SymbolIt The collection's iterator type + * @param begin The beginning of the collection of symbols. + * @param end The end of symbol collection. + */ + template void addSymbols(SymbolIt begin, SymbolIt end) noexcept { + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::addSymbols(symbols = " + elementsToString(begin, end) + ")"); + } + + std::for_each( +#if __cpp_lib_parallel_algorithm + std::execution::par, +#endif + begin, end, [this](const SymbolWrapper &wrapper) { addSymbolImpl(wrapper.toGraal()); }); + } + + /** + * Adds the specified collection of symbols to the set of subscribed symbols. + * + * Example: + * ```cpp + * auto v = std::vector{"XBT/USD:GDAX"s, "BTC/EUR:CXBITF"sv, "TSLA", "GOOG"_s}; + * + * sub->addSymbols(std::vector{"AAPL", "IBM"}); + * sub->addSymbols(v); + * ``` + * + * @tparam SymbolsCollection The symbols collection's type + * @param collection The symbols collection + */ + template + void addSymbols(const SymbolsCollection &collection) noexcept { + addSymbols(std::begin(collection), std::end(collection)); + } + + /** + * Adds the specified collection (initializer list) of symbols to the set of subscribed symbols. + * + * Example: + * ```cpp + * sub->addSymbols({"AAPL", "IBM"sv, "TSLA"s, "GOOG"_s}); + * ``` + * + * @param collection The symbols collection + */ + void addSymbols(std::initializer_list collection) noexcept { + addSymbols(collection.begin(), collection.end()); + } + + /** + * Removes the specified collection (using iterators) of symbols from the set of subscribed symbols. + * + * Example: + * ```cpp + * auto v = std::vector{"XBT/USD:GDAX"s, "BTC/EUR:CXBITF"sv, "TSLA", "GOOG"_s}; + * + * sub->removeSymbols(v.begin(), v.end()); + * ``` + * + * @tparam SymbolIt The collection's iterator type + * @param begin The beginning of the collection of symbols. + * @param end The end of symbol collection. + */ + template void removeSymbols(SymbolIt begin, SymbolIt end) noexcept { + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::removeSymbols(symbols = " + elementsToString(begin, end) + ")"); + } + + std::for_each( +#if __cpp_lib_parallel_algorithm + std::execution::par, +#endif + begin, end, [this](const SymbolWrapper &wrapper) { removeSymbolImpl(wrapper.toGraal()); }); + } + + /** + * Removes the specified collection of symbols from the set of subscribed symbols. + * + * Example: + * ```cpp + * auto v = std::vector{"XBT/USD:GDAX"s, "BTC/EUR:CXBITF"sv, "TSLA", "GOOG"_s}; + * + * sub->removeSymbols(std::vector{"AAPL", "IBM"}); + * sub->removeSymbols(v); + * ``` + * + * @tparam SymbolsCollection The symbols collection's type + * @param collection The symbols collection + */ + template + void removeSymbols(SymbolsCollection &&collection) noexcept { + removeSymbols(std::begin(collection), std::end(collection)); + } - template void addSymbol(Symbol &&symbol) noexcept; + /** + * Removes the specified collection (initializer list) of symbols from the set of subscribed symbols. + * + * Example: + * ```cpp + * sub->removeSymbols({"AAPL", "IBM"sv, "TSLA"s, "GOOG"_s}); + * ``` + * + * @param collection The symbols collection + */ + void removeSymbols(std::initializer_list collection) noexcept { + removeSymbols(collection.begin(), collection.end()); + } - template void addSymbols(SymbolsCollection &&collection) noexcept; + /** + * Changes the set of subscribed symbols so that it contains just the symbols from the specified collection (using + * iterators). + * + * Example: + * ```cpp + * auto v = std::vector{"XBT/USD:GDAX"s, "BTC/EUR:CXBITF"sv, "TSLA", "GOOG"_s}; + * + * sub->setSymbols(v.begin(), v.end()); + * ``` + * + * @tparam SymbolIt The collection's iterator type + * @param begin The beginning of the collection of symbols. + * @param end The end of symbol collection. + */ + template void setSymbols(SymbolIt begin, SymbolIt end) noexcept { + // TODO: implement using the native implementation + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::setSymbols(symbols = " + elementsToString(begin, end) + ")"); + } - template void addSymbols(std::initializer_list collection) noexcept; + clearImpl(); + addSymbols(begin, end); + } - template void removeSymbol(Symbol &&symbol) noexcept; + /** + * Changes the set of subscribed symbols so that it contains just the symbols from the specified collection. + * + * Example: + * ```cpp + * auto v = std::vector{"XBT/USD:GDAX"s, "BTC/EUR:CXBITF"sv, "TSLA", "GOOG"_s}; + * + * sub->setSymbols(std::vector{"AAPL", "IBM"}); + * sub->setSymbols(v); + * ``` + * + * @tparam SymbolsCollection The symbols collection's type + * @param collection The symbols collection + */ + template + void setSymbols(SymbolsCollection &&collection) noexcept { + setSymbols(std::begin(collection), std::end(collection)); + } - template void removeSymbols(SymbolsCollection &&collection) noexcept; + /** + * Changes the set of subscribed symbols so that it contains just the symbols from the specified collection + * (initializer list). + * + * Example: + * ```cpp + * sub->setSymbols({"AAPL", "IBM"sv, "TSLA"s, "GOOG"_s}); + * ``` + * + * @param collection The symbols collection + */ + void setSymbols(std::initializer_list collection) noexcept { + setSymbols(collection.begin(), collection.end()); + } /** * Clears the set of subscribed symbols. */ void clear() noexcept { - if constexpr (isDebug) { - debug("{}::clear()", toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::clear()"); } std::lock_guard lock(mtx_); @@ -233,8 +614,8 @@ class DXFeedSubscription : public std::enable_shared_from_this getEventTypes() noexcept; + const std::unordered_set &getEventTypes() const noexcept { return eventTypes_; } /** * Returns `true` if this subscription contains the corresponding event type. @@ -256,7 +637,7 @@ class DXFeedSubscription : public std::enable_shared_from_this void setSymbol(Symbol &&symbol) noexcept; - - template void setSymbols(SymbolsCollection &&collection) noexcept; - /** * Returns a set of decorated symbols (depending on the actual implementation of subscription). * diff --git a/include/dxfeed_graal_cpp_api/api/osub/WildcardSymbol.hpp b/include/dxfeed_graal_cpp_api/api/osub/WildcardSymbol.hpp new file mode 100644 index 00000000..7a3e2a7a --- /dev/null +++ b/include/dxfeed_graal_cpp_api/api/osub/WildcardSymbol.hpp @@ -0,0 +1,53 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include +#include + +namespace dxfcpp { + +struct WildcardSymbol final { + static const WildcardSymbol ALL; + + private: + std::string symbol; + + WildcardSymbol(const std::string &symbol) noexcept : symbol{symbol} {} + + public: + WildcardSymbol(const WildcardSymbol &) noexcept = default; + WildcardSymbol(WildcardSymbol &&) noexcept = default; + WildcardSymbol &operator=(const WildcardSymbol &) noexcept = default; + WildcardSymbol &operator=(WildcardSymbol &&) noexcept = default; + WildcardSymbol() noexcept = default; + ~WildcardSymbol() noexcept = default; + + const std::string &getSymbol() const noexcept { return symbol; } + + void *toGraal() const noexcept; + + std::string toString() const noexcept { return "WildcardSymbol{" + getSymbol() + "}"; } + + bool operator==(const WildcardSymbol &wildcardSymbol) const { return getSymbol() == wildcardSymbol.getSymbol(); } + + auto operator<(const WildcardSymbol &wildcardSymbol) const { return getSymbol() < wildcardSymbol.getSymbol(); } +}; + +inline namespace literals { + +inline WildcardSymbol operator""_ws(const char *string, size_t length) noexcept { return WildcardSymbol::ALL; } + +inline WildcardSymbol operator""_wcs(const char *string, size_t length) noexcept { return WildcardSymbol::ALL; } + +} // namespace literals + +} // namespace dxfcpp + +template <> struct std::hash { + std::size_t operator()(const dxfcpp::WildcardSymbol &wildcardSymbol) const noexcept { + return std::hash{}(wildcardSymbol.getSymbol()); + } +}; \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/entity/SharedEntity.hpp b/include/dxfeed_graal_cpp_api/entity/SharedEntity.hpp index 439d8f82..e075ce82 100644 --- a/include/dxfeed_graal_cpp_api/entity/SharedEntity.hpp +++ b/include/dxfeed_graal_cpp_api/entity/SharedEntity.hpp @@ -50,6 +50,13 @@ struct SharedEntity : public Entity, std::enable_shared_from_this template std::shared_ptr sharedAs() const noexcept { return std::dynamic_pointer_cast(shared_from_this()); } + + /** + * Returns a string representation of the current object. + * + * @return a string representation + */ + virtual std::string toString() const noexcept { return "SharedEntity{}"; } }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/EventFlag.hpp b/include/dxfeed_graal_cpp_api/event/EventFlag.hpp index 76c79d0e..681756f9 100644 --- a/include/dxfeed_graal_cpp_api/event/EventFlag.hpp +++ b/include/dxfeed_graal_cpp_api/event/EventFlag.hpp @@ -200,9 +200,11 @@ class EventFlag final { */ template bool in(const EventFlagsMask &eventFlagsMask) const +#if __cpp_concepts requires requires { - { eventFlagsMask.getMask() } -> std::same_as; - } + { eventFlagsMask.getMask() } -> std::same_as; + } +#endif { return in(eventFlagsMask.getMask()); } diff --git a/include/dxfeed_graal_cpp_api/event/EventType.hpp b/include/dxfeed_graal_cpp_api/event/EventType.hpp index cbd50251..56fd6c61 100644 --- a/include/dxfeed_graal_cpp_api/event/EventType.hpp +++ b/include/dxfeed_graal_cpp_api/event/EventType.hpp @@ -57,12 +57,8 @@ struct EventType : public SharedEntity { // The default implementation is empty }; - /** - * Returns a string representation of the current object. - * - * @return a string representation - */ - virtual std::string toString() const noexcept { return "EventType{}"; } + /// + std::string toString() const noexcept override { return "EventType{}"; } friend std::ostream &operator<<(std::ostream &os, const EventType &e) { return os << e.toString(); } @@ -72,7 +68,9 @@ struct EventType : public SharedEntity { template friend std::ostream &operator<<(std::ostream &os, const std::shared_ptr &e) +#if __cpp_concepts requires(std::is_base_of_v) +#endif { return os << e->toString(); } diff --git a/include/dxfeed_graal_cpp_api/event/market/OptionSale.hpp b/include/dxfeed_graal_cpp_api/event/market/OptionSale.hpp index 3bf0cae0..04fa3333 100644 --- a/include/dxfeed_graal_cpp_api/event/market/OptionSale.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/OptionSale.hpp @@ -21,7 +21,23 @@ namespace dxfcpp { struct EventMapper; -// TODO: implement +/** + * Option Sale event represents a trade or another market event with the price + * (for example, market open/close price, etc.) for each option symbol listed under the specified Underlying. + * Option Sales are intended to provide information about option trades in a continuous time slice with + * the additional metrics, like Option Volatility, Option Delta, and Underlying Price. + * + *

Option Sale events have unique @ref ::getIndex() "index" which can be used for later + * correction/cancellation processing. + * + * Option sale data source provides a consistent view of the set of known option sales. + * The corresponding information is carried in @ref ::getEventFlags() "eventFlags" property. + * The logic behind this property is detailed in IndexedEvent class documentation. + * Multiple event sources for the same symbol are not supported for option sale events, thus + * @ref ::getSource() "source" property is always @ref IndexedEventSource::DEFAULT "DEFAULT". + * + * This event is implemented on top of QDS records `OptionSale`. + */ class OptionSale final : public MarketEvent, public IndexedEvent { friend struct EventMapper; @@ -500,26 +516,7 @@ class OptionSale final : public MarketEvent, public IndexedEvent { * * @return a string representation */ - std::string toString() const noexcept { - return fmt::format( - "OptionSale{{{}, eventTime={}, eventFlags={:#x}, index={:#x}, time={}, timeNanoPart={}, sequence={}, " - "exchange={}, price={}, size={}, bid={}, ask={}, ESC='{}', TTE={}, side={}, spread={}, ETH={}, " - "validTick={}, type={}, underlyingPrice={}, volatility={}, delta={}, optionSymbol='{}'}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - getEventFlags().getMask(), getIndex(), formatTimeStampWithMillis(getTime()), getTimeNanoPart(), - getSequence(), string_util::encodeChar(getExchangeCode()), getPrice(), getSize(), getBidPrice(), - getAskPrice(), getExchangeSaleConditions(), string_util::encodeChar(getTradeThroughExempt()), - getAggressorSide().toString(), isSpreadLeg(), isExtendedTradingHours(), isValidTick(), getType().toString(), - getUnderlyingPrice(), getVolatility(), getDelta(), getOptionSymbol()); - } - - template friend OStream &operator<<(OStream &os, const OptionSale &e) { - return os << e.toString(); - } - - template friend OStream &operator<<(OStream &os, const std::shared_ptr &e) { - return os << e->toString(); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/market/OrderAction.hpp b/include/dxfeed_graal_cpp_api/event/market/OrderAction.hpp index 86831786..3b1def7d 100644 --- a/include/dxfeed_graal_cpp_api/event/market/OrderAction.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/OrderAction.hpp @@ -1,8 +1,4 @@ -// -// Created by ttldt on 31.03.2023. -// +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 -#ifndef DXFEEDNATIVEAPI_ORDERACTION_HPP -#define DXFEEDNATIVEAPI_ORDERACTION_HPP -// TODO: implement -#endif // DXFEEDNATIVEAPI_ORDERACTION_HPP +#pragma once \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/market/Profile.hpp b/include/dxfeed_graal_cpp_api/event/market/Profile.hpp index a0e20eda..d322f365 100644 --- a/include/dxfeed_graal_cpp_api/event/market/Profile.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/Profile.hpp @@ -22,7 +22,6 @@ struct EventMapper; * Profile information snapshot that contains security instrument description. * It represents the most recent information that is available about the traded security * on the market at any given moment of time. - * */ class Profile final : public MarketEvent, public LastingEvent { friend struct EventMapper; @@ -349,18 +348,7 @@ class Profile final : public MarketEvent, public LastingEvent { * * @return a string representation */ - std::string toString() const noexcept override { - return fmt::format("Profile{{{}, eventTime={}, description='{}', SSR={}, status={}, statusReason='{}', " - "haltStartTime={}, haltEndTime={}, highLimitPrice={}, lowLimitPrice={}, high52WeekPrice={}, " - "low52WeekPrice={}, beta={}, earningsPerShare={}, dividendFrequency={}, " - "exDividendAmount={}, exDividendDay={}, shares={}, freeFloat={}}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - getDescription(), getShortSaleRestriction().toString(), getTradingStatus().toString(), - getStatusReason(), formatTimeStamp(getHaltStartTime()), formatTimeStamp(getHaltEndTime()), - getHighLimitPrice(), getLowLimitPrice(), getHigh52WeekPrice(), getLow52WeekPrice(), - getBeta(), getEarningsPerShare(), getDividendFrequency(), getExDividendAmount(), - day_util::getYearMonthDayByDayId(getExDividendDayId()), getShares(), getFreeFloat()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/market/Quote.hpp b/include/dxfeed_graal_cpp_api/event/market/Quote.hpp index 13b2ec0e..23f8e42d 100644 --- a/include/dxfeed_graal_cpp_api/event/market/Quote.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/Quote.hpp @@ -285,15 +285,7 @@ class Quote final : public MarketEvent, public LastingEvent { * * @return a string representation */ - std::string toString() const noexcept override { - return fmt::format( - "Quote{{{}, eventTime={}, time={}, timeNanoPart={}, sequence={}, bidTime={}, bidExchange={}, bidPrice={}, " - "bidSize={}, askTime={}, askExchange={}, askPrice={}, askSize={}}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - formatTimeStampWithMillis(getTime()), getTimeNanoPart(), getSequence(), formatTimeStamp(getBidTime()), - string_util::encodeChar(getBidExchangeCode()), getBidPrice(), getBidSize(), formatTimeStamp(getAskTime()), - string_util::encodeChar(getAskExchangeCode()), getAskPrice(), getAskSize()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/market/Summary.hpp b/include/dxfeed_graal_cpp_api/event/market/Summary.hpp index cf7c53df..da170bc6 100644 --- a/include/dxfeed_graal_cpp_api/event/market/Summary.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/Summary.hpp @@ -244,16 +244,7 @@ class Summary final : public MarketEvent, public LastingEvent { * * @return a string representation */ - std::string toString() const noexcept override { - return fmt::format("Summary{{{}, eventTime={}, day={}, dayOpen={}, dayHigh={}, dayLow='{}', " - "dayClose={}, dayCloseType={}, prevDay={}, prevDayClose={}, prevDayCloseType={}, " - "prevDayVolume={}, openInterest={}}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - day_util::getYearMonthDayByDayId(getDayId()), getDayOpenPrice(), getDayHighPrice(), - getDayLowPrice(), getDayLowPrice(), getDayClosePrice(), getDayClosePriceType().toString(), - day_util::getYearMonthDayByDayId(getPrevDayId()), getPrevDayClosePrice(), - getPrevDayClosePriceType().toString(), getPrevDayVolume(), getOpenInterest()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/market/TimeAndSale.hpp b/include/dxfeed_graal_cpp_api/event/market/TimeAndSale.hpp index 59a5a054..ea45b03c 100644 --- a/include/dxfeed_graal_cpp_api/event/market/TimeAndSale.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/TimeAndSale.hpp @@ -21,7 +21,29 @@ namespace dxfcpp { struct EventMapper; class OptionSale; -// TODO: doc +/** + * Time and Sale represents a trade or other market event with price, like market open/close price, etc. + * Time and Sales are intended to provide information about trades in a continuous time slice + * (unlike Trade events which are supposed to provide snapshot about the current last trade). + * + *

Time and Sale events have unique @ref ::getIndex() "index" which can be used for later + * correction/cancellation processing. + * + * Some time and sale sources provide a consistent view of the set of known time and sales + * for a given time range when used with DXFeedTimeSeriesSubscription}. + * The corresponding information is carried in @ref ::getEventFlags() "eventFlags" property. + * The logic behind this property is detailed in IndexedEvent class documentation. + * Multiple event sources for the same symbol are not supported for time and sales, thus + * @ref ::getSource() "source" property is always @ref IndexedEventSource::DEFAULT "DEFAULT". + * + *

Regular subscription via DXFeedSubscription produces a stream of time and + * sale events as they happen and their @ref ::getEventFlags() "eventFlags" are always zero. + * + * Publishing of time and sales events follows the general rules explained in TimeSeriesEvent class + * documentation. + * + * This event is implemented on top of QDS record `TimeAndSale`. + */ class TimeAndSale final : public MarketEvent, public TimeSeriesEvent { friend struct EventMapper; friend class OptionSale; @@ -468,19 +490,7 @@ class TimeAndSale final : public MarketEvent, public TimeSeriesEvent { * * @return a string representation */ - std::string toString() const noexcept override { - return fmt::format("TimeAndSale{{{}, eventTime={}, eventFlags={:#x}, time={}, timeNanoPart={}, sequence={}, " - "exchange={}, price={}, size={}, bid={}, " - "ask={}, ESC='{}', TTE={}, side={}, spread={}, ETH={}, validTick={}, type={}{}{}}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - getEventFlags().getMask(), formatTimeStampWithMillis(getTime()), getTimeNanoPart(), - getSequence(), string_util::encodeChar(getExchangeCode()), getPrice(), getSize(), - getBidPrice(), getAskPrice(), getExchangeSaleConditions(), - string_util::encodeChar(getTradeThroughExempt()), getAggressorSide().toString(), - isSpreadLeg(), isExtendedTradingHours(), isValidTick(), getType().toString(), - getBuyer().empty() ? std::string{} : fmt::format(", buyer='{}'", getBuyer()), - getSeller().empty() ? std::string{} : fmt::format(", seller='{}'", getSeller())); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/market/Trade.hpp b/include/dxfeed_graal_cpp_api/event/market/Trade.hpp index 167c7e5a..61532ce9 100644 --- a/include/dxfeed_graal_cpp_api/event/market/Trade.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/Trade.hpp @@ -18,8 +18,54 @@ namespace dxfcpp { struct EventMapper; -// TODO: doc - +/** + * Trade event is a snapshot of the price and size of the last trade during regular trading hours + * and an overall day volume and day turnover. + * It represents the most recent information that is available about the regular last trade on the market + * at any given moment of time. + * + *

Trading sessions

+ * + * The Trade event defines last trade @ref ::getPrice() "price" as officially defined + * by the corresponding exchange for its regular trading hours (RTH). + * It also include an official exchange @ref ::getDayVolume() "dayVolume" and @ref ::getDayTurnover() "dayTurnover" + * for the whole trading day identified by @ref ::getDayId() "dayId". + * So, Trade event captures all the official numbers that are typically reported by exchange. + * + *

Trades that happen in extended trading hours (ETH, pre-market and post-market trading sessions), + * which are typically defined for stocks and ETFs, do not update last trade @ref ::getTime() "time", + * @ref ::getExchangeCode() "exchangeCode", @ref ::getPrice() "price", @ref ::getChange() "change", + * @ref ::getSize() "size", and @ref ::getTickDirection() "tickDirection" in the Trade event, but they do update + * @ref ::getDayVolume() "dayVolume" and @ref ::getDayTurnover() "dayTurnover". + * + *

During extended trading hours a TradeETH event is generated on each trade with its + * @ref TradeETH::isExtendedTradingHours() "extendedTradingHours" property set to `true`.

+ * + *

Volume and Turnover

+ * + *

The volume and turnover are included into the Trade event instead + * of Summary event, because both volume and turnover typically update with each trade. + * The @ref ::getDayId() "dayId" field identifies current trading day for which volume and turnover statistics are computed. + * This solution avoids generation of multiple events on each trade during regular trading hours. + * Summary event is generated during the trading day only when new highs or lows are reached or other properties change. + * + *

Note that one can compute volume-weighted average price (VWAP) for a day by this formula: + *
vwap = @ref ::getDayTurnover() "dayTurnover" / @ref ::getDayVolume() "dayVolume"; + * + *

Daily reset

+ * + * Daily reset procedure that happens on a schedule during non-trading hours resets Trade + * @ref ::getDayVolume() "dayVolume" and @ref ::getDayTurnover() "dayTurnover" to math::NaN + * and sets @ref ::getDayId() "dayId" to the next trading day in preparation to the next day's pre-market trading session + * (or for regular trading if there is no pre-market) while leaving all other properties intact. + * They reflect information about the last known RTH trade until the next RTH trade happens. + * + *

Implementation details

+ * + * This event is implemented on top of QDS records `Trade` and `Trade&X` + * for regional exchange trades. + * Regional records do not explicitly store a field for @ref #getExchangeCode() "exchangeCode" property. + */ class Trade final : public TradeBase { friend struct EventMapper; @@ -43,7 +89,7 @@ class Trade final : public TradeBase { * * @return a string representation */ - std::string toString() const noexcept override { return fmt::format("Trade{{{}}}", baseFieldsToString()); } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/market/TradeBase.hpp b/include/dxfeed_graal_cpp_api/event/market/TradeBase.hpp index 71e489d6..feb24665 100644 --- a/include/dxfeed_graal_cpp_api/event/market/TradeBase.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/TradeBase.hpp @@ -74,7 +74,9 @@ class TradeBase : public MarketEvent, public LastingEvent { template static std::shared_ptr fromGraalNative(void *graalNative) noexcept +#if __cpp_concepts requires(std::is_base_of_v) +#endif { if (!graalNative) { return {}; @@ -360,16 +362,7 @@ class TradeBase : public MarketEvent, public LastingEvent { * * @return string representation of this trade event's fields. */ - std::string baseFieldsToString() const noexcept { - return fmt::format("{}, eventTime={}, time={}, timeNanoPart={}, sequence={}, exchange={}, price={}, " - "change={}, size={}, day={}, dayVolume={}, dayTurnover={}, " - "direction={}, ETH={}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - formatTimeStampWithMillis(getTime()), getTimeNanoPart(), getSequence(), - string_util::encodeChar(getExchangeCode()), getPrice(), getChange(), getSize(), - day_util::getYearMonthDayByDayId(getDayId()), getDayVolume(), getDayTurnover(), - getTickDirection().toString(), isExtendedTradingHours()); - } + std::string baseFieldsToString() const noexcept; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/market/TradeETH.hpp b/include/dxfeed_graal_cpp_api/event/market/TradeETH.hpp index 6c537917..6e563a8e 100644 --- a/include/dxfeed_graal_cpp_api/event/market/TradeETH.hpp +++ b/include/dxfeed_graal_cpp_api/event/market/TradeETH.hpp @@ -18,8 +18,77 @@ namespace dxfcpp { struct EventMapper; -// TODO: doc - +/** + * TradeETH event is a snapshot of the price and size of the last trade during + * extended trading hours and the extended trading hours day volume and day turnover. + * This event is defined only for symbols (typically stocks and ETFs) with a designated + * extended trading hours (ETH, pre market and post market trading sessions). + * It represents the most recent information that is available about + * ETH last trade on the market at any given moment of time. + * + *

Trading sessions

+ * + * The TradeETH event defines last trade @ref ::getPrice() "price" as officially defined + * by the corresponding exchange for its extended trading hours (ETH). + * It also includes @ref ::getDayVolume() "dayVolume" and @ref ::getDayTurnover() "dayTurnover" + * for the extended trading hours only of the trading day identified by @ref ::getDayId "dayId". + * This event is not defined for symbols that has no concept of ETH. + * + *

When the first trade of regular trading hours (RTH) happens, then TradeETH event is generated + * with @ref ::isExtendedTradingHours() "extendedTradingHours" property set to `false`. Afterwards, during RTH, + * TradeETH event is not updated and retains information about the last trade, volume and turnover of the pre market trading session. + * + *

When the first trade of extended trading hours (ETH) happens, then TradeETH event is generated + * with @ref ::isExtendedTradingHours() "extendedTradingHours" property set to `true`. Afterwards, during ETH, + * TradeETH event is updated on each trade with the last trade information from post market trading session + * and total volume and turnover of the pre and post market trading session (excluding the volume and turnover of a regular trading session). + * + * Note, that during pre- and post-market sessions, Trade event also updates, but only its + * @ref Trade::getDayVolume() "dayVolume" and @ref Trade::getDayTurnover() "dayTurnover" properties change + * to reflect the overall official volume and turnover as reported by exchanges. + * During post market trading session, exchanges may correct their official RTH last trading price, which results + * in the update to Trade event. + * + *

Volume and Turnover

+ * + *

Note that one can compute volume-weighted average price (VWAP) for extended trading hours by this formula: + *
vwap = @ref ::getDayTurnover() "dayTurnover" / @ref ::getDayVolume() "dayVolume"; + * + *

Daily reset

+ * + * Daily reset procedure that happens on a schedule during non-trading hours resets TradeETH + * @ref #getDayVolume() "dayVolume" and @ref ::getDayTurnover() "dayTurnover" to math::NaN + * and sets @ref ::getDayId() "dayId" to the next trading day in preparation to the next day's pre-market trading session + * (or for regular trading if there is no pre-market) while leaving all other properties intact. + * They reflect information about the last known ETH trade until the next ETH trade happens. + * + *

The most recent last trade price

+ * + * The most recent last trade price ("extended last price") in the market can be found by combining information from both + * Trade and TradeETH events using @ref ::isExtendedTradingHours() "isExtendedTradingHours" method to figure out + * which trading session had the most recent trade. The following piece of code finds the most + * recent last trade price from the given @ref DXFeed "feed" for a given `symbol`, + * assuming there is a @ref DXFeedSubscription "subscription" for both Trade and TradeETH events + * for the given `symbol`: + * + *

+ *     auto @ref Trade "trade" = feed->@ref DXFeed::getLastEvent() "getLastEvent"(Trade::create(symbol));
+ *     auto @ref TradeETH "tradeEth" = feed->@ref DXFeed::getLastEvent() "getLastEvent"(TradeETH::create(symbol));
+ *     double extLast = tradeEth->@ref ::isExtendedTradingHours() "isExtendedTradingHours"() ? tradeEth->@ref ::getPrice() "getPrice"() : trade->@ref Trade::getPrice() "getPrice"();
+ * 
+ * + * Note, that the above code works correctly for symbols that has no concept of ETH, too, because in this + * case the DXFeed::getLastEvent() leaves default values in TradeETH + * event properties, which means that @ref ::isExtendedTradingHours() "extendedTradingHours" flag is `false` + * and a regular Trade::getPrice() is used. + * + *

Implementation details

+ * + * This event is implemented on top of QDS record `TradeETH` and `TradeETH&X` + * for regional exchange extended trade hours. + * @ref ::isExtendedTradingHours() "extendedTradingHours" property is internally represented as a last bit of the "Flags" field of the record. + * Regional records do not explicitly store a field for @ref ::getExchangeCode() "exchangeCode" property. + */ class TradeETH final : public TradeBase { friend struct EventMapper; @@ -43,7 +112,7 @@ class TradeETH final : public TradeBase { * * @return a string representation */ - std::string toString() const noexcept override { return fmt::format("TradeETH{{{}}}", baseFieldsToString()); } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/option/Greeks.hpp b/include/dxfeed_graal_cpp_api/event/option/Greeks.hpp index 8348652b..bc602fae 100644 --- a/include/dxfeed_graal_cpp_api/event/option/Greeks.hpp +++ b/include/dxfeed_graal_cpp_api/event/option/Greeks.hpp @@ -271,14 +271,7 @@ class Greeks final : public MarketEvent, public TimeSeriesEvent, public LastingE * * @return a string representation */ - std::string toString() const noexcept override { - return fmt::format( - "Greeks{{{}, eventTime={}, eventFlags={:#x}, time={}, sequence={}, price={}, volatility={}, delta={}, " - "gamma={}, theta={}, rho={}, vega={}}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - getEventFlags().getMask(), formatTimeStampWithMillis(getTime()), getSequence(), getPrice(), getVolatility(), - getDelta(), getGamma(), getTheta(), getRho(), getVega()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/option/Series.hpp b/include/dxfeed_graal_cpp_api/event/option/Series.hpp index dc78588f..91382f06 100644 --- a/include/dxfeed_graal_cpp_api/event/option/Series.hpp +++ b/include/dxfeed_graal_cpp_api/event/option/Series.hpp @@ -18,7 +18,35 @@ namespace dxfcpp { struct EventMapper; -// TODO: doc +/** + * Series event is a snapshot of computed values that are available for all option series for + * a given underlying symbol based on the option prices on the market. + * It represents the most recent information that is available about the corresponding values on + * the market at any given moment of time. + * + *

Series is an IndexedEvent with multiple instances of event available for + * each underlying symbol. Each series event instance corresponds to an OptionSeries + * of the corresponding underlying. The correspondence between a series event instance and + * an OptionSeries is established via @ref ::getExpiration() "expiration" property. + * If case where there are multiple series at the same expiration day id, then series events are + * are ordered by their @ref #getIndex() "index" in the same order as the corresponding + * OptionSeries are @ref OptionSeries::compareTo(OptionSeries) "ordered" by their attributes. + * + *

Event flags, transactions and snapshots

+ * + * Series data source provides a consistent view of the set of known series. + * The corresponding information is carried in @ref ::getEventFlags() "eventFlags" property. + * The logic behind this property is detailed in IndexedEvent class documentation. + * Multiple event sources for the same symbol are not supported for series, thus + * @ref ::getSource() "source" property is always @ref IndexedEventSource::DEFAULT "DEFAULT". + * + *

IndexedEventModel class handles all the snapshot and transaction logic and conveniently represents + * a list current of events. + * + *

Implementation details

+ * + * This event is implemented on top of QDS records `Series`. + */ class Series final : public MarketEvent, public IndexedEvent { friend struct EventMapper; @@ -304,15 +332,7 @@ class Series final : public MarketEvent, public IndexedEvent { * * @return a string representation */ - std::string toString() const noexcept override { - return fmt::format( - "Series{{{}, eventTime={}, eventFlags={:#x}, index={:#x}, time={}, sequence={}, expiration={}, " - "volatility={}, callVolume={}, putVolume={}, putCallRatio={}, forwardPrice={}, dividend={}, interest={}}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - getEventFlags().getMask(), getIndex(), formatTimeStampWithMillis(getTime()), getSequence(), - day_util::getYearMonthDayByDayId(getExpiration()), getVolatility(), getCallVolume(), getPutVolume(), - getPutCallRatio(), getForwardPrice(), getDividend(), getInterest()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/option/TheoPrice.hpp b/include/dxfeed_graal_cpp_api/event/option/TheoPrice.hpp index d2f7bc06..f72554fd 100644 --- a/include/dxfeed_graal_cpp_api/event/option/TheoPrice.hpp +++ b/include/dxfeed_graal_cpp_api/event/option/TheoPrice.hpp @@ -19,7 +19,36 @@ namespace dxfcpp { struct EventMapper; -// TODO: doc +/** + * Theo price is a snapshot of the theoretical option price computation that is + * periodically performed by dxPrice + * model-free computation. + * It represents the most recent information that is available about the corresponding + * values at any given moment of time. + * The values include first and second order derivative of the price curve by price, so that + * the real-time theoretical option price can be estimated on real-time changes of the underlying + * price in the vicinity. + * + *

Event flags, transactions and snapshots

+ * + * Some TheoPrice sources provide a consistent view of the set of known TheoPrice. + * The corresponding information is carried in @ref ::getEventFlags() "eventFlags" property. + * The logic behind this property is detailed in IndexedEvent class documentation. + * Multiple event sources for the same symbol are not supported for TheoPrice, thus + * @ref ::getSource() "source" property is always @ref IndexedEventSource::DEFAULT "DEFAULT". + * + *

TimeSeriesEventModel class handles all the snapshot and transaction logic and conveniently represents + * a list current of time-series events order by their @ref ::getTime() "time". + * + *

Publishing TheoPrice

+ * + * Publishing of TheoPrice events follows the general rules explained in TimeSeriesEvent class + * documentation. + * + *

Implementation details

+ * + * This event is implemented on top of QDS records `TheoPrice`. + */ class TheoPrice final : public MarketEvent, public TimeSeriesEvent, public LastingEvent { friend struct EventMapper; @@ -237,14 +266,7 @@ class TheoPrice final : public MarketEvent, public TimeSeriesEvent, public Lasti * * @return a string representation */ - std::string toString() const noexcept override { - return fmt::format( - "TheoPrice{{{}, eventTime={}, eventFlags={:#x}, time={}, sequence={}, price={}, underlyingPrice={}, " - "delta={}, gamma={}, dividend={}, interest={}}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - getEventFlags().getMask(), formatTimeStampWithMillis(getTime()), getSequence(), getPrice(), - getUnderlyingPrice(), getDelta(), getGamma(), getDividend(), getInterest()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/event/option/Underlying.hpp b/include/dxfeed_graal_cpp_api/event/option/Underlying.hpp index 6c4a2098..d1ff436c 100644 --- a/include/dxfeed_graal_cpp_api/event/option/Underlying.hpp +++ b/include/dxfeed_graal_cpp_api/event/option/Underlying.hpp @@ -19,7 +19,32 @@ namespace dxfcpp { struct EventMapper; -// TODO: doc +/** + * Underlying event is a snapshot of computed values that are available for an option underlying + * symbol based on the option prices on the market. + * It represents the most recent information that is available about the corresponding values on + * the market at any given moment of time. + * + *

Event flags, transactions and snapshots

+ * + * Some Underlying sources provide a consistent view of the set of known Underlying events. + * The corresponding information is carried in @ref ::getEventFlags() "eventFlags" property. + * The logic behind this property is detailed in IndexedEvent class documentation. + * Multiple event sources for the same symbol are not supported for Underlying, thus + * @ref ::getSource() "source" property is always @ref IndexedEventSource::DEFAULT "DEFAULT". + * + *

TimeSeriesEventModel class handles all the snapshot and transaction logic and conveniently represents + * a list current of time-series events order by their @ref ::getTime() "time". + * + *

Publishing Underlying

+ * + * Publishing of Underlying events follows the general rules explained in TimeSeriesEvent class + * documentation. + * + *

Implementation details

+ * + * This event is implemented on top of QDS record `Underlying`. + */ class Underlying final: public MarketEvent, public TimeSeriesEvent, public LastingEvent { friend struct EventMapper; @@ -252,14 +277,7 @@ class Underlying final: public MarketEvent, public TimeSeriesEvent, public Lasti * * @return a string representation */ - std::string toString() const noexcept override { - return fmt::format( - "Underlying{{{}, eventTime={}, eventFlags={:#x}, time={}, sequence={}, volatility={}, frontVolatility={}, " - "backVolatility={}, callVolume={}, putVolume={}, putCallRatio={}}}", - MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), - getEventFlags().getMask(), formatTimeStampWithMillis(getTime()), getSequence(), getVolatility(), - getFrontVolatility(), getBackVolatility(), getCallVolume(), getPutVolume(), getPutCallRatio()); - } + std::string toString() const noexcept override; }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/CEntryPointErrors.hpp b/include/dxfeed_graal_cpp_api/internal/CEntryPointErrors.hpp index a57a9648..19348778 100644 --- a/include/dxfeed_graal_cpp_api/internal/CEntryPointErrors.hpp +++ b/include/dxfeed_graal_cpp_api/internal/CEntryPointErrors.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "Common.hpp" diff --git a/include/dxfeed_graal_cpp_api/internal/Common.hpp b/include/dxfeed_graal_cpp_api/internal/Common.hpp index 7fc30f46..59f5480f 100644 --- a/include/dxfeed_graal_cpp_api/internal/Common.hpp +++ b/include/dxfeed_graal_cpp_api/internal/Common.hpp @@ -14,13 +14,12 @@ #include #include #include +#include -#include -#include -#include -#include +#include "utils/debug/Debug.hpp" namespace dxfcpp { + template concept Integral = std::is_integral_v; @@ -28,20 +27,6 @@ struct DXFeedEventListener {}; struct DXEndpointStateChangeListener {}; -#if defined(NDEBUG) && !defined(DXFCPP_DEBUG) -constexpr bool isDebug = false; -constexpr bool isDebugIsolates = false; -#else -constexpr bool isDebug = true; - -# ifdef DXFCPP_DEBUG_ISOLATES -constexpr bool isDebugIsolates = true; -# else -constexpr bool isDebugIsolates = false; -# endif - -#endif - #if defined(__clang__) constexpr bool isClangFlavouredCompiler = true; #else @@ -62,7 +47,9 @@ constexpr inline auto is_constant_evaluated(bool default_value = false) noexcept // Implementation of std::bit_cast for pre-C++20. template constexpr To bit_cast(const From &from) +#if __cpp_concepts requires(sizeof(To) == sizeof(From)) +#endif { #ifdef __cpp_lib_bit_cast if (is_constant_evaluated()) @@ -106,115 +93,6 @@ inline auto now() { .count(); } -template std::string vformat(std::string_view format, Args &&...args) { - return fmt::vformat(format, fmt::make_format_args(args...)); -} - -template void vprint(std::ostream &os, std::string_view format, Args &&...args) { - fmt::vprint(os, format, fmt::make_format_args(args...)); -} - -inline std::string nowStr() { - auto now = std::chrono::system_clock::now(); - auto ms = std::chrono::duration_cast(now.time_since_epoch()).count() % 1000; - - return fmt::format("{:%y%m%d %H%M%S}.{:0>3}", std::chrono::floor(now), ms); -} - -inline std::string nowStrWithTimeZone() { - auto now = std::chrono::system_clock::now(); - auto ms = std::chrono::duration_cast(now.time_since_epoch()).count() % 1000; - - return fmt::format("{:%y%m%d-%H%M%S}.{:0>3}{:%z}", std::chrono::floor(now), ms, - std::chrono::floor(now)); -} - -inline std::string formatTimeStamp(std::int64_t timestamp) { - auto tm = fmt::localtime(static_cast(timestamp / 1000)); - - return fmt::format("{:%y%m%d-%H%M%S%z}", tm); -} - -inline std::string formatTimeStampWithMillis(std::int64_t timestamp) { - auto ms = timestamp % 1000; - auto tm = fmt::localtime(static_cast(timestamp / 1000)); - - return fmt::format("{:%y%m%d-%H%M%S}.{:0>3}{:%z}", tm, ms, tm); -} - -inline std::string debugPrefixStr() { - std::ostringstream tid{}; - - tid << std::this_thread::get_id(); - - return fmt::format("D {} [{}]", nowStr(), tid.str()); -} - -template inline void debug(std::string_view format, Args &&...args) { - vprint(std::cerr, "{} {}\n", debugPrefixStr(), vformat(format, std::forward(args)...)); -} - -namespace handler_utils { - -template struct JavaObjectHandler { - using Type = T; - static void deleter(void *handler) noexcept; - explicit JavaObjectHandler(void *handler = nullptr) noexcept : impl_{handler, &deleter} {} - - JavaObjectHandler(JavaObjectHandler &&) = default; - JavaObjectHandler &operator=(JavaObjectHandler &&) = default; - virtual ~JavaObjectHandler() noexcept = default; - - [[nodiscard]] std::string toString() const noexcept { - if (impl_) - return fmt::format("{}", impl_.get()); - else - return "nullptr"; - } - - [[nodiscard]] void *get() const noexcept { return impl_.get(); } - - explicit operator bool() const noexcept { return static_cast(impl_); } - - private: - std::unique_ptr impl_; -}; - -struct EventClassList { - static std::unique_ptr create(std::size_t size) noexcept; - - void set(std::size_t index, std::uint32_t id) noexcept; - - [[nodiscard]] bool isEmpty() const noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - - void *getHandler() noexcept; - - ~EventClassList() noexcept; - - private: - EventClassList() noexcept; - - struct Impl; - - std::unique_ptr impl_; -}; - -} // namespace handler_utils - -template - requires requires { std::is_same_v getName())>, std::string>; } -std::string namesToString(It begin, It end) { - std::string result{"["}; - - for (auto it = begin; it != end; it++) { - result += fmt::format("'{}'{}", it->getName(), std::next(it) == end ? "" : ", "); - } - - return result + "]"; -} - template inline void callWithLock(M &mtx, F &&f, Args &&...args) noexcept { std::once_flag once{}; @@ -339,23 +217,6 @@ static constexpr std::int32_t getSecondsFromTime(std::int64_t timeMillis) { } } // namespace time_util -namespace string_util { -static std::string encodeChar(std::int16_t c) { - if (c >= 32 && c <= 126) { - return std::string{} + static_cast(c); - } - - if (c == 0) { - return "\\0"; - } - - return fmt::format("\\u{:04x}", c); -} - -static std::string encodeChar(char c) { return encodeChar(static_cast(static_cast(c))); } - -} // namespace string_util - namespace math_util { /** @@ -732,54 +593,4 @@ template static constexpr F set } } -std::string toString(const char *chars); - -char utf16to8(std::int16_t in); - -std::int16_t utf8to16(char in); - -struct String { - inline static const std::string EMPTY{}; -}; - -template struct Id { - using ValueType = std::size_t; - - private: - const ValueType value_{}; - - explicit Id(ValueType value) : value_{value} {} - - public: - static Id getNext() { - static std::atomic value{}; - - return Id{value++}; - } - - [[nodiscard]] ValueType getValue() const { return value_; } - - explicit operator ValueType() const { return value_; } - - static Id from(ValueType value) { return Id{value}; } - - template bool operator==(const Id &id) const { return getValue() == id.getValue(); } - - template auto operator<=>(const Id &id) const { return getValue() <=> id.getValue(); } -}; - -template class NonCopyable { - public: - NonCopyable(const NonCopyable &) = delete; - T &operator=(const T &) = delete; - - protected: - NonCopyable() = default; - ~NonCopyable() = default; -}; - } // namespace dxfcpp - -template struct std::hash> { - std::size_t operator()(const dxfcpp::Id &id) const noexcept { return id.getValue(); } -}; \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/Enum.hpp b/include/dxfeed_graal_cpp_api/internal/Enum.hpp index f7b9070a..1ffcf65c 100644 --- a/include/dxfeed_graal_cpp_api/internal/Enum.hpp +++ b/include/dxfeed_graal_cpp_api/internal/Enum.hpp @@ -94,6 +94,7 @@ template struct Enum { return found->second; } + //TODO: try to implement C++11-like code for this if constexpr (requires { Child::getDefault(); }) { return Child::getDefault(); } else { diff --git a/include/dxfeed_graal_cpp_api/internal/EventClassList.hpp b/include/dxfeed_graal_cpp_api/internal/EventClassList.hpp new file mode 100644 index 00000000..b4205159 --- /dev/null +++ b/include/dxfeed_graal_cpp_api/internal/EventClassList.hpp @@ -0,0 +1,56 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include +#include + +namespace dxfcpp { + +struct EventClassList { + template + static std::unique_ptr create(EventTypeIt begin, EventTypeIt end) noexcept { + auto size = std::distance(begin, end); + + if (size <= 0) { + return {}; + } + + auto list = create(size); + + if (!list || list->isEmpty()) { + return {}; + } + + std::size_t i = 0; + + for (auto it = begin; it != end; it++, i++) { + list->set(i, it->getId()); + } + + return list; + } + + void set(std::size_t index, std::uint32_t id) noexcept; + + [[nodiscard]] bool isEmpty() const noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + + void *getHandler() noexcept; + + ~EventClassList() noexcept; + + private: + static std::unique_ptr create(std::size_t size) noexcept; + + EventClassList() noexcept; + + struct Impl; + + std::unique_ptr impl_; +}; + +} \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/Handler.hpp b/include/dxfeed_graal_cpp_api/internal/Handler.hpp index dc99ae84..7bf8871b 100644 --- a/include/dxfeed_graal_cpp_api/internal/Handler.hpp +++ b/include/dxfeed_graal_cpp_api/internal/Handler.hpp @@ -41,6 +41,7 @@ template struct Handler final { /// The listener type using ListenerType = std::function; + static constexpr std::size_t FAKE_ID{static_cast(-1)}; private: static constexpr unsigned MAIN_FUTURES_DEFAULT_SIZE = 1024; @@ -121,6 +122,10 @@ struct Handler final { std::size_t add(ListenerType &&listener) { std::lock_guard guard{listenersMutex_}; + if (lastId_ >= FAKE_ID - 1) { + return FAKE_ID; + } + lastId_++; listeners_.emplace(lastId_, std::forward(listener)); @@ -137,6 +142,10 @@ struct Handler final { std::size_t addLowPriority(ListenerType &&listener) { std::lock_guard guard{listenersMutex_}; + if (lastId_ >= FAKE_ID - 1) { + return FAKE_ID; + } + lastId_++; lowPriorityListeners_.emplace(lastId_, std::forward(listener)); @@ -168,6 +177,10 @@ struct Handler final { void remove(std::size_t id) { std::lock_guard guard{listenersMutex_}; + if (id == FAKE_ID) { + return; + } + if (listeners_.count(id) > 0) { listeners_.erase(id); } else if (lowPriorityListeners_.count(id) > 0) { diff --git a/include/dxfeed_graal_cpp_api/internal/Id.hpp b/include/dxfeed_graal_cpp_api/internal/Id.hpp new file mode 100644 index 00000000..a2c7bf04 --- /dev/null +++ b/include/dxfeed_graal_cpp_api/internal/Id.hpp @@ -0,0 +1,40 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include + +namespace dxfcpp { + +template struct Id { + using ValueType = std::size_t; + + private: + const ValueType value_{}; + + explicit Id(ValueType value) : value_{value} {} + + public: + static Id getNext() { + static std::atomic value{}; + + return Id{value++}; + } + + [[nodiscard]] ValueType getValue() const { return value_; } + + explicit operator ValueType() const { return value_; } + + static Id from(ValueType value) { return Id{value}; } + + template bool operator==(const Id &id) const { return getValue() == id.getValue(); } + + template auto operator<=>(const Id &id) const { return getValue() <=> id.getValue(); } +}; +} // namespace dxfcpp + +template struct std::hash> { + std::size_t operator()(const dxfcpp::Id &id) const noexcept { return id.getValue(); } +}; \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/Isolate.hpp b/include/dxfeed_graal_cpp_api/internal/Isolate.hpp index 39fbd89b..a888dcd8 100644 --- a/include/dxfeed_graal_cpp_api/internal/Isolate.hpp +++ b/include/dxfeed_graal_cpp_api/internal/Isolate.hpp @@ -32,25 +32,24 @@ class Isolate final { explicit IsolateThread(GraalIsolateThreadHandle handle = nullptr, bool isMain = false) noexcept : handle{handle}, isMain{isMain}, tid{std::this_thread::get_id()} { - static size_t idx = 0; + this->idx = Id::getNext().getValue(); - this->idx = idx++; - - if constexpr (isDebugIsolates) { - debug("IsolateThread{{{}, isMain = {}, tid = {}, idx = {}}}()", bit_cast(handle), isMain, - tid, idx); + if constexpr (Debugger::traceIsolates) { + Debugger::trace("IsolateThread{" + dxfcpp::toString(bit_cast(handle)) + + ", isMain = " + dxfcpp::toString(isMain) + ", tid = " + dxfcpp::toString(tid) + + ", idx = " + std::to_string(idx) + "}()"); } } CEntryPointErrors detach() noexcept { - if constexpr (isDebugIsolates) { - debug("{}::detach()", toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::detach()"); } // OK if nothing is attached. if (!handle) { - if constexpr (isDebugIsolates) { - debug("\tNot attached"); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::detach(): !handle => Not attached"); } return CEntryPointErrors::NO_ERROR; @@ -59,8 +58,8 @@ class Isolate final { auto result = CEntryPointErrors::valueOf(graal_detach_thread(handle)); if (result == CEntryPointErrors::NO_ERROR) { - if constexpr (isDebugIsolates) { - debug("\tDetached"); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::detach(): result == CEntryPointErrors::NO_ERROR => Detached"); } handle = nullptr; @@ -70,13 +69,13 @@ class Isolate final { } CEntryPointErrors detachAllThreadsAndTearDownIsolate() noexcept { - if constexpr (isDebugIsolates) { - debug("{}::detachAllThreadsAndTearDownIsolate()", toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::detachAllThreadsAndTearDownIsolate()"); } if (!handle) { - if constexpr (isDebugIsolates) { - debug("\tNot attached"); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::detachAllThreadsAndTearDownIsolate(): !handle => Not attached"); } return CEntryPointErrors::NO_ERROR; @@ -85,8 +84,10 @@ class Isolate final { auto result = CEntryPointErrors::valueOf(graal_detach_all_threads_and_tear_down_isolate(handle)); if (result == CEntryPointErrors::NO_ERROR) { - if constexpr (isDebugIsolates) { - debug("\tAll threads have been detached. The isolate has been teared down."); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + + "::detachAllThreadsAndTearDownIsolate(): CEntryPointErrors::NO_ERROR => All " + "threads have been detached. The isolate has been teared down."); } handle = nullptr; @@ -96,13 +97,13 @@ class Isolate final { } ~IsolateThread() noexcept { - if constexpr (isDebugIsolates) { - debug("~{}()", toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::~()"); } if (isMain) { - if constexpr (isDebugIsolates) { - debug("\tThis is the main thread"); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::~(): isMain => This is the main thread"); } return; @@ -112,8 +113,9 @@ class Isolate final { } std::string toString() const { - return fmt::format("IsolateThread{{{}, isMain = {}, tid = {}, idx = {}}}", bit_cast(handle), - isMain, tid, idx); + return std::string("IsolateThread{") + dxfcpp::toString(bit_cast(handle)) + + ", isMain = " + dxfcpp::toString(isMain) + ", tid = " + dxfcpp::toString(tid) + + ", idx = " + std::to_string(idx) + "}"; } }; @@ -128,15 +130,15 @@ class Isolate final { currentIsolateThread_.handle = mainIsolateThreadHandle; currentIsolateThread_.isMain = true; - if constexpr (isDebugIsolates) { - debug("Isolate{{{}, main = {}, current = {}}}()", bit_cast(handle), - mainIsolateThread_.toString(), currentIsolateThread_.toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace("Isolate{" + dxfcpp::toString(bit_cast(handle)) + ", main = " + + mainIsolateThread_.toString() + ", current = " + currentIsolateThread_.toString() + "}()"); } } static std::shared_ptr create() noexcept { - if constexpr (isDebugIsolates) { - debug("Isolate::create()"); + if constexpr (Debugger::traceIsolates) { + Debugger::trace("Isolate::create()"); } GraalIsolateHandle graalIsolateHandle{}; @@ -145,31 +147,31 @@ class Isolate final { if (CEntryPointErrors::valueOf(graal_create_isolate(nullptr, &graalIsolateHandle, &graalIsolateThreadHandle)) == CEntryPointErrors::NO_ERROR) { - auto result = std::shared_ptr{new Isolate{graalIsolateHandle, graalIsolateThreadHandle}}; + auto result = std::shared_ptr{new (std::nothrow) Isolate{graalIsolateHandle, graalIsolateThreadHandle}}; - if constexpr (isDebugIsolates) { - debug("Isolate::create() -> *{}", result->toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace("Isolate::create() -> *" + result->toString()); } return result; } - if constexpr (isDebugIsolates) { - debug("\t-> nullptr"); + if constexpr (Debugger::traceIsolates) { + Debugger::trace("Isolate::create() -> nullptr"); } return nullptr; } CEntryPointErrors attach() noexcept { - if constexpr (isDebugIsolates) { - debug("{}::attach()", toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::attach()"); } // We will not re-attach. if (!currentIsolateThread_.handle) { - if constexpr (isDebugIsolates) { - debug("\tNeeds to be attached."); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::attach(): !currentIsolateThread_.handle => Needs to be attached."); } GraalIsolateThreadHandle newIsolateThreadHandle{}; @@ -177,8 +179,9 @@ class Isolate final { if (auto result = CEntryPointErrors::valueOf(graal_attach_thread(handle_, &newIsolateThreadHandle)); result != CEntryPointErrors::NO_ERROR) { - if constexpr (isDebugIsolates) { - debug("\t-> {}", result.getDescription()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::attach(): result != CEntryPointErrors::NO_ERROR [" + + std::to_string(result.getCode()) + "] " + result.getDescription()); } return result; @@ -187,12 +190,12 @@ class Isolate final { currentIsolateThread_.handle = newIsolateThreadHandle; currentIsolateThread_.isMain = mainIsolateThread_.handle == newIsolateThreadHandle; - if constexpr (isDebugIsolates) { - debug("\tAttached: {}", currentIsolateThread_.toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::attach(): Attached: " + currentIsolateThread_.toString()); } } else { - if constexpr (isDebugIsolates) { - debug("\tCached: {}", currentIsolateThread_.toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::attach(): Cached: " + currentIsolateThread_.toString()); } } @@ -200,8 +203,8 @@ class Isolate final { } GraalIsolateThreadHandle get() noexcept { - if constexpr (isDebugIsolates) { - debug("{}::get()", toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::get()"); } return graal_get_current_thread(handle_); @@ -213,14 +216,14 @@ class Isolate final { Isolate &operator=(const Isolate &) = delete; static std::shared_ptr getInstance() noexcept { - if constexpr (isDebugIsolates) { - debug("Isolate::getInstance()"); + if constexpr (Debugger::traceIsolates) { + Debugger::trace("Isolate::getInstance()"); } static std::shared_ptr instance = create(); - if constexpr (isDebugIsolates) { - debug("Isolate::getInstance() -> *{}", instance->toString()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace("Isolate::getInstance() -> *" + instance->toString()); } return instance; @@ -228,8 +231,8 @@ class Isolate final { template auto runIsolated(F &&f) -> std::variant> { - if constexpr (isDebugIsolates) { - debug("{}::runIsolated({})", toString(), bit_cast(&f)); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::runIsolated(" + typeid(f).name() + ")"); } // Perhaps the code is already running within the GraalVM thread (for example, we are in a listener) @@ -238,8 +241,9 @@ class Isolate final { } if (auto result = attach(); result != CEntryPointErrors::NO_ERROR) { - if constexpr (isDebugIsolates) { - debug("\t-> {}", result.getDescription()); + if constexpr (Debugger::traceIsolates) { + Debugger::trace(toString() + "::runIsolated(" + typeid(f).name() + + "): result != CEntryPointErrors::NO_ERROR -> " + result.getDescription()); } return result; @@ -249,7 +253,9 @@ class Isolate final { } template +#if __cpp_concepts requires std::convertible_to> +#endif auto runIsolatedOrElse(F &&f, R defaultValue) { return std::visit( [defaultValue = @@ -264,23 +270,25 @@ class Isolate final { } ~Isolate() { - if constexpr (isDebugIsolates) { - debug("~Isolate()"); + if constexpr (Debugger::traceIsolates) { + Debugger::trace("~Isolate()"); } } std::string toString() const { std::lock_guard lock(mtx_); - return fmt::format("Isolate{{{}, main = {}, current = {}}}", bit_cast(handle_), - mainIsolateThread_.toString(), currentIsolateThread_.toString()); + return std::string("Isolate{") + dxfcpp::toString(bit_cast(handle_)) + + ", main = " + mainIsolateThread_.toString() + ", current = " + currentIsolateThread_.toString() + "}"; } }; template auto runIsolated(F &&f) { return Isolate::getInstance()->runIsolated(std::forward(f)); } template +#if __cpp_concepts requires std::convertible_to> +#endif auto runIsolatedOrElse(F &&f, R defaultValue) { return Isolate::getInstance()->runIsolatedOrElse(std::forward(f), std::move(defaultValue)); } diff --git a/include/dxfeed_graal_cpp_api/internal/JavaObjectHandler.hpp b/include/dxfeed_graal_cpp_api/internal/JavaObjectHandler.hpp new file mode 100644 index 00000000..5099daaa --- /dev/null +++ b/include/dxfeed_graal_cpp_api/internal/JavaObjectHandler.hpp @@ -0,0 +1,37 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include "utils/StringUtils.hpp" + +#include +#include + +namespace dxfcpp { + +template struct JavaObjectHandler { + using Type = T; + static void deleter(void *handler) noexcept; + explicit JavaObjectHandler(void *handler = nullptr) noexcept : impl_{handler, &deleter} {} + + JavaObjectHandler(JavaObjectHandler &&) = default; + JavaObjectHandler &operator=(JavaObjectHandler &&) = default; + virtual ~JavaObjectHandler() noexcept = default; + + [[nodiscard]] std::string toString() const noexcept { + if (impl_) + return dxfcpp::toString(impl_.get()); + else + return "nullptr"; + } + + [[nodiscard]] void *get() const noexcept { return impl_.get(); } + + explicit operator bool() const noexcept { return static_cast(impl_); } + + private: + std::unique_ptr impl_; +}; + +} // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/NonCopyable.hpp b/include/dxfeed_graal_cpp_api/internal/NonCopyable.hpp new file mode 100644 index 00000000..e90e9438 --- /dev/null +++ b/include/dxfeed_graal_cpp_api/internal/NonCopyable.hpp @@ -0,0 +1,18 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +namespace dxfcpp { + +template class NonCopyable { + public: + NonCopyable(const NonCopyable &) = delete; + T &operator=(const T &) = delete; + + protected: + NonCopyable() = default; + ~NonCopyable() = default; +}; + +} \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/RawListWrapper.hpp b/include/dxfeed_graal_cpp_api/internal/RawListWrapper.hpp new file mode 100644 index 00000000..e2b7228d --- /dev/null +++ b/include/dxfeed_graal_cpp_api/internal/RawListWrapper.hpp @@ -0,0 +1,167 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include +#include +#include + +#include "utils/StringUtils.hpp" + +namespace dxfcpp { + +struct Debugger; + +template +concept RawGraalList = requires(T list) { + { list.size }; + { list.elements }; +}; + +template struct RawGraalListTraits { + using SizeType = typename std::decay_t; + using ElementType = typename std::decay_t>>; +}; + +template struct RawListWrapper { +#if DXFCPP_DEBUG == 1 + static auto getDebugName() { + return std::string("RawListWrapper<") + typeid(List).name() + ", " + typeid(ElementSetter).name() + ">"; + } +#endif + + mutable List list_; + + RawListWrapper() noexcept : list_{0, nullptr} { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::()"); + } + } + + void set(std::size_t index, const auto& value) const noexcept { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::set(" + std::to_string(index) + ", " + std::to_string(value) + ")"); + } + + if (list_.size == 0) { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::set(" + std::to_string(index) + ", " + std::to_string(value) + + "): list_.size == 0"); + } + + return; + } + + if (index < list_.size) { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::set(" + std::to_string(index) + ", " + std::to_string(value) + + "): index < list_.size"); + } + + ElementSetter(list_, index, value); + } + } + + [[nodiscard]] bool isEmpty() const noexcept { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::isEmpty() -> " + dxfcpp::toString(list_.size == 0)); + } + + return list_.size == 0; + } + + [[nodiscard]] std::size_t size() const noexcept { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::size() -> " + std::to_string(static_cast(list_.size))); + } + + return static_cast(list_.size); + } + + void *getHandler() noexcept { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::getHandler() -> " + dxfcpp::toString(bit_cast(&list_))); + } + + return bit_cast(&list_); + } + + void init(std::size_t size) noexcept { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::init(" + std::to_string(size) + ")"); + } + + if (size <= 0) { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::init(" + std::to_string(size) + "): size <= 0"); + } + + return; + } + + list_.size = static_cast::SizeType>(size); + list_.elements = new (std::nothrow) typename RawGraalListTraits::ElementType *[list_.size]; + + if (!list_.elements) { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::init(" + std::to_string(size) + "): !list_.elements"); + } + + release(); + + return; + } + + bool needToRelease = false; + + for (typename RawGraalListTraits::SizeType i = 0; i < list_.size; i++) { + list_.elements[i] = new (std::nothrow) typename RawGraalListTraits::ElementType{}; + + if (!list_.elements[i]) { + needToRelease = true; + } + } + + if (needToRelease) { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::init({}): needToRelease"); + } + + release(); + } + } + + void release() { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::release()"); + } + + if (list_.size == 0 || list_.elements == nullptr) { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::release(): list_.size == 0 || list_.elements == nullptr"); + } + + return; + } + + for (auto i = list_.size - 1; i >= 0; i--) { + delete list_.elements[i]; + } + + delete[] list_.elements; + list_.size = 0; + list_.elements = nullptr; + } + + ~RawListWrapper() noexcept { + if constexpr (Debugger::traceLists) { + Debugger::trace(getDebugName() + "::~()"); + } + + release(); + } +}; + +} // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/SymbolList.hpp b/include/dxfeed_graal_cpp_api/internal/SymbolList.hpp new file mode 100644 index 00000000..a957c28e --- /dev/null +++ b/include/dxfeed_graal_cpp_api/internal/SymbolList.hpp @@ -0,0 +1,58 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include +#include +#include "../symbols/SymbolWrapper.hpp" + +namespace dxfcpp { + +struct SymbolList { + template static std::unique_ptr create(SymbolIt begin, SymbolIt end) noexcept { + auto size = std::distance(begin, end); + + if (size <= 0) { + return {}; + } + + auto list = create(size); + + if (!list || list->isEmpty()) { + return {}; + } + + std::size_t i = 0; + + for (auto it = begin; it != end; it++, i++) { + list->set(i, *it); + } + + return list; + } + + void set(std::size_t index, const SymbolWrapper& symbolWrapper) noexcept; + + [[nodiscard]] bool isEmpty() const noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + + void *getHandler() noexcept; + + ~SymbolList() noexcept; + + std::string toString() const noexcept; + + private: + static std::unique_ptr create(std::size_t size) noexcept; + + SymbolList() noexcept; + + struct Impl; + + std::unique_ptr impl_; +}; + +} \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/context/ApiContext.hpp b/include/dxfeed_graal_cpp_api/internal/context/ApiContext.hpp index e59fb921..bd3c4eb9 100644 --- a/include/dxfeed_graal_cpp_api/internal/context/ApiContext.hpp +++ b/include/dxfeed_graal_cpp_api/internal/context/ApiContext.hpp @@ -11,24 +11,26 @@ namespace dxfcpp { class ApiContext { - std::shared_ptr dxEndpointManager_; - std::shared_ptr dxFeedSubscriptionManager_; + mutable std::shared_ptr dxEndpointManager_; + mutable std::shared_ptr dxFeedSubscriptionManager_; std::atomic initialized{false}; - ApiContext() + ApiContext() noexcept : dxEndpointManager_{std::make_shared()}, dxFeedSubscriptionManager_{std::make_shared()} {} public: - static std::shared_ptr getInstance() { - static std::shared_ptr instance = std::shared_ptr(new ApiContext{}); + static std::shared_ptr getInstance() noexcept { + static std::shared_ptr instance = std::shared_ptr(new (std::nothrow) ApiContext{}); return instance; } - std::shared_ptr getDxEndpointManager() { return dxEndpointManager_; } + std::shared_ptr getDxEndpointManager() const noexcept { return dxEndpointManager_; } - std::shared_ptr getDxFeedSubscriptionManager() { return dxFeedSubscriptionManager_; } + std::shared_ptr getDxFeedSubscriptionManager() const noexcept { + return dxFeedSubscriptionManager_; + } }; } // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/managers/EntityManager.hpp b/include/dxfeed_graal_cpp_api/internal/managers/EntityManager.hpp index dce6e68c..151abacf 100644 --- a/include/dxfeed_graal_cpp_api/internal/managers/EntityManager.hpp +++ b/include/dxfeed_graal_cpp_api/internal/managers/EntityManager.hpp @@ -13,15 +13,25 @@ namespace dxfcpp { template struct EntityManager : private NonCopyable> { +#if DXFCPP_DEBUG == 1 + static auto getDebugName() { return std::string("EntityManager<") + typeid(EntityType).name() + ">"; } +#endif + // TODO: Boost.Bimap - std::unordered_map, std::shared_ptr> entitiesById_{}; - std::unordered_map, Id> idsByEntities_{}; - std::mutex mutex_{}; + std::unordered_map, std::shared_ptr> entitiesById_; + std::unordered_map, Id> idsByEntities_; + std::mutex mutex_; + + EntityManager() : entitiesById_{}, idsByEntities_{}, mutex_{} { + if constexpr (Debugger::isDebug) { + Debugger::debug(getDebugName() + "()"); + } + } public: Id registerEntity(std::shared_ptr entity) { - if constexpr (isDebug) { - debug("EntityManager::registerEntity({})", entity->toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug(getDebugName() + "::registerEntity(" + entity->toString() + ")"); } std::lock_guard lockGuard{mutex_}; @@ -39,8 +49,8 @@ template struct EntityManager : private NonCopyable entity) { - if constexpr (isDebug) { - debug("EntityManager::unregisterEntity({})", entity->toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug(getDebugName() + "::unregisterEntity(" + entity->toString() + ")"); } std::lock_guard lockGuard{mutex_}; @@ -56,8 +66,8 @@ template struct EntityManager : private NonCopyable id) { - if constexpr (isDebug) { - debug("EntityManager::unregisterEntity(id = {})", id.getValue()); + if constexpr (Debugger::isDebug) { + Debugger::debug(getDebugName() + "::unregisterEntity(id = " + std::to_string(id.getValue()) + ")"); } std::lock_guard lockGuard{mutex_}; @@ -73,6 +83,10 @@ template struct EntityManager : private NonCopyable getEntity(Id id) { + if constexpr (Debugger::isDebug) { + Debugger::debug(getDebugName() + "::getEntity(id = " + std::to_string(id.getValue()) + ")"); + } + std::lock_guard lockGuard{mutex_}; if (auto it = entitiesById_.find(id); it != entitiesById_.end()) { @@ -83,6 +97,10 @@ template struct EntityManager : private NonCopyable> getId(std::shared_ptr entity) { + if constexpr (Debugger::isDebug) { + Debugger::debug(getDebugName() + "::getId(" + entity->toString() + ")"); + } + std::lock_guard lockGuard{mutex_}; if (auto it = idsByEntities_.find(entity); it != idsByEntities_.end()) { diff --git a/include/dxfeed_graal_cpp_api/internal/managers/ErrorHandlingManager.hpp b/include/dxfeed_graal_cpp_api/internal/managers/ErrorHandlingManager.hpp index b909d40a..3c4a0525 100644 --- a/include/dxfeed_graal_cpp_api/internal/managers/ErrorHandlingManager.hpp +++ b/include/dxfeed_graal_cpp_api/internal/managers/ErrorHandlingManager.hpp @@ -39,7 +39,7 @@ struct Error { //TODO: implement retrieving, grouping methods class ErrorHandlingManager { static constexpr std::size_t DEFAULT_ERROR_COLLECTION_CAPACITY{1024ULL}; - thread_local static inline const Error NO_ERROR{Error::UNKNOWN_ID, 0, "", "NO ERROR"}; + static inline const Error NO_ERROR{Error::UNKNOWN_ID, 0, "", "NO ERROR"}; std::mutex errorCollectionMutex_{}; std::unordered_map errorCollection_{}; diff --git a/include/dxfeed_graal_cpp_api/internal/utils/StringUtils.hpp b/include/dxfeed_graal_cpp_api/internal/utils/StringUtils.hpp new file mode 100644 index 00000000..f9214fe4 --- /dev/null +++ b/include/dxfeed_graal_cpp_api/internal/utils/StringUtils.hpp @@ -0,0 +1,77 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include +#include + +namespace dxfcpp { + +struct String { + inline static const std::string EMPTY{}; +}; + +std::string toString(bool b); + +std::string toString(const char *chars); + +std::string toString(std::thread::id theadId); + +std::string toString(void *ptr); + +template std::string toStringAny(T &&t) { + if constexpr (requires { t.toString(); }) { + return t.toString(); + } else if constexpr (requires { *t.toString(); }) { + return *t.toString(); + } else if constexpr (requires { toString(t); }) { + return toString(t); + } else if constexpr (requires { std::to_string(t); }) { + return std::to_string(t); + } else if constexpr (requires { std::string(t); }) { + return std::string(t); + } else { + return "unknown"; + } +} + +char utf16to8(std::int16_t in); + +std::int16_t utf8to16(char in); + +std::string formatTimeStamp(std::int64_t timestamp); + +std::string formatTimeStampWithMillis(std::int64_t timestamp); + +template +#if __cpp_concepts + requires requires { std::is_same_v getName())>, std::string>; } +#endif +std::string namesToString(It begin, It end) { + std::string result{"["}; + + for (auto it = begin; it != end; it++) { + result += String::EMPTY + "'" + it->getName() + "'" + (std::next(it) == end ? "" : ", "); + } + + return result + "]"; +} + +template +std::string elementsToString(It begin, It end) { + std::string result{"["}; + + for (auto it = begin; it != end; it++) { + result += String::EMPTY + "'" + toStringAny(*it) + "'" + (std::next(it) == end ? "" : ", "); + } + + return result + "]"; +} + +std::string encodeChar(std::int16_t c); + +inline std::string encodeChar(char c) { return encodeChar(static_cast(static_cast(c))); } + +} // namespace dxfcpp \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/internal/utils/debug/Debug.hpp b/include/dxfeed_graal_cpp_api/internal/utils/debug/Debug.hpp new file mode 100644 index 00000000..fc030f3e --- /dev/null +++ b/include/dxfeed_graal_cpp_api/internal/utils/debug/Debug.hpp @@ -0,0 +1,69 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#define DXFCPP_DEBUG 0 +// #define DXFCPP_TRACE_LISTS 1 +// #define DXFCPP_TRACE_ISOLATES 1 + +#ifndef DXFCPP_DEBUG +# define DXFCPP_DEBUG 0 +#endif + +#ifndef DXFCPP_TRACE_ISOLATES +# define DXFCPP_TRACE_ISOLATES 0 +#endif +#ifndef DXFCPP_TRACE_LISTS +# define DXFCPP_TRACE_LISTS 0 +#endif + +#if DXFCPP_TRACE_ISOLATES == 1 || DXFCPP_TRACE_LISTS == 1 +# define DXFCPP_TRACE DXFCPP_DEBUG +#endif + +namespace dxfcpp { + +#if DXFCPP_DEBUG == 0 + +static inline std::string getDebugName() { return {}; } + +struct Debugger { + static constexpr bool isDebug = false; + static constexpr bool traceIsolates = false; + static constexpr bool traceLists = false; + + static void debug(...) {} + static void trace(...) {} +}; + +#else + +struct Debugger { + static constexpr bool isDebug = true; +# if DXFCPP_TRACE_ISOLATES == 1 + static constexpr bool traceIsolates = true; +# else + static constexpr bool traceIsolates = false; +# endif + +# if DXFCPP_TRACE_LISTS == 1 + static constexpr bool traceLists = true; +# else + static constexpr bool traceLists = false; +# endif + static std::string nowStr(); + static std::string nowStrWithTimeZone(); + static std::string debugPrefixStr(); + static void debug(std::string); +# if DXFCPP_TRACE == 1 + static std::string tracePrefixStr(); + static void trace(std::string); +# else + static void trace(...); +# endif +}; + +#endif + +} // namespace dxfcpp diff --git a/include/dxfeed_graal_cpp_api/symbols/StringSymbol.hpp b/include/dxfeed_graal_cpp_api/symbols/StringSymbol.hpp new file mode 100644 index 00000000..a03b058d --- /dev/null +++ b/include/dxfeed_graal_cpp_api/symbols/StringSymbol.hpp @@ -0,0 +1,115 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace dxfcpp { + +/** + * A helper wrapper class needed to pass heterogeneous string symbols using a container and convert them to internal Graal representation. + * + * The current implementation is suboptimal (by memory usage) and will be enhanced. + */ +struct StringSymbol final { + private: + std::string data_; + + struct Impl; + std::unique_ptr impl_; + + public: + StringSymbol(const StringSymbol &stringSymbol) noexcept; + StringSymbol(StringSymbol &&) noexcept; + StringSymbol &operator=(const StringSymbol &stringSymbol) noexcept; + StringSymbol &operator=(StringSymbol &&) noexcept; + StringSymbol() noexcept; + ~StringSymbol() noexcept; + + /** + * Constructs StringSymbol from a char array + * + * @param chars The array of chars + */ + StringSymbol(const char *chars) noexcept : StringSymbol() { + if constexpr (Debugger::isDebug) { + Debugger::debug("StringSymbol(chars = " + toStringAny(chars) + ")"); + } + + data_ = std::string(chars); + } + + /** + * Constructs StringSymbol from a std::string_view + * + * @param stringView The string view + */ + StringSymbol(std::string_view stringView) noexcept : StringSymbol() { + if constexpr (Debugger::isDebug) { + Debugger::debug("StringSymbol(stringView = " + toStringAny(stringView) + ")"); + } + + data_ = std::string(stringView); + } + + StringSymbol(std::string string) noexcept : StringSymbol() { + if constexpr (Debugger::isDebug) { + Debugger::debug("StringSymbol(string = " + toStringAny(string) + ")"); + } + + data_ = std::move(string); + } + + void *toGraal() const noexcept; + + /** + * Returns a string representation of the current object. + * + * @return a string representation + */ + std::string toString() const noexcept { return "StringSymbol{" + data_ + "}"; } + + const std::string &getData() const; + + bool operator==(const StringSymbol &stringSymbol) const { return getData() == stringSymbol.getData(); } + + bool operator<(const StringSymbol &stringSymbol) const { return getData() < stringSymbol.getData(); } +}; + +/** + * A concept describing a string that can be wrapped. + * + * @tparam T Probable string symbol type + */ +template +concept ConvertibleToStringSymbol = + std::is_convertible_v, std::string> || std::is_convertible_v, std::string_view>; + +inline namespace literals { + +/** + * String literal that helps to construct StringSymbol from a char array. + * + * @param string The char array + * @param length Tha char array's length + * @return Wrapped string view built on char array + */ +inline StringSymbol operator""_s(const char *string, size_t length) noexcept { + return {std::string_view{string, length}}; +} + +} // namespace literals + +std::string graalSymbolToString(void *graalSymbol); + +} // namespace dxfcpp + +template <> struct std::hash { + std::size_t operator()(const dxfcpp::StringSymbol &stringSymbol) const noexcept { + return std::hash{}(stringSymbol.getData()); + } +}; diff --git a/include/dxfeed_graal_cpp_api/symbols/SymbolMapper.hpp b/include/dxfeed_graal_cpp_api/symbols/SymbolMapper.hpp new file mode 100644 index 00000000..c524f01e --- /dev/null +++ b/include/dxfeed_graal_cpp_api/symbols/SymbolMapper.hpp @@ -0,0 +1,18 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace dxfcpp { + +struct SymbolMapper { + template + static void* toNativeList(SymbolIt begin, SymbolIt end) noexcept; +}; + +} \ No newline at end of file diff --git a/include/dxfeed_graal_cpp_api/symbols/SymbolWrapper.hpp b/include/dxfeed_graal_cpp_api/symbols/SymbolWrapper.hpp new file mode 100644 index 00000000..3210d06f --- /dev/null +++ b/include/dxfeed_graal_cpp_api/symbols/SymbolWrapper.hpp @@ -0,0 +1,165 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include +#include +#include +#include +#include + +#include "../api/osub/WildcardSymbol.hpp" +#include "StringSymbol.hpp" + +namespace dxfcpp { + +/** + * A helper wrapper class needed to pass heterogeneous symbols using a container and convert them to internal Graal representation. + */ +struct SymbolWrapper final { + using DataType = typename std::variant; + + private: + DataType data_; + + public: + SymbolWrapper(const SymbolWrapper &) noexcept = default; + SymbolWrapper(SymbolWrapper &&) noexcept = delete; + SymbolWrapper &operator=(const SymbolWrapper &) noexcept = default; + SymbolWrapper &operator=(SymbolWrapper &&) noexcept = delete; + SymbolWrapper() noexcept = default; + ~SymbolWrapper() noexcept = default; + + /** + * Constructor for any wrapped symbol. + * Must be implicit to wrap symbols passed to collection or container + * + * @tparam Symbol The symbol type + * @param symbol The symbol + */ + template + SymbolWrapper(Symbol &&symbol) noexcept : SymbolWrapper(StringSymbol(std::forward(symbol))) { + if constexpr (Debugger::isDebug) { + // Could be "perfectly" moved, so it won't show up in the logs + Debugger::debug("SymbolWrapper(symbol = " + toStringAny(symbol) + ")"); + } + } + + /** + * Constructor for any wrapped string symbol. + * Must be implicit to wrap symbols passed to collection or container + * + * @param stringSymbol The wrapped string (std::string, std::string_view, const char*) symbol + */ + SymbolWrapper(const StringSymbol &stringSymbol) noexcept { + if constexpr (Debugger::isDebug) { + Debugger::debug("SymbolWrapper(stringSymbol = " + toStringAny(stringSymbol) + ")"); + } + + data_ = stringSymbol; + } + + /** + * Constructor for any wrapped wildcard (*) symbol. + * + * @param wildcardSymbol The wrapped wildcard symbl + */ + SymbolWrapper(const WildcardSymbol &wildcardSymbol) noexcept { + if constexpr (Debugger::isDebug) { + Debugger::debug("SymbolWrapper(wildcardSymbol = " + toStringAny(wildcardSymbol) + ")"); + } + + data_ = wildcardSymbol; + } + + void *toGraal() const noexcept { + return std::visit([](const auto &symbol) { return symbol.toGraal(); }, data_); + } + + /** + * Returns a string representation of the current object. + * + * @return a string representation + */ + std::string toString() const noexcept { + return "SymbolWrapper{" + std::visit([](const auto &symbol) { return toStringAny(symbol); }, data_) + "}"; + } + + /** + * @return `true` if current SymbolWrapper holds a WildcardSymbol + */ + bool isWildcardSymbol() const noexcept { return std::holds_alternative(data_); } + + /** + * @return WildcardSymbol (optional) or nullopt if current SymbolWrapper doesn't hold WildcardSymbol + */ + std::optional asWildcardSymbol() const noexcept { + return isWildcardSymbol() ? std::make_optional(WildcardSymbol::ALL) : std::nullopt; + } + + /** + * @return `true` if current SymbolWrapper holds a StringSymbol + */ + bool isStringSymbol() const noexcept { return std::holds_alternative(data_); } + + /** + * @return String representation of StringSymbol or an empty string + */ + std::string asStringSymbol() const noexcept { + return isStringSymbol() ? std::get(data_).getData() : String::EMPTY; + } + + const DataType &getData() const noexcept { return data_; } + + bool operator==(const SymbolWrapper &symbolWrapper) const { return getData() == symbolWrapper.getData(); } + + auto operator<(const SymbolWrapper &symbolWrapper) const { return getData() < symbolWrapper.getData(); } +}; + +/** + * A concept describing a symbol that can be wrapped. + * + * @tparam T Probable symbol type + */ +template +concept ConvertibleToSymbolWrapper = + ConvertibleToStringSymbol> || std::is_same_v, WildcardSymbol>; + +/** + * A concept that defines a collection of wrapped or wrapping symbols. + * + * @tparam Collection The collection type + */ +template +concept ConvertibleToSymbolWrapperCollection = requires(Collection c) { + std::begin(c); + std::end(c); +} && requires(Collection c) { + { *std::begin(c) } -> std::convertible_to; +} || requires(Collection c) { + { *std::begin(c) } -> ConvertibleToSymbolWrapper; +}; + +inline namespace literals { + +/** + * String literal that helps to construct SymbolWrapper from a char array. + * + * @param string The char array + * @param length Tha char array's length + * @return Wrapped string view built on char array + */ +inline SymbolWrapper operator""_sw(const char *string, size_t length) noexcept { + return {std::string_view{string, length}}; +} + +} // namespace literals + +} // namespace dxfcpp + +template <> struct std::hash { + std::size_t operator()(const dxfcpp::SymbolWrapper &symbolWrapper) const noexcept { + return std::hash{}(symbolWrapper.getData()); + } +}; \ No newline at end of file diff --git a/samples/cpp/PrintQuoteEvents/CMakeLists.txt b/samples/cpp/PrintQuoteEvents/CMakeLists.txt index 6123ad2e..eb46006d 100644 --- a/samples/cpp/PrintQuoteEvents/CMakeLists.txt +++ b/samples/cpp/PrintQuoteEvents/CMakeLists.txt @@ -12,7 +12,7 @@ cmake_policy(SET CMP0135 NEW) add_executable(PrintQuoteEvents src/main.cpp) target_include_directories(PrintQuoteEvents PUBLIC ../../../include) -target_link_libraries(PrintQuoteEvents PUBLIC DxFeedGraalNativeApi dxFeedGraalCxxApi) +target_link_libraries(PrintQuoteEvents PUBLIC DxFeedGraalNativeSdk dxFeedGraalCxxApi dxFeedGraalCxxApi_Clang) add_custom_command(TARGET PrintQuoteEvents POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ $) \ No newline at end of file + $ $) \ No newline at end of file diff --git a/samples/cpp/PrintQuoteEvents/src/main.cpp b/samples/cpp/PrintQuoteEvents/src/main.cpp index 1d90797f..8f3d6759 100644 --- a/samples/cpp/PrintQuoteEvents/src/main.cpp +++ b/samples/cpp/PrintQuoteEvents/src/main.cpp @@ -4,47 +4,108 @@ #include #include -auto cApiStateToString(dxfc_dxendpoint_state_t state) { - switch (state) { - case DXFC_DXENDPOINT_STATE_NOT_CONNECTED: - return "NOT_CONNECTED"; - case DXFC_DXENDPOINT_STATE_CONNECTING: - return "CONNECTING"; - case DXFC_DXENDPOINT_STATE_CONNECTED: - return "CONNECTED"; - case DXFC_DXENDPOINT_STATE_CLOSED: - return "CLOSED"; - } - - return ""; -} - int main() { { using namespace std::string_literals; + using namespace std::string_view_literals; + using namespace dxfcpp::literals; + + std::vector vi{}; + + auto eventTypes = {dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}; + + dxfcpp::DXFeedSubscription::create(eventTypes.begin(), eventTypes.end()); + auto sub2 = dxfcpp::DXFeedSubscription::create(std::unordered_set{dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}); - auto builder = dxfcpp::DXEndpoint::newBuilder()->withRole(dxfcpp::DXEndpoint::Role::FEED); + std::vector symbols{{"123", "123123"sv, "123213123"s, "*"_wcs, "ZZZ"_s}}; + + for (const auto &s : symbols) { + + std::cerr << dxfcpp::graalSymbolToString(s.toGraal()) + "\n"; + } + + auto builder = dxfcpp::DXEndpoint::newBuilder() + ->withRole(dxfcpp::DXEndpoint::Role::FEED) + ->withProperty(dxfcpp::DXEndpoint::DXFEED_WILDCARD_ENABLE_PROPERTY, "true"); auto endpoint = builder->build(); - endpoint->onStateChange() += [](dxfcpp::DXEndpoint::State oldState, dxfcpp::DXEndpoint::State newState) { - dxfcpp::debug("{}", std::string("State changed: ") + dxfcpp::DXEndpoint::stateToString(oldState) + " -> " + - dxfcpp::DXEndpoint::stateToString(newState)); - }; + auto sub = endpoint->getFeed()->createSubscription({dxfcpp::Quote::Type, dxfcpp::TimeAndSale::Type}); - auto sub = - endpoint->getFeed()->createSubscription({dxfcpp::EventTypeEnum::QUOTE}); + // sub->addEventListener([](auto &&events) { + // for (const auto &e : events) { + // std::cout << e << std::endl; + // } + // }); sub->addEventListener([](auto &&events) { for (const auto &e : events) { - std::cout << e << std::endl; + if (auto quote = e->template sharedAs(); quote) { + std::cout << "Q : " + quote->toString() << std::endl; + } else if (auto tns = e->template sharedAs(); tns) { + std::cout << "TnS : " + tns->toString() << std::endl; + } } }); - sub->addSymbol("AAPL"s); - sub->addSymbol("IBM"s); - sub->addSymbol("TSLA"s); + auto id2 = sub->addEventListener( + std::function([](const std::vector> &timeAndSales) -> void { + for (const auto &tns : timeAndSales) { + std::cout << "TnS2: " + tns->toString() << std::endl; + } + })); + + sub->addEventListener(std::function([](const std::vector> &trades) -> void { + for (const auto &trade : trades) { + std::cout << "Trade: " + trade->toString() << std::endl; + } + })); + + sub->addEventListener([](const auto &timeAndSales) -> void { + for (const auto &tns : timeAndSales) { + std::cout << "TnS3: " + tns->toString() << std::endl; + } + }); + + sub->addSymbols({"AAPL&Q", "IBM"sv, "XBT/USD:GDAX"s, "BTC/EUR:CXBITF"_s, "*"_wcs}); + sub->addSymbols("META"); + sub->addSymbols("ADDYY"sv); + sub->addSymbols("MSFT"s); + sub->addSymbols(dxfcpp::WildcardSymbol::ALL); + sub->addSymbols("TCELL:TR"_s); + sub->addSymbols("*"_wcs); + sub->addSymbols("PFE"_sw); + sub->addSymbols(std::vector{"CSCO"s}); + sub->addSymbols(std::vector{"$TOP10L/Q", "$SP500#45", "$TICK", "SPX"}); + + auto v = std::vector{"$TOP10G/Q", "30Y:SME"}; + auto v2 = std::vector{"$DECN"s, "./E1AN23P3580:XCME"sv, "/ESZ23:XCME", "/ESH25:XCME"_s}; + + sub->addSymbols(v); + sub->addSymbols(v2); + sub->addSymbols(v2.begin(), v2.end()); + + // D 230530 103822.766 [8212] DXFeedSubscription{000002672C607310}::addSymbols(symbols = + // ['SymbolWrapper{StringSymbol{333}}', 'SymbolWrapper{StringSymbol{222}}', 'SymbolWrapper{StringSymbol{111}}']) + auto us = std::unordered_set{"111"s, "222"sv, "333", "111"_s}; + + sub->addSymbols(us); + + // D 230530 103822.766 [8212] DXFeedSubscription{000002672C607310}::addSymbols(symbols = + // ['SymbolWrapper{WildcardSymbol{*}}', 'SymbolWrapper{StringSymbol{*}}']) + sub->addSymbols(std::unordered_set{"*", "*"_wcs}); + + // endpoint->connect("demo.dxfeed.com:7300"); + endpoint->connect("mddqa.in.devexperts.com:7400"); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + + sub->removeSymbols("TSLA"); + + sub->removeSymbols(symbols); + + sub->onEvent() -= id2; - endpoint->connect("demo.dxfeed.com:7300"); + std::cerr << " ----------------------- \n"; std::this_thread::sleep_for(std::chrono::seconds(5)); diff --git a/src/api/DXEndpoint.cpp b/src/api/DXEndpoint.cpp index 5c634165..a5551ca5 100644 --- a/src/api/DXEndpoint.cpp +++ b/src/api/DXEndpoint.cpp @@ -10,6 +10,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const std::string DXEndpoint::NAME_PROPERTY = "name"; @@ -66,20 +71,20 @@ static DXEndpoint::State graalStateToState(dxfg_endpoint_state_t state) { std::shared_ptr DXEndpoint::create(void *endpointHandle, DXEndpoint::Role role, const std::unordered_map &properties) { - if constexpr (isDebug) { - debug("DXEndpoint::create{{handle = {}, role = {}, properties[{}]}}()", endpointHandle, roleToString(role), - properties.size()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::create(handle = " + dxfcpp::toString(endpointHandle) + + ", role = " + roleToString(role) + ", properties[" + std::to_string(properties.size()) + "])"); } std::shared_ptr endpoint{new (std::nothrow) DXEndpoint{}}; if (!endpoint) { - //TODO: dummy endpoint & error handling; + // TODO: dummy endpoint & error handling; return endpoint; } - endpoint->handler_ = handler_utils::JavaObjectHandler(endpointHandle); + endpoint->handler_ = JavaObjectHandler(endpointHandle); endpoint->role_ = role; endpoint->name_ = properties.contains(NAME_PROPERTY) ? properties.at(NAME_PROPERTY) : std::string{}; @@ -90,7 +95,10 @@ std::shared_ptr DXEndpoint::create(void *endpointHandle, DXEndpoint: auto id = Id::from(bit_cast::ValueType>(userData)); auto endpoint = ApiContext::getInstance()->getDxEndpointManager()->getEntity(id); - std::cerr << "onStateChange: id = " + std::to_string(id.getValue()) + ", endpoint = " + ((endpoint) ? endpoint->toString() : "nullptr") + "\n"; + if constexpr (Debugger::isDebug) { + Debugger::debug("onStateChange: id = " + std::to_string(id.getValue()) + + ", endpoint = " + ((endpoint) ? endpoint->toString() : "nullptr")); + } if (endpoint) { endpoint->onStateChange_(graalStateToState(oldState), graalStateToState(newState)); @@ -101,12 +109,11 @@ std::shared_ptr DXEndpoint::create(void *endpointHandle, DXEndpoint: } }; - endpoint->stateChangeListenerHandler_ = - handler_utils::JavaObjectHandler(runIsolatedOrElse( - [idValue = id.getValue(), onPropertyChange](auto threadHandle) { - return dxfg_PropertyChangeListener_new(threadHandle, onPropertyChange, bit_cast(idValue)); - }, - nullptr)); + endpoint->stateChangeListenerHandler_ = JavaObjectHandler(runIsolatedOrElse( + [idValue = id.getValue(), onPropertyChange](auto threadHandle) { + return dxfg_PropertyChangeListener_new(threadHandle, onPropertyChange, bit_cast(idValue)); + }, + nullptr)); endpoint->setStateChangeListenerImpl(); return endpoint; @@ -118,10 +125,7 @@ void DXEndpoint::setStateChangeListenerImpl() { [handler = bit_cast(handler_.get()), stateChangeListenerHandler = bit_cast( stateChangeListenerHandler_.get())](auto threadHandle) { - // TODO: finalize function - - return dxfg_DXEndpoint_addStateChangeListener( - threadHandle, handler, stateChangeListenerHandler, [](auto, auto) {}, nullptr) == 0; + return dxfg_DXEndpoint_addStateChangeListener(threadHandle, handler, stateChangeListenerHandler) == 0; }, false); } @@ -150,8 +154,8 @@ void DXEndpoint::closeImpl() { std::shared_ptr DXEndpoint::user(const std::string &user) { // TODO: check invalid utf-8 - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::user(user = {})", handler_.toString(), user); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::user(user = " + user + ")"); } if (handler_) { @@ -162,13 +166,13 @@ std::shared_ptr DXEndpoint::user(const std::string &user) { false); } - return shared_from_this(); + return sharedAs(); } std::shared_ptr DXEndpoint::password(const std::string &password) { // TODO: check invalid utf-8 - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::password(password = {})", handler_.toString(), password); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::password(password = " + password + ")"); } if (handler_) { @@ -179,13 +183,13 @@ std::shared_ptr DXEndpoint::password(const std::string &password) { false); } - return shared_from_this(); + return sharedAs(); } std::shared_ptr DXEndpoint::connect(const std::string &address) { // TODO: check invalid utf-8 - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::connect(address = {})", handler_.toString(), address); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::connect(address = " + address + ")"); } if (handler_) { @@ -196,12 +200,12 @@ std::shared_ptr DXEndpoint::connect(const std::string &address) { false); } - return shared_from_this(); + return sharedAs(); } void DXEndpoint::reconnect() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::reconnect()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::reconnect()"); } if (!handler_) { @@ -214,8 +218,8 @@ void DXEndpoint::reconnect() { } void DXEndpoint::disconnect() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::disconnect()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::disconnect()"); } if (!handler_) { @@ -228,8 +232,8 @@ void DXEndpoint::disconnect() { } void DXEndpoint::disconnectAndClear() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::disconnectAndClear()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::disconnectAndClear()"); } if (!handler_) { @@ -242,8 +246,8 @@ void DXEndpoint::disconnectAndClear() { } void DXEndpoint::awaitNotConnected() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::awaitNotConnected()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::awaitNotConnected()"); } if (!handler_) { @@ -256,8 +260,8 @@ void DXEndpoint::awaitNotConnected() { } void DXEndpoint::awaitProcessed() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::awaitProcessed()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::awaitProcessed()"); } if (!handler_) { @@ -270,8 +274,8 @@ void DXEndpoint::awaitProcessed() { } void DXEndpoint::closeAndAwaitTermination() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::closeAndAwaitTermination()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::closeAndAwaitTermination()"); } if (!handler_) { @@ -288,8 +292,8 @@ void DXEndpoint::closeAndAwaitTermination() { } std::shared_ptr DXEndpoint::getFeed() { - if constexpr (isDebug) { - debug("DXEndpoint{{{}}}::getFeed()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint{" + handler_.toString() + "}::getFeed()"); } if (!feed_) { @@ -307,14 +311,14 @@ std::shared_ptr DXEndpoint::getFeed() { } std::shared_ptr DXEndpoint::Builder::create() noexcept { - if constexpr (isDebug) { - debug("DXEndpoint::Builder::create()"); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder::create()"); } auto builder = std::shared_ptr(new (std::nothrow) Builder{}); if (builder) { - builder->handler_ = handler_utils::JavaObjectHandler( + builder->handler_ = JavaObjectHandler( runIsolatedOrElse([](auto threadHandle) { return dxfg_DXEndpoint_newBuilder(threadHandle); }, nullptr)); } @@ -368,8 +372,9 @@ void DXEndpoint::Builder::loadDefaultPropertiesImpl() { } std::shared_ptr DXEndpoint::Builder::withRole(DXEndpoint::Role role) { - if constexpr (isDebug) { - debug("DXEndpoint::Builder{{{}}}::withRole(role = {})", handler_.toString(), roleToString(role)); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder{" + handler_.toString() + "}::withRole(role = " + roleToString(role) + + ")"); } role_ = role; @@ -388,8 +393,9 @@ std::shared_ptr DXEndpoint::Builder::withRole(DXEndpoint::R std::shared_ptr DXEndpoint::Builder::withProperty(const std::string &key, const std::string &value) { // TODO: check invalid utf-8 - if constexpr (isDebug) { - debug("DXEndpoint::Builder{{{}}}::withProperty(key = {}, value = {})", handler_.toString(), key, value); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder{" + handler_.toString() + "}::withProperty(key = " + key + + ", value = " + value + ")"); } properties_[key] = value; @@ -408,8 +414,8 @@ std::shared_ptr DXEndpoint::Builder::withProperty(const std bool DXEndpoint::Builder::supportsProperty(const std::string &key) { // TODO: check invalid utf-8 - if constexpr (isDebug) { - debug("DXEndpoint::Builder{{{}}}::supportsProperty(key = {})", handler_.toString(), key); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder{" + handler_.toString() + "}::supportsProperty(key = " + key + ")"); } if (!handler_) { @@ -424,8 +430,8 @@ bool DXEndpoint::Builder::supportsProperty(const std::string &key) { } std::shared_ptr DXEndpoint::Builder::build() { - if constexpr (isDebug) { - debug("DXEndpoint::Builder{{{}}}::build()", handler_.toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXEndpoint::Builder{" + handler_.toString() + "}::build()"); } loadDefaultPropertiesImpl(); @@ -440,6 +446,8 @@ std::shared_ptr DXEndpoint::Builder::build() { return DXEndpoint::create(endpointHandle, role_, properties_); } +std::string DXEndpoint::toString() const noexcept { return fmt::format("DXEndpoint{{{}}}", handler_.toString()); } + struct BuilderHandle {}; struct BuilderRegistry { diff --git a/src/api/DXFeed.cpp b/src/api/DXFeed.cpp index 067dd7c8..38d15839 100644 --- a/src/api/DXFeed.cpp +++ b/src/api/DXFeed.cpp @@ -6,22 +6,27 @@ #include #include -#include #include +#include + +#include +#include +#include +#include namespace dxfcpp { std::shared_ptr DXFeed::getInstance() noexcept { - if constexpr (isDebug) { - debug("DXFeed::getInstance()"); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeed::getInstance()"); } return DXEndpoint::getInstance()->getFeed(); } void DXFeed::attachSubscription(std::shared_ptr subscription) noexcept { - if constexpr (isDebug) { - debug("{}::attachSubscription({})", toString(), subscription->toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::attachSubscription(" + subscription->toString() + ")"); } if (!handler_ || !subscription || !subscription->handler_) { @@ -40,8 +45,8 @@ void DXFeed::attachSubscription(std::shared_ptr subscription } void DXFeed::detachSubscription(std::shared_ptr subscription) noexcept { - if constexpr (isDebug) { - debug("{}::detachSubscription({})", toString(), subscription->toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::detachSubscription(" + subscription->toString() + ")"); } if (!handler_ || !subscription || !subscription->handler_) { @@ -60,8 +65,8 @@ void DXFeed::detachSubscription(std::shared_ptr subscription } void DXFeed::detachSubscriptionAndClear(std::shared_ptr subscription) noexcept { - if constexpr (isDebug) { - debug("{}::detachSubscriptionAndClear({})", toString(), subscription->toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::detachSubscriptionAndClear(" + subscription->toString() + ")"); } if (!handler_ || !subscription || !subscription->handler_) { @@ -80,8 +85,8 @@ void DXFeed::detachSubscriptionAndClear(std::shared_ptr subs } std::shared_ptr DXFeed::createSubscription(const EventTypeEnum &eventType) noexcept { - if constexpr (isDebug) { - debug("{}::createSubscription(eventType = {})", toString(), eventType.getName()); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::createSubscription(eventType = " + eventType.getName() + ")"); } auto sub = DXFeedSubscription::create(eventType); @@ -93,9 +98,9 @@ std::shared_ptr DXFeed::createSubscription(const EventTypeEn std::shared_ptr DXFeed::createSubscription(std::initializer_list eventTypes) noexcept { - if constexpr (isDebug) { - debug("{}::createSubscription(eventTypes = {})", toString(), - namesToString(eventTypes.begin(), eventTypes.end())); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::createSubscription(eventTypes = " + + namesToString(eventTypes.begin(), eventTypes.end()) + ")"); } auto sub = DXFeedSubscription::create(eventTypes); @@ -106,15 +111,21 @@ DXFeed::createSubscription(std::initializer_list eventTypes) noex } std::shared_ptr DXFeed::create(void *feedHandle) noexcept { - if constexpr (isDebug) { - debug("DXFeed::create({})", feedHandle); + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeed::create(" + dxfcpp::toString(feedHandle) + ")"); } std::shared_ptr feed{new (std::nothrow) DXFeed{}}; - feed->handler_ = handler_utils::JavaObjectHandler(feedHandle); + //TODO: error handling + + if (feed) { + feed->handler_ = JavaObjectHandler(feedHandle); + } return feed; } -} \ No newline at end of file +std::string DXFeed::toString() const noexcept { return fmt::format("DXFeed{{{}}}", handler_.toString()); } + +} // namespace dxfcpp \ No newline at end of file diff --git a/src/api/DXFeedSubscription.cpp b/src/api/DXFeedSubscription.cpp index 6c14e272..eb4a1bca 100644 --- a/src/api/DXFeedSubscription.cpp +++ b/src/api/DXFeedSubscription.cpp @@ -6,36 +6,50 @@ #include #include +#include + +#include +#include +#include +#include namespace dxfcpp { void DXFeedSubscription::attach(std::shared_ptr feed) noexcept { - if constexpr (isDebug) { - debug("{}::attach(feed = {})", toString(), feed->toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::attach(feed = " + feed->toString() + ")"); } - feed->attachSubscription(shared_from_this()); + feed->attachSubscription(sharedAs()); } void DXFeedSubscription::detach(std::shared_ptr feed) noexcept { - if constexpr (isDebug) { - debug("{}::detach(feed = {})", toString(), feed->toString()); + if constexpr (Debugger::isDebug) { + Debugger::debug(toString() + "::detach(feed = " + feed->toString() + ")"); } - feed->detachSubscription(shared_from_this()); + feed->detachSubscription(sharedAs()); } -template <> void DXFeedSubscription::addSymbol(std::string &&symbol) noexcept { +void DXFeedSubscription::addSymbolImpl(void *graalSymbol) const noexcept { runIsolatedOrElse( - [handler = bit_cast(handler_.get()), symbol](auto threadHandle) { - dxfg_string_symbol_t s{{STRING}, symbol.c_str()}; + [handler = bit_cast(handler_.get()), graalSymbol](auto threadHandle) { + return dxfg_DXFeedSubscription_addSymbol(threadHandle, handler, bit_cast(graalSymbol)) == + 0; + }, + false); +} - return dxfg_DXFeedSubscription_addSymbol(threadHandle, handler, (dxfg_symbol_t *)&s) == 0; +void DXFeedSubscription::removeSymbolImpl(void *graalSymbol) const noexcept { + runIsolatedOrElse( + [handler = bit_cast(handler_.get()), graalSymbol](auto threadHandle) { + return dxfg_DXFeedSubscription_removeSymbol(threadHandle, handler, + bit_cast(graalSymbol)) == 0; }, false); } -void DXFeedSubscription::closeImpl() noexcept { +void DXFeedSubscription::closeImpl() const noexcept { if (!handler_) { return; } @@ -45,7 +59,7 @@ void DXFeedSubscription::closeImpl() noexcept { false); } -void DXFeedSubscription::clearImpl() noexcept { +void DXFeedSubscription::clearImpl() const noexcept { if (!handler_) { return; } @@ -55,7 +69,7 @@ void DXFeedSubscription::clearImpl() noexcept { false); } -bool DXFeedSubscription::isClosedImpl() noexcept { +bool DXFeedSubscription::isClosedImpl() const noexcept { if (!handler_) { return false; } @@ -68,21 +82,21 @@ bool DXFeedSubscription::isClosedImpl() noexcept { } DXFeedSubscription::DXFeedSubscription(const EventTypeEnum &eventType) noexcept - : mtx_{}, handler_{}, eventListenerHandler_{}, onEvent_{} { - if constexpr (isDebug) { - debug("DXFeedSubscription(eventType = {})", eventType.getName()); + : mtx_{}, eventTypes_{eventType}, handler_{}, eventListenerHandler_{}, onEvent_{} { + if constexpr (Debugger::isDebug) { + Debugger::debug("DXFeedSubscription(eventType = " + eventType.getName() + ")"); } - handler_ = handler_utils::JavaObjectHandler(runIsolatedOrElse( + handler_ = JavaObjectHandler(runIsolatedOrElse( [eventType](auto threadHandle) { return dxfg_DXFeedSubscription_new(threadHandle, static_cast(eventType.getId())); }, nullptr)); } -handler_utils::JavaObjectHandler DXFeedSubscription::createSubscriptionHandlerFromEventClassList( - const std::unique_ptr &list) noexcept { - return handler_utils::JavaObjectHandler( +JavaObjectHandler +DXFeedSubscription::createSubscriptionHandlerFromEventClassList(const std::unique_ptr &list) noexcept { + return JavaObjectHandler( runIsolatedOrElse([listHandler = bit_cast(list->getHandler())]( auto threadHandle) { return dxfg_DXFeedSubscription_new2(threadHandle, listHandler); }, nullptr)); @@ -100,7 +114,7 @@ void DXFeedSubscription::setEventListenerHandler(Id id) noex } }; - eventListenerHandler_ = handler_utils::JavaObjectHandler(runIsolatedOrElse( + eventListenerHandler_ = JavaObjectHandler(runIsolatedOrElse( [idValue = id.getValue(), onEvents](auto threadHandle) { return dxfg_DXFeedEventListener_new(threadHandle, onEvents, bit_cast(idValue)); }, @@ -111,13 +125,14 @@ void DXFeedSubscription::setEventListenerHandler(Id id) noex [handler = bit_cast(handler_.get()), eventListenerHandler = bit_cast(eventListenerHandler_.get())](auto threadHandle) { - // TODO: finalize function - - return dxfg_DXFeedSubscription_addEventListener( - threadHandle, handler, eventListenerHandler, [](auto, auto) {}, nullptr) == 0; + return dxfg_DXFeedSubscription_addEventListener(threadHandle, handler, eventListenerHandler) == 0; }, false); } } +std::string DXFeedSubscription::toString() const noexcept { + return fmt::format("DXFeedSubscription{{{}}}", handler_.toString()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/api/osub/WildcardSymbol.cpp b/src/api/osub/WildcardSymbol.cpp new file mode 100644 index 00000000..1c4f46ae --- /dev/null +++ b/src/api/osub/WildcardSymbol.cpp @@ -0,0 +1,19 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include "dxfeed_graal_c_api/api.h" +#include "dxfeed_graal_cpp_api/api.hpp" + +namespace dxfcpp { + +const WildcardSymbol WildcardSymbol::ALL{"*"}; + +void *WildcardSymbol::toGraal() const noexcept { + static const dxfg_wildcard_symbol_t wildcardGraalSymbol{{WILDCARD}}; + + return bit_cast(&wildcardGraalSymbol); +} + +} // namespace dxfcpp \ No newline at end of file diff --git a/src/event/market/OptionSale.cpp b/src/event/market/OptionSale.cpp index 0dbd8e6e..5a5054f7 100644 --- a/src/event/market/OptionSale.cpp +++ b/src/event/market/OptionSale.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &OptionSale::Type = EventTypeEnum::OPTION_SALE; @@ -58,4 +63,17 @@ std::shared_ptr OptionSale::fromGraalNative(void *graalNative) noexc void OptionSale::setExchangeCode(char exchangeCode) { data_.exchangeCode = utf8to16(exchangeCode); } +std::string OptionSale::toString() const noexcept { + return fmt::format( + "OptionSale{{{}, eventTime={}, eventFlags={:#x}, index={:#x}, time={}, timeNanoPart={}, sequence={}, " + "exchange={}, price={}, size={}, bid={}, ask={}, ESC='{}', TTE={}, side={}, spread={}, ETH={}, " + "validTick={}, type={}, underlyingPrice={}, volatility={}, delta={}, optionSymbol='{}'}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + getEventFlags().getMask(), getIndex(), formatTimeStampWithMillis(getTime()), getTimeNanoPart(), + getSequence(), encodeChar(getExchangeCode()), getPrice(), getSize(), getBidPrice(), + getAskPrice(), getExchangeSaleConditions(), encodeChar(getTradeThroughExempt()), + getAggressorSide().toString(), isSpreadLeg(), isExtendedTradingHours(), isValidTick(), getType().toString(), + getUnderlyingPrice(), getVolatility(), getDelta(), getOptionSymbol()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/market/Profile.cpp b/src/event/market/Profile.cpp index 1225a431..c53c8306 100644 --- a/src/event/market/Profile.cpp +++ b/src/event/market/Profile.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &Profile::Type = EventTypeEnum::PROFILE; @@ -57,4 +62,17 @@ std::shared_ptr Profile::fromGraalNative(void *graalNative) noexcept { } } +std::string Profile::toString() const noexcept { + return fmt::format("Profile{{{}, eventTime={}, description='{}', SSR={}, status={}, statusReason='{}', " + "haltStartTime={}, haltEndTime={}, highLimitPrice={}, lowLimitPrice={}, high52WeekPrice={}, " + "low52WeekPrice={}, beta={}, earningsPerShare={}, dividendFrequency={}, " + "exDividendAmount={}, exDividendDay={}, shares={}, freeFloat={}}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + getDescription(), getShortSaleRestriction().toString(), getTradingStatus().toString(), + getStatusReason(), formatTimeStamp(getHaltStartTime()), formatTimeStamp(getHaltEndTime()), + getHighLimitPrice(), getLowLimitPrice(), getHigh52WeekPrice(), getLow52WeekPrice(), + getBeta(), getEarningsPerShare(), getDividendFrequency(), getExDividendAmount(), + day_util::getYearMonthDayByDayId(getExDividendDayId()), getShares(), getFreeFloat()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/market/Quote.cpp b/src/event/market/Quote.cpp index 31476141..b2984c0b 100644 --- a/src/event/market/Quote.cpp +++ b/src/event/market/Quote.cpp @@ -9,6 +9,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &Quote::Type = EventTypeEnum::QUOTE; @@ -62,4 +67,14 @@ std::shared_ptr Quote::fromGraalNative(void *graalNative) noexcept { } } +std::string Quote::toString() const noexcept { + return fmt::format( + "Quote{{{}, eventTime={}, time={}, timeNanoPart={}, sequence={}, bidTime={}, bidExchange={}, bidPrice={}, " + "bidSize={}, askTime={}, askExchange={}, askPrice={}, askSize={}}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + formatTimeStampWithMillis(getTime()), getTimeNanoPart(), getSequence(), formatTimeStamp(getBidTime()), + encodeChar(getBidExchangeCode()), getBidPrice(), getBidSize(), formatTimeStamp(getAskTime()), + encodeChar(getAskExchangeCode()), getAskPrice(), getAskSize()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/market/Summary.cpp b/src/event/market/Summary.cpp index 722d99fa..b4a50707 100644 --- a/src/event/market/Summary.cpp +++ b/src/event/market/Summary.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &Summary::Type = EventTypeEnum::SUMMARY; @@ -49,4 +54,15 @@ std::shared_ptr Summary::fromGraalNative(void *graalNative) noexcept { } } +std::string Summary::toString() const noexcept { + return fmt::format("Summary{{{}, eventTime={}, day={}, dayOpen={}, dayHigh={}, dayLow='{}', " + "dayClose={}, dayCloseType={}, prevDay={}, prevDayClose={}, prevDayCloseType={}, " + "prevDayVolume={}, openInterest={}}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + day_util::getYearMonthDayByDayId(getDayId()), getDayOpenPrice(), getDayHighPrice(), + getDayLowPrice(), getDayLowPrice(), getDayClosePrice(), getDayClosePriceType().toString(), + day_util::getYearMonthDayByDayId(getPrevDayId()), getPrevDayClosePrice(), + getPrevDayClosePriceType().toString(), getPrevDayVolume(), getOpenInterest()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/market/TimeAndSale.cpp b/src/event/market/TimeAndSale.cpp index 760db3dd..3fbf5870 100644 --- a/src/event/market/TimeAndSale.cpp +++ b/src/event/market/TimeAndSale.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { void TimeAndSale::setExchangeCode(char exchangeCode) { data_.exchangeCode = utf8to16(exchangeCode); } @@ -55,4 +60,18 @@ std::shared_ptr TimeAndSale::fromGraalNative(void *graalNative) noe } } +std::string TimeAndSale::toString() const noexcept { + return fmt::format("TimeAndSale{{{}, eventTime={}, eventFlags={:#x}, time={}, timeNanoPart={}, sequence={}, " + "exchange={}, price={}, size={}, bid={}, " + "ask={}, ESC='{}', TTE={}, side={}, spread={}, ETH={}, validTick={}, type={}{}{}}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + getEventFlags().getMask(), formatTimeStampWithMillis(getTime()), getTimeNanoPart(), + getSequence(), encodeChar(getExchangeCode()), getPrice(), getSize(), getBidPrice(), + getAskPrice(), getExchangeSaleConditions(), encodeChar(getTradeThroughExempt()), + getAggressorSide().toString(), isSpreadLeg(), isExtendedTradingHours(), isValidTick(), + getType().toString(), + getBuyer().empty() ? std::string{} : fmt::format(", buyer='{}'", getBuyer()), + getSeller().empty() ? std::string{} : fmt::format(", seller='{}'", getSeller())); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/market/Trade.cpp b/src/event/market/Trade.cpp index b570541b..e1e2c904 100644 --- a/src/event/market/Trade.cpp +++ b/src/event/market/Trade.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &Trade::Type = EventTypeEnum::TRADE; @@ -20,4 +25,6 @@ std::shared_ptr Trade::fromGraalNative(void *graalNative) noexcept { graalNative); } +std::string Trade::toString() const noexcept { return fmt::format("Trade{{{}}}", baseFieldsToString()); } + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/market/TradeBase.cpp b/src/event/market/TradeBase.cpp index 4958cc54..0f6cc3c0 100644 --- a/src/event/market/TradeBase.cpp +++ b/src/event/market/TradeBase.cpp @@ -11,8 +11,24 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { void TradeBase::setExchangeCode(char exchangeCode) { data_.exchangeCode = utf8to16(exchangeCode); } +std::string TradeBase::baseFieldsToString() const noexcept { + return fmt::format("{}, eventTime={}, time={}, timeNanoPart={}, sequence={}, exchange={}, price={}, " + "change={}, size={}, day={}, dayVolume={}, dayTurnover={}, " + "direction={}, ETH={}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + formatTimeStampWithMillis(getTime()), getTimeNanoPart(), getSequence(), + encodeChar(getExchangeCode()), getPrice(), getChange(), getSize(), + day_util::getYearMonthDayByDayId(getDayId()), getDayVolume(), getDayTurnover(), + getTickDirection().toString(), isExtendedTradingHours()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/market/TradeETH.cpp b/src/event/market/TradeETH.cpp index cf7aa867..afce4b29 100644 --- a/src/event/market/TradeETH.cpp +++ b/src/event/market/TradeETH.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &TradeETH::Type = EventTypeEnum::TRADE_ETH; @@ -20,4 +25,6 @@ std::shared_ptr TradeETH::fromGraalNative(void *graalNative) noexcept dxfg_event_clazz_t::DXFG_EVENT_TRADE_ETH>(graalNative); } +std::string TradeETH::toString() const noexcept { return fmt::format("TradeETH{{{}}}", baseFieldsToString()); } + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/option/Greeks.cpp b/src/event/option/Greeks.cpp index c67545b7..b2d26912 100644 --- a/src/event/option/Greeks.cpp +++ b/src/event/option/Greeks.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &Greeks::Type = EventTypeEnum::GREEKS; @@ -44,5 +49,13 @@ std::shared_ptr Greeks::fromGraalNative(void *graalNative) noexcept { return {}; } } +std::string Greeks::toString() const noexcept { + return fmt::format( + "Greeks{{{}, eventTime={}, eventFlags={:#x}, time={}, sequence={}, price={}, volatility={}, delta={}, " + "gamma={}, theta={}, rho={}, vega={}}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + getEventFlags().getMask(), formatTimeStampWithMillis(getTime()), getSequence(), getPrice(), getVolatility(), + getDelta(), getGamma(), getTheta(), getRho(), getVega()); +} } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/option/Series.cpp b/src/event/option/Series.cpp index 432f4380..72308c13 100644 --- a/src/event/option/Series.cpp +++ b/src/event/option/Series.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &Series::Type = EventTypeEnum::SERIES; @@ -45,4 +50,14 @@ std::shared_ptr Series::fromGraalNative(void *graalNative) noexcept { } } +std::string Series::toString() const noexcept { + return fmt::format( + "Series{{{}, eventTime={}, eventFlags={:#x}, index={:#x}, time={}, sequence={}, expiration={}, " + "volatility={}, callVolume={}, putVolume={}, putCallRatio={}, forwardPrice={}, dividend={}, interest={}}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + getEventFlags().getMask(), getIndex(), formatTimeStampWithMillis(getTime()), getSequence(), + day_util::getYearMonthDayByDayId(getExpiration()), getVolatility(), getCallVolume(), getPutVolume(), + getPutCallRatio(), getForwardPrice(), getDividend(), getInterest()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/option/TheoPrice.cpp b/src/event/option/TheoPrice.cpp index 93a1b8a7..65f9fd8f 100644 --- a/src/event/option/TheoPrice.cpp +++ b/src/event/option/TheoPrice.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &TheoPrice::Type = EventTypeEnum::THEO_PRICE; @@ -44,4 +49,13 @@ std::shared_ptr TheoPrice::fromGraalNative(void *graalNative) noexcep } } +std::string TheoPrice::toString() const noexcept { + return fmt::format( + "TheoPrice{{{}, eventTime={}, eventFlags={:#x}, time={}, sequence={}, price={}, underlyingPrice={}, " + "delta={}, gamma={}, dividend={}, interest={}}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + getEventFlags().getMask(), formatTimeStampWithMillis(getTime()), getSequence(), getPrice(), + getUnderlyingPrice(), getDelta(), getGamma(), getDividend(), getInterest()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/event/option/Underlying.cpp b/src/event/option/Underlying.cpp index 3563ef9e..6e74ab47 100644 --- a/src/event/option/Underlying.cpp +++ b/src/event/option/Underlying.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + namespace dxfcpp { const EventTypeEnum &Underlying::Type = EventTypeEnum::UNDERLYING; @@ -44,4 +49,13 @@ std::shared_ptr Underlying::fromGraalNative(void *graalNative) noexc } } +std::string Underlying::toString() const noexcept { + return fmt::format( + "Underlying{{{}, eventTime={}, eventFlags={:#x}, time={}, sequence={}, volatility={}, frontVolatility={}, " + "backVolatility={}, callVolume={}, putVolume={}, putCallRatio={}}}", + MarketEvent::getEventSymbol(), formatTimeStampWithMillis(MarketEvent::getEventTime()), + getEventFlags().getMask(), formatTimeStampWithMillis(getTime()), getSequence(), getVolatility(), + getFrontVolatility(), getBackVolatility(), getCallVolume(), getPutVolume(), getPutCallRatio()); +} + } // namespace dxfcpp \ No newline at end of file diff --git a/src/internal/Common.cpp b/src/internal/Common.cpp index 1e267fb2..cdc2fb1d 100644 --- a/src/internal/Common.cpp +++ b/src/internal/Common.cpp @@ -5,158 +5,3 @@ #include #include - -#include -#include -#include -#include - -namespace dxfcpp { - -namespace handler_utils { - -template void JavaObjectHandler::deleter(void *handler) noexcept { - runIsolatedOrElse( - [handler = handler](auto threadHandle) { - if (handler) { - return dxfg_JavaObjectHandler_release(threadHandle, bit_cast(handler)) == 0; - } - - return true; - }, - false); -} - -struct EventClassList::Impl { - dxfg_event_clazz_list_t list_; - - Impl() noexcept : list_{0, nullptr} {} - - void set(std::size_t index, std::uint32_t id) const noexcept { - if (list_.size == 0) { - return; - } - - if (index < list_.size) { - *list_.elements[index] = static_cast(id); - } - } - - [[nodiscard]] bool isEmpty() const noexcept { return list_.size == 0; } - - [[nodiscard]] std::size_t size() const noexcept { return static_cast(list_.size); } - - void *getHandler() noexcept { return bit_cast(&list_); } - - void init(std::uint32_t size) noexcept { - if (size <= 0) { - return; - } - - list_.size = static_cast(size); - list_.elements = new (std::nothrow) dxfg_event_clazz_t *[list_.size]; - - if (!list_.elements) { - release(); - - return; - } - - bool needToRelease = false; - - for (std::int32_t i = 0; i < list_.size; i++) { - list_.elements[i] = new (std::nothrow) dxfg_event_clazz_t{}; - - if (!list_.elements[i]) { - needToRelease = true; - } - } - - if (needToRelease) { - release(); - } - } - - void release() { - if (list_.size == 0 || list_.elements == nullptr) { - return; - } - - for (auto i = list_.size - 1; i >= 0; i--) { - delete list_.elements[i]; - } - - delete[] list_.elements; - list_.size = 0; - list_.elements = nullptr; - } - - ~Impl() noexcept { release(); } -}; - -template struct handler_utils::JavaObjectHandler; -template struct handler_utils::JavaObjectHandler; -template struct handler_utils::JavaObjectHandler; -template struct handler_utils::JavaObjectHandler; -template struct handler_utils::JavaObjectHandler; -template struct handler_utils::JavaObjectHandler; - -EventClassList::EventClassList() noexcept : impl_(std::make_unique()) {} - -std::unique_ptr EventClassList::create(std::size_t size) noexcept { - auto result = std::unique_ptr(new EventClassList{}); - - result->impl_->init(static_cast(size)); - - return result; -} - -void EventClassList::set(std::size_t index, std::uint32_t id) noexcept { impl_->set(index, id); } - -bool EventClassList::isEmpty() const noexcept { return impl_->isEmpty(); } - -std::size_t EventClassList::size() const noexcept { return impl_->size(); } - -void *EventClassList::getHandler() noexcept { return impl_->getHandler(); } - -EventClassList::~EventClassList() noexcept {} - -} - -std::string toString(const char *chars) { - if (chars == nullptr) { - return ""; - } - - return chars; -} - -char utf16to8(std::int16_t in) { - try { - std::string out{}; - auto utf16in = {in}; - - utf8::utf16to8(std::begin(utf16in), std::end(utf16in), std::back_inserter(out)); - - return out.empty() ? char{} : out[0]; - } catch (...) { - // TODO: error handling - return char{}; - } -} - -std::int16_t utf8to16(char in) { - try { - std::u16string out{}; - auto utf8in = {in}; - - utf8::utf8to16(std::begin(utf8in), std::end(utf8in), std::back_inserter(out)); - - return out.empty() ? std::int16_t{} : static_cast(out[0]); - } catch (...) { - // TODO: error handling - return std::int16_t{}; - } -} - -} \ No newline at end of file diff --git a/src/internal/EventClassList.cpp b/src/internal/EventClassList.cpp new file mode 100644 index 00000000..98ab413d --- /dev/null +++ b/src/internal/EventClassList.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include +#include + +namespace dxfcpp { + +struct EventClassList::Impl : public RawListWrapper(id); +}> {}; + +EventClassList::EventClassList() noexcept : impl_(std::make_unique()) {} + +std::unique_ptr EventClassList::create(std::size_t size) noexcept { + auto result = std::unique_ptr(new (std::nothrow) EventClassList{}); + + //TODO: error handling + if (result) { + result->impl_->init(static_cast(size)); + } + + return result; +} + +void EventClassList::set(std::size_t index, std::uint32_t id) noexcept { impl_->set(index, id); } + +bool EventClassList::isEmpty() const noexcept { return impl_->isEmpty(); } + +std::size_t EventClassList::size() const noexcept { return impl_->size(); } + +void *EventClassList::getHandler() noexcept { return impl_->getHandler(); } + +EventClassList::~EventClassList() noexcept = default; + +} // namespace dxfcpp \ No newline at end of file diff --git a/src/internal/JavaObjectHandler.cpp b/src/internal/JavaObjectHandler.cpp new file mode 100644 index 00000000..fab4caf2 --- /dev/null +++ b/src/internal/JavaObjectHandler.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include +#include + +namespace dxfcpp { + +template void JavaObjectHandler::deleter(void *handler) noexcept { + runIsolatedOrElse( + [handler = handler](auto threadHandle) { + if (handler) { + return dxfg_JavaObjectHandler_release(threadHandle, bit_cast(handler)) == 0; + } + + return true; + }, + false); +} + +template struct JavaObjectHandler; +template struct JavaObjectHandler; +template struct JavaObjectHandler; +template struct JavaObjectHandler; +template struct JavaObjectHandler; +template struct JavaObjectHandler; + +} // namespace dxfcpp \ No newline at end of file diff --git a/src/internal/SymbolList.cpp b/src/internal/SymbolList.cpp new file mode 100644 index 00000000..108fbfef --- /dev/null +++ b/src/internal/SymbolList.cpp @@ -0,0 +1,153 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include +#include + +namespace dxfcpp { + +std::string toString(const dxfg_symbol_list &graalSymbolList); + +// TODO: CandleSymbol, indexed_event_subscription_symbol, time_series_subscription_symbol, +// ::toGraal +struct SymbolList::Impl : public RawListWrapper(symbolWrapper.toGraal()); +}> { + std::string toString() const noexcept { return "SymbolList::Impl{list = " + dxfcpp::toString(list_) + "}"; } +}; + +SymbolList::SymbolList() noexcept : impl_(std::make_unique()) {} + +std::unique_ptr SymbolList::create(std::size_t size) noexcept { + auto result = std::unique_ptr(new SymbolList{}); + + result->impl_->init(static_cast(size)); + + return result; +} + +void SymbolList::set(std::size_t index, const SymbolWrapper &symbolWrapper) noexcept { + impl_->set(index, symbolWrapper); +} + +bool SymbolList::isEmpty() const noexcept { return impl_->isEmpty(); } + +std::size_t SymbolList::size() const noexcept { return impl_->size(); } + +void *SymbolList::getHandler() noexcept { return impl_->getHandler(); } + +SymbolList::~SymbolList() noexcept = default; + +std::string SymbolList::toString() const noexcept { return "SymbolList{impl = " + impl_->toString() + "}"; } + +std::string toString(const dxfg_indexed_event_source_t &graalEventSource) { + std::string result = "dxfg_indexed_event_source_t{"; + + switch (graalEventSource.type) { + + case INDEXED_EVENT_SOURCE: + result += "type = INDEXED_EVENT_SOURCE"; + + break; + + case ORDER_SOURCE: + result += "type = ORDER_SOURCE"; + + break; + } + + result += ", id = " + toStringAny(graalEventSource.id); + result += std::string(", name = ") + (graalEventSource.name ? "null" : graalEventSource.name); + + return result; +} + +std::string toString(const dxfg_symbol_t *graalSymbol) { + std::string result = "dxfg_symbol_t{type = "; + switch (graalSymbol->type) { + + case STRING: { + result += "STRING"; + + const auto *stringSymbol = bit_cast(graalSymbol); + + result += std::string(", symbol = ") + (!stringSymbol->symbol ? "null" : stringSymbol->symbol); + + break; + } + case CANDLE: { + result += "CANDLE"; + + const auto *candleSymbol = bit_cast(graalSymbol); + + result += std::string(", symbol = ") + (!candleSymbol->symbol ? "null" : candleSymbol->symbol); + + break; + } + case WILDCARD: + result += "WILDCARD"; + break; + case INDEXED_EVENT_SUBSCRIPTION: { + result += "INDEXED_EVENT_SUBSCRIPTION"; + + const auto *indexedEventSubscriptionSymbol = + bit_cast(graalSymbol); + + auto symbol = + !indexedEventSubscriptionSymbol->symbol ? "null" : toString(indexedEventSubscriptionSymbol->symbol); + + result += ",symbol = " + symbol; + result += + ",source = " + + (!indexedEventSubscriptionSymbol->source ? "null" : toString(*indexedEventSubscriptionSymbol->source)); + + break; + } + case TIME_SERIES_SUBSCRIPTION: { + result += "TIME_SERIES_SUBSCRIPTION"; + + const auto *timeSeriesSubscriptionSymbol = + bit_cast(graalSymbol); + + auto symbol = !timeSeriesSubscriptionSymbol->symbol ? "null" : toString(timeSeriesSubscriptionSymbol->symbol); + + result += ",symbol = " + symbol; + result += ",from_time = " + formatTimeStampWithMillis(timeSeriesSubscriptionSymbol->from_time); + + break; + } + default: + result += "UNKNOWN(" + std::to_string(graalSymbol->type) + ")"; + } + + result += "}"; + + return result; +} + +std::string toString(const dxfg_symbol_list &graalSymbolList) { + std::string result = "dxfg_symbol_list{"; + + result += "size = " + std::to_string(graalSymbolList.size); + result += ", elements = "; + + if (!graalSymbolList.elements) { + result += "null"; + } else { + result += "["; + for (auto i = 0; i < graalSymbolList.size; i++) { + result += !graalSymbolList.elements[i] ? "null" : toString(graalSymbolList.elements[i]) + ", "; + } + + result += "]"; + } + + result += "}"; + + return result; +} + +} // namespace dxfcpp \ No newline at end of file diff --git a/src/internal/utils/StringUtils.cpp b/src/internal/utils/StringUtils.cpp new file mode 100644 index 00000000..3126d19d --- /dev/null +++ b/src/internal/utils/StringUtils.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace dxfcpp { + +std::string toString(bool b) { return b ? "true" : "false"; } + +std::string toString(const char *chars) { + if (chars == nullptr) { + return ""; + } + + return chars; +} + +std::string toString(std::thread::id threadId) { + std::ostringstream result{}; + + result << threadId; + + return result.str(); +} + +std::string toString(void *ptr) { + std::ostringstream result{}; + + result << ptr; + + return result.str(); +} + +std::string encodeChar(std::int16_t c) { + if (c >= 32 && c <= 126) { + return std::string{} + static_cast(c); + } + + if (c == 0) { + return "\\0"; + } + + return fmt::format("\\u{:04x}", c); +} + +char utf16to8(std::int16_t in) { + try { + std::string out{}; + auto utf16in = {in}; + + utf8::utf16to8(std::begin(utf16in), std::end(utf16in), std::back_inserter(out)); + + return out.empty() ? char{} : out[0]; + } catch (...) { + // TODO: error handling + return char{}; + } +} + +std::int16_t utf8to16(char in) { + try { + std::u16string out{}; + auto utf8in = {in}; + + utf8::utf8to16(std::begin(utf8in), std::end(utf8in), std::back_inserter(out)); + + return out.empty() ? std::int16_t{} : static_cast(out[0]); + } catch (...) { + // TODO: error handling + return std::int16_t{}; + } +} + +std::string formatTimeStamp(std::int64_t timestamp) { + auto tm = fmt::localtime(static_cast(timestamp / 1000)); + + return fmt::format("{:%y%m%d-%H%M%S%z}", tm); +} + +std::string formatTimeStampWithMillis(std::int64_t timestamp) { + auto ms = timestamp % 1000; + auto tm = fmt::localtime(static_cast(timestamp / 1000)); + + return fmt::format("{:%y%m%d-%H%M%S}.{:0>3}{:%z}", tm, ms, tm); +} + +} \ No newline at end of file diff --git a/src/internal/utils/debug/Debug.cpp b/src/internal/utils/debug/Debug.cpp new file mode 100644 index 00000000..6b900856 --- /dev/null +++ b/src/internal/utils/debug/Debug.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace dxfcpp { + +#if DXFCPP_DEBUG == 0 +#else +template std::string vformat(std::string_view format, Args &&...args) { + return fmt::vformat(format, fmt::make_format_args(args...)); +} + +template void vprint(std::ostream &os, std::string_view format, Args &&...args) { + fmt::vprint(os, format, fmt::make_format_args(args...)); +} + +std::string Debugger::nowStr() { + auto now = std::chrono::system_clock::now(); + auto ms = std::chrono::duration_cast(now.time_since_epoch()).count() % 1000; + + return fmt::format("{:%y%m%d %H%M%S}.{:0>3}", std::chrono::floor(now), ms); +} + +std::string Debugger::nowStrWithTimeZone() { + auto now = std::chrono::system_clock::now(); + auto ms = std::chrono::duration_cast(now.time_since_epoch()).count() % 1000; + + return fmt::format("{:%y%m%d-%H%M%S}.{:0>3}{:%z}", std::chrono::floor(now), ms, + std::chrono::floor(now)); +} + +std::string Debugger::debugPrefixStr() { + return fmt::format("D {} [{}]", nowStr(), toString(std::this_thread::get_id())); +} + + void Debugger::debug(std::string str) { + vprint(std::cerr, "{} {}\n", debugPrefixStr(), std::move(str)); + } + +# if DXFCPP_TRACE == 1 +std::string Debugger::tracePrefixStr() { + return fmt::format("T {} [{}]", nowStr(), toString(std::this_thread::get_id())); +} + +void Debugger::trace(std::string str) { + vprint(std::cerr, "{} ~~ {}\n", tracePrefixStr(), std::move(str)); +} +# else +inline void Debugger::trace(...) {} +# endif + +#endif + +} // namespace dxfcpp diff --git a/src/symbols/StringSymbol.cpp b/src/symbols/StringSymbol.cpp new file mode 100644 index 00000000..fdb9257d --- /dev/null +++ b/src/symbols/StringSymbol.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include +#include + +#include + +namespace dxfcpp { + +struct StringSymbol::Impl final { + dxfg_string_symbol_t graalSymbol; + + Impl() noexcept : graalSymbol{{STRING}, nullptr} {} + + Impl(const Impl &impl) noexcept { + graalSymbol.supper = {STRING}; + graalSymbol.symbol = nullptr; + } + + Impl(Impl &&impl) noexcept { + graalSymbol.supper = {STRING}; + graalSymbol.symbol = nullptr; + } + + Impl &operator=(const Impl &impl) noexcept { + if (this == &impl) { + return *this; + } + + graalSymbol.supper = {STRING}; + graalSymbol.symbol = nullptr; + + return *this; + } + + Impl &operator=(Impl &&impl) noexcept { + if (this == &impl) { + return *this; + } + + graalSymbol.supper = {STRING}; + graalSymbol.symbol = nullptr; + + return *this; + } +}; + +StringSymbol::StringSymbol(const StringSymbol &stringSymbol) noexcept : impl_{std::make_unique()} { + data_ = stringSymbol.data_; +} + +StringSymbol::StringSymbol(StringSymbol &&stringSymbol) noexcept : impl_{std::make_unique()} { + data_ = std::move(stringSymbol.data_); +} + +StringSymbol &StringSymbol::operator=(const StringSymbol &stringSymbol) noexcept { + if (this == &stringSymbol) { + return *this; + } + + impl_ = std::make_unique(); + data_ = stringSymbol.data_; + + return *this; +} + +StringSymbol &StringSymbol::operator=(StringSymbol &&stringSymbol) noexcept { + if (this == &stringSymbol) { + return *this; + } + + impl_ = std::make_unique(); + data_ = std::move(stringSymbol.data_); + + return *this; +} + +StringSymbol::StringSymbol() noexcept : impl_{std::make_unique()} {} + +StringSymbol::~StringSymbol() noexcept = default; + +void *StringSymbol::toGraal() const noexcept { + if (impl_->graalSymbol.symbol == nullptr) { + impl_->graalSymbol.symbol = data_.c_str(); + + // std::visit([this](auto symbol) { impl_->graalSymbol.symbol = symbol.data(); }, data_); + } + + return bit_cast(&impl_->graalSymbol); +} + +const std::string &StringSymbol::getData() const { return data_; } + +std::string toString(const dxfg_symbol_t *graalSymbol); + +std::string graalSymbolToString(void *graalSymbol) { + return !graalSymbol ? "null" : toString(bit_cast(graalSymbol)); +} + +} // namespace dxfcpp \ No newline at end of file diff --git a/src/symbols/SymbolMapper.cpp b/src/symbols/SymbolMapper.cpp new file mode 100644 index 00000000..ffa274db --- /dev/null +++ b/src/symbols/SymbolMapper.cpp @@ -0,0 +1,20 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include +#include + +#include + +namespace dxfcpp { + +template +void* SymbolMapper::toNativeList(SymbolIt begin, SymbolIt end) noexcept { + + + return nullptr; +} + +} \ No newline at end of file diff --git a/src/symbols/SymbolWrapper.cpp b/src/symbols/SymbolWrapper.cpp new file mode 100644 index 00000000..6f4e88c6 --- /dev/null +++ b/src/symbols/SymbolWrapper.cpp @@ -0,0 +1,10 @@ +// Copyright (c) 2023 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include +#include + +namespace dxfcpp { + + +} \ No newline at end of file diff --git a/src/system/System.cpp b/src/system/System.cpp index 44ddcfd5..73f2d5a1 100644 --- a/src/system/System.cpp +++ b/src/system/System.cpp @@ -16,9 +16,9 @@ namespace dxfcpp { bool System::setProperty(const std::string &key, const std::string &value) { - //TODO: check invalid utf-8 - if constexpr (dxfcpp::isDebug) { - debug("System::setProperty(key = '{}', value = '{}')", key, value); + // TODO: check invalid utf-8 + if constexpr (Debugger::isDebug) { + Debugger::debug("System::setProperty(key = '" + key + "', value = '" + value + "')"); } auto result = runIsolatedOrElse( @@ -28,17 +28,17 @@ bool System::setProperty(const std::string &key, const std::string &value) { }, false); - if constexpr (dxfcpp::isDebug) { - debug("System::setProperty(key = '{}', value = '{}') -> {}", key, value, result); + if constexpr (Debugger::isDebug) { + Debugger::debug("System::setProperty(key = '" + key + "', value = '" + value + "') -> " + toString(result)); } return result; } std::string System::getProperty(const std::string &key) { - //TODO: check invalid utf-8 - if constexpr (isDebug) { - debug("System::getProperty(key = {})", key); + // TODO: check invalid utf-8 + if constexpr (Debugger::isDebug) { + Debugger::debug("System::getProperty(key = " + key + ")"); } auto result = runIsolatedOrElse( @@ -54,8 +54,8 @@ std::string System::getProperty(const std::string &key) { }, std::string{}); - if constexpr (isDebug) { - debug("System::getProperty(key = '{}') -> '{}'", key, result); + if constexpr (Debugger::isDebug) { + Debugger::debug("System::getProperty(key = '" + key + "') -> '" + result + "'"); } return result; @@ -64,7 +64,7 @@ std::string System::getProperty(const std::string &key) { } // namespace dxfcpp dxfc_error_code_t dxfc_system_set_property(const char *key, const char *value) { - //TODO: check invalid utf-8 + // TODO: check invalid utf-8 return dxfcpp::System::setProperty(key, value) ? DXFC_EC_SUCCESS : DXFC_EC_ERROR; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9e3cf92a..dbef0615 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,12 +20,12 @@ foreach (DXFC_TEST_SOURCE ${DXFC_TEST_SOURCES}) add_executable(${DXFC_TEST_BASENAME} ${DXFC_TEST_SOURCE}) target_include_directories(${DXFC_TEST_BASENAME} PUBLIC ${DXFC_TEST_INCLUDE_DIRS}) - target_link_libraries(${DXFC_TEST_BASENAME} PUBLIC DxFeedGraalNativeApi dxFeedGraalCxxApi) + target_link_libraries(${DXFC_TEST_BASENAME} PUBLIC DxFeedGraalNativeSdk dxFeedGraalCxxApi dxFeedGraalCxxApi_Clang) set_property(TARGET ${DXFC_TEST_BASENAME} PROPERTY CXX_STANDARD 20) set_property(TARGET ${DXFC_TEST_BASENAME} PROPERTY CMAKE_C_STANDARD 11) set_property(TARGET ${DXFC_TEST_BASENAME} PROPERTY CXX_EXTENSIONS OFF) add_test(NAME ${DXFC_TEST_BASENAME} COMMAND ${DXFC_TEST_BASENAME}) add_custom_command(TARGET ${DXFC_TEST_BASENAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ $) + $ $) endforeach () diff --git a/tests/api/DXEndpointTest.cpp b/tests/api/DXEndpointTest.cpp index af3622d6..0ea1c241 100644 --- a/tests/api/DXEndpointTest.cpp +++ b/tests/api/DXEndpointTest.cpp @@ -16,15 +16,15 @@ TEST_CASE("DXEndpoint::Builder") { auto endpoint = builder->build(); endpoint->onStateChange() += [](dxfcpp::DXEndpoint::State oldState, dxfcpp::DXEndpoint::State newState) { - dxfcpp::debug("DXEndpoint::Builder Test: {}", std::string("State changed: ") + + std::cerr << "DXEndpoint::Builder Test: {}" + std::string("State changed: ") + dxfcpp::DXEndpoint::stateToString(oldState) + " -> " + - dxfcpp::DXEndpoint::stateToString(newState)); + dxfcpp::DXEndpoint::stateToString(newState) << "\n"; }; endpoint->addStateChangeListener([](dxfcpp::DXEndpoint::State oldState, dxfcpp::DXEndpoint::State newState) { - dxfcpp::debug("DXEndpoint::Builder Test: {}", std::string("State changed 2: ") + - dxfcpp::DXEndpoint::stateToString(oldState) + " -> " + - dxfcpp::DXEndpoint::stateToString(newState)); + std::cerr << "DXEndpoint::Builder Test: {}" + std::string("State changed 2: ") + + dxfcpp::DXEndpoint::stateToString(oldState) + " -> " + + dxfcpp::DXEndpoint::stateToString(newState) << "\n"; }); endpoint->connect("demo.dxfeed.com:7300"); @@ -76,7 +76,7 @@ TEST_CASE("dxfc_dxendpoint_builder_t bug") { // // result = dxfc_dxendpoint_add_state_change_listener( // endpoint, [](dxfc_dxendpoint_state_t oldState, dxfc_dxendpoint_state_t newState, void *) { -// dxfcpp::debug("dxfc_dxendpoint_builder_t Test: {}", std::string("State changed: ") + +// Debugger::debug("dxfc_dxendpoint_builder_t Test: {}", std::string("State changed: ") + // cApiStateToString(oldState) + " -> " + // cApiStateToString(newState)); // }); diff --git a/third_party/fmt-9.1.0/CMakeLists.txt b/third_party/fmt-10.0.0/CMakeLists.txt similarity index 78% rename from third_party/fmt-9.1.0/CMakeLists.txt rename to third_party/fmt-10.0.0/CMakeLists.txt index 088a2a9c..fb650f41 100644 --- a/third_party/fmt-9.1.0/CMakeLists.txt +++ b/third_party/fmt-10.0.0/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1...3.18) +cmake_minimum_required(VERSION 3.8...3.26) # Fallback for using newer policies on CMake <3.12. if(${CMAKE_VERSION} VERSION_LESS 3.12) @@ -24,15 +24,86 @@ function(join result_var) set(${result_var} "${result}" PARENT_SCOPE) endfunction() +# DEPRECATED! Should be merged into add_module_library. function(enable_module target) if (MSVC) set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) target_compile_options(${target} PRIVATE /interface /ifcOutput ${BMI} INTERFACE /reference fmt=${BMI}) + set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) + set_source_files_properties(${BMI} PROPERTIES GENERATED ON) endif () - set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) - set_source_files_properties(${BMI} PROPERTIES GENERATED ON) +endfunction() + +# Adds a library compiled with C++20 module support. +# `enabled` is a CMake variables that specifies if modules are enabled. +# If modules are disabled `add_module_library` falls back to creating a +# non-modular library. +# +# Usage: +# add_module_library( [sources...] FALLBACK [sources...] [IF enabled]) +function(add_module_library name) + cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN}) + set(sources ${AML_UNPARSED_ARGUMENTS}) + + add_library(${name}) + set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX) + + if (NOT ${${AML_IF}}) + # Create a non-modular library. + target_sources(${name} PRIVATE ${AML_FALLBACK}) + return() + endif () + + # Modules require C++20. + target_compile_features(${name} PUBLIC cxx_std_20) + if (CMAKE_COMPILER_IS_GNUCXX) + target_compile_options(${name} PUBLIC -fmodules-ts) + endif () + + # `std` is affected by CMake options and may be higher than C++20. + get_target_property(std ${name} CXX_STANDARD) + + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(pcms) + foreach (src ${sources}) + get_filename_component(pcm ${src} NAME_WE) + set(pcm ${pcm}.pcm) + + # Propagate -fmodule-file=*.pcm to targets that link with this library. + target_compile_options( + ${name} PUBLIC -fmodule-file=${CMAKE_CURRENT_BINARY_DIR}/${pcm}) + + # Use an absolute path to prevent target_link_libraries prepending -l + # to it. + set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm}) + add_custom_command( + OUTPUT ${pcm} + COMMAND ${CMAKE_CXX_COMPILER} + -std=c++${std} -x c++-module --precompile -c + -o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src} + "-I$,;-I>" + # Required by the -I generator expression above. + COMMAND_EXPAND_LISTS + DEPENDS ${src}) + endforeach () + + # Add .pcm files as sources to make sure they are built before the library. + set(sources) + foreach (pcm ${pcms}) + get_filename_component(pcm_we ${pcm} NAME_WE) + set(obj ${pcm_we}.o) + # Use an absolute path to prevent target_link_libraries prepending -l. + set(sources ${sources} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj}) + add_custom_command( + OUTPUT ${obj} + COMMAND ${CMAKE_CXX_COMPILER} $ + -c -o ${obj} ${pcm} + DEPENDS ${pcm}) + endforeach () + endif () + target_sources(${name} PRIVATE ${sources}) endfunction() include(CMakeParseArguments) @@ -75,7 +146,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings." # Options that control generation of various targets. option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT}) -option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT}) +option(FMT_INSTALL "Generate the install target." ON) option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) @@ -83,16 +154,6 @@ option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) option(FMT_MODULE "Build a module instead of a traditional library." OFF) option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF) -set(FMT_CAN_MODULE OFF) -if (CMAKE_CXX_STANDARD GREATER 17 AND - # msvc 16.10-pre4 - MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035) - set(FMT_CAN_MODULE OFF) -endif () -if (NOT FMT_CAN_MODULE) - set(FMT_MODULE OFF) - message(STATUS "Module support is disabled.") -endif () if (FMT_TEST AND FMT_MODULE) # The tests require {fmt} to be compiled as traditional library message(STATUS "Testing is incompatible with build mode 'module'.") @@ -101,6 +162,10 @@ set(FMT_SYSTEM_HEADERS_ATTRIBUTE "") if (FMT_SYSTEM_HEADERS) set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) endif () +if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS") + set(FMT_TEST OFF) + message(STATUS "MSDOS is incompatible with gtest") +endif() # Get version from core.h file(READ include/fmt/core.h core_h) @@ -118,23 +183,15 @@ message(STATUS "Version: ${FMT_VERSION}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) endif () set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") -include(cxx14) +include(CheckCXXCompilerFlag) include(JoinPaths) -list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index) -if (${index} GREATER -1) - # Use cxx_variadic_templates instead of more appropriate cxx_std_11 for - # compatibility with older CMake versions. - set(FMT_REQUIRED_FEATURES cxx_variadic_templates) -endif () -message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}") - if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING "Preset for the export of private symbols") @@ -220,16 +277,18 @@ endfunction() add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h format-inl.h os.h ostream.h printf.h ranges.h std.h xchar.h) -if (FMT_MODULE) - set(FMT_SOURCES src/fmt.cc) -elseif (FMT_OS) - set(FMT_SOURCES src/format.cc src/os.cc) -else() - set(FMT_SOURCES src/format.cc) +set(FMT_SOURCES src/format.cc) +if (FMT_OS) + set(FMT_SOURCES ${FMT_SOURCES} src/os.cc) endif () -add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) +add_module_library(fmt src/fmt.cc FALLBACK + ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst + IF FMT_MODULE) add_library(fmt::fmt ALIAS fmt) +if (FMT_MODULE) + enable_module(fmt) +endif () if (FMT_WERROR) target_compile_options(fmt PRIVATE ${WERROR_FLAG}) @@ -237,11 +296,8 @@ endif () if (FMT_PEDANTIC) target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) endif () -if (FMT_MODULE) - enable_module(fmt) -endif () -target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) +target_compile_features(fmt PUBLIC cxx_std_11) target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC $ @@ -262,7 +318,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug") endif () if (BUILD_SHARED_LIBS) - target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED) + target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED) endif () if (FMT_SAFE_DURATION_CAST) target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST) @@ -272,7 +328,7 @@ add_library(fmt-header-only INTERFACE) add_library(fmt::fmt-header-only ALIAS fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) -target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) +target_compile_features(fmt-header-only INTERFACE cxx_std_11) target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE $ @@ -339,8 +395,6 @@ if (FMT_INSTALL) install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} NAMESPACE fmt::) - install(FILES $ - DESTINATION ${FMT_LIB_DIR} OPTIONAL) install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") endif () diff --git a/third_party/fmt-9.1.0/ChangeLog.rst b/third_party/fmt-10.0.0/ChangeLog.rst similarity index 91% rename from third_party/fmt-9.1.0/ChangeLog.rst rename to third_party/fmt-10.0.0/ChangeLog.rst index 4ebc5c73..2b3b9990 100644 --- a/third_party/fmt-9.1.0/ChangeLog.rst +++ b/third_party/fmt-10.0.0/ChangeLog.rst @@ -1,3 +1,434 @@ +10.0.0 - 2023-05-09 +------------------- + +* Replaced Grisu with a new floating-point formatting algorithm for given + precision (`#3262 `_, + `#2750 `_, + `#3269 `_, + `#3276 `_). + The new algorithm is based on Dragonbox already used for the + shortest representation and gives substantial performance improvement: + + .. image:: https://user-images.githubusercontent.com/33922675/ + 211956670-84891a09-6867-47d9-82fc-3230da7abe0f.png + + * Red: new algorithm + * Green: new algorithm with ``FMT_USE_FULL_CACHE_DRAGONBOX`` defined to 1 + * Blue: old algorithm + + Thanks `@jk-jeon (Junekey Jeon) `_. + +* Replaced ``snprintf``-based hex float formatter with an internal + implementation (`#3179 `_, + `#3203 `_). + This removes the last usage of ``s(n)printf`` in {fmt}. + Thanks `@phprus (Vladislav Shchapov) `_. + +* Fixed alignment of floating-point numbers with localization + (`#3263 `_, + `#3272 `_). + Thanks `@ShawnZhong (Shawn Zhong) `_. + +* Improved C++20 module support + (`#3134 `_, + `#3254 `_, + `#3386 `_, + `#3387 `_, + `#3388 `_, + `#3392 `_, + `#3397 `_, + `#3399 `_, + `#3400 `_). + Thanks `@laitingsheng (Tinson Lai) `_, + `@Orvid (Orvid King) `_, + `@DanielaE (Daniela Engert) `_. + Switched to the `modules CMake library `_ + which allows building {fmt} as a C++20 module with clang:: + + CXX=clang++ cmake -DFMT_MODULE=ON . + make + +* Made ``format_as`` work with any user-defined type and not just enums. + For example (`godbolt `__): + + .. code:: c++ + + #include + + struct floaty_mc_floatface { + double value; + }; + + auto format_as(floaty_mc_floatface f) { return f.value; } + + int main() { + fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints " 0.42" + } + +* Removed deprecated implicit conversions for enums and conversions to primitive + types for compatibility with ``std::format`` and to prevent potential ODR + violations. Use ``format_as`` instead. + +* Added support for fill, align and width to the time point formatter + (`#3237 `_, + `#3260 `_, + `#3275 `_). + For example (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + // prints " 2023" + fmt::print("{:>8%Y}\n", std::chrono::system_clock::now()); + } + + Thanks `@ShawnZhong (Shawn Zhong) `_. + +* Implemented formatting of subseconds + (`#2207 `_, + `#3117 `_, + `#3115 `_, + `#3143 `_, + `#3144 `_, + `#3349 `_). + For example (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + // prints 01.234567 + fmt::print("{:%S}\n", std::chrono::microseconds(1234567)); + } + + Thanks `@patrickroocks (Patrick Roocks) `_ + `@phprus (Vladislav Shchapov) `_, + `@BRevzin (Barry Revzin) `_. + +* Added precision support to ``%S`` + (`#3148 `_). + Thanks `@SappyJoy (Stepan Ponomaryov) `_ + +* Added support for ``std::utc_time`` + (`#3098 `_, + `#3110 `_). + Thanks `@patrickroocks (Patrick Roocks) `_. + +* Switched formatting of ``std::chrono::system_clock`` from local time to UTC + for compatibility with the standard + (`#3199 `_, + `#3230 `_). + Thanks `@ned14 (Niall Douglas) `_. + +* Added support for ``%Ez`` and ``%Oz`` to chrono formatters. + (`#3220 `_, + `#3222 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Improved validation of format specifiers for ``std::chrono::duration`` + (`#3219 `_, + `#3232 `_). + Thanks `@ShawnZhong (Shawn Zhong) `_. + +* Fixed formatting of time points before the epoch + (`#3117 `_, + `#3261 `_). + For example (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + auto t = std::chrono::system_clock::from_time_t(0) - + std::chrono::milliseconds(250); + fmt::print("{:%S}\n", t); // prints 59.750000000 + } + + Thanks `@ShawnZhong (Shawn Zhong) `_. + +* Experimental: implemented glibc extension for padding seconds, minutes and + hours (`#2959 `_, + `#3271 `_). + Thanks `@ShawnZhong (Shawn Zhong) `_. + +* Added a formatter for ``std::exception`` + (`#2977 `_, + `#3012 `_, + `#3062 `_, + `#3076 `_, + `#3119 `_). + For example (`godbolt `__): + + .. code:: c++ + + #include + #include + + int main() { + try { + std::vector().at(0); + } catch(const std::exception& e) { + fmt::print("{}", e); + } + } + + prints:: + + vector::_M_range_check: __n (which is 0) >= this->size() (which is 0) + + on libstdc++. + Thanks `@zach2good (Zach Toogood) `_ and + `@phprus (Vladislav Shchapov) `_. + +* Moved ``std::error_code`` formatter from ``fmt/os.h`` to ``fmt/std.h``. + (`#3125 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Added formatters for standard container adapters: ``std::priority_queue``, + ``std::queue`` and ``std::stack`` + (`#3215 `_, + `#3279 `_). + For example (`godbolt `__): + + .. code:: c++ + + #include + #include + #include + + int main() { + auto s = std::stack>(); + for (auto b: {true, false, true}) s.push(b); + fmt::print("{}\n", s); // prints [true, false, true] + } + + Thanks `@ShawnZhong (Shawn Zhong) `_. + +* Added a formatter for ``std::optional`` to ``fmt/std.h``. + Thanks `@tom-huntington `_. + +* Fixed formatting of valueless by exception variants + (`#3347 `_). + Thanks `@TheOmegaCarrot `_. + +* Made ``fmt::ptr`` accept ``unique_ptr`` with a custom deleter + (`#3177 `_). + Thanks `@hmbj (Hans-Martin B. Jensen) `_. + +* Fixed formatting of noncopyable ranges and nested ranges of chars + (`#3158 `_ + `#3286 `_, + `#3290 `_). + Thanks `@BRevzin (Barry Revzin) `_. + +* Fixed issues with formatting of paths and ranges of paths + (`#3319 `_, + `#3321 `_ + `#3322 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Improved handling of invalid Unicode in paths. + +* Enabled compile-time checks on Apple clang 14 and later + (`#3331 `_). + Thanks `@cloyce (Cloyce D. Spradling) `_. + +* Improved compile-time checks of named arguments + (`#3105 `_, + `#3214 `_). + Thanks `@rbrich (Radek Brich) `_. + +* Fixed formatting when both alignment and ``0`` are given + (`#3236 `_, + `#3248 `_). + Thanks `@ShawnZhong (Shawn Zhong) `_. + +* Improved Unicode support in the experimental file API on Windows + (`#3234 `_, + `#3293 `_). + Thanks `@Fros1er (Froster) `_. + +* Unified UTF transcoding + (`#3416 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Added support for UTF-8 digit separators via an experimental locale facet + (`#1861 `_). + For example (`godbolt `__): + + .. code:: c++ + + auto loc = std::locale( + std::locale(), new fmt::format_facet("’")); + auto s = fmt::format(loc, "{:L}", 1000); + + where ``’`` is U+2019 used as a digit separator in the de_CH locale. + +* Added an overload of ``formatted_size`` that takes a locale + (`#3084 `_, + `#3087 `_). + Thanks `@gerboengels `_. + +* Removed the deprecated ``FMT_DEPRECATED_OSTREAM``. + +* Fixed a UB when using a null ``std::string_view`` with ``fmt::to_string`` + or format string compilation + (`#3241 `_, + `#3244 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Added ``starts_with`` to the fallback ``string_view`` implementation + (`#3080 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Added ``fmt::basic_format_string::get()`` for compatibility with + ``basic_format_string`` (`#3111 `_). + Thanks `@huangqinjin `_. + +* Added ``println`` for compatibility with C++23 + (`#3267 `_). + Thanks `@ShawnZhong (Shawn Zhong) `_. + +* Improved documentation + (`#3108 `_, + `#3169 `_, + `#3243 `_). + `#3404 `_). + Thanks `@Cleroth `_ and + `@Vertexwahn `_. + +* Improved build configuration and tests + (`#3118 `_, + `#3120 `_, + `#3188 `_, + `#3189 `_, + `#3198 `_, + `#3205 `_, + `#3207 `_, + `#3210 `_, + `#3240 `_, + `#3256 `_, + `#3264 `_, + `#3299 `_, + `#3302 `_, + `#3312 `_, + `#3317 `_, + `#3328 `_, + `#3333 `_, + `#3369 `_, + `#3373 `_, + `#3395 `_, + `#3406 `_, + `#3411 `_). + Thanks `@dimztimz (Dimitrij Mijoski) `_, + `@phprus (Vladislav Shchapov) `_, + `@DavidKorczynski `_, + `@ChrisThrasher (Chris Thrasher) `_, + `@FrancoisCarouge (François Carouge) `_, + `@kennyweiss (Kenny Weiss) `_, + `@luzpaz `_, + `@codeinred (Alecto Irene Perez) `_, + `@Mixaill (Mikhail Paulyshka) `_, + `@joycebrum (Joyce) `_, + `@kevinhwang (Kevin Hwang) `_, + `@Vertexwahn `_. + +* Fixed a regression in handling empty format specifiers after a colon (``{:}``) + (`#3086 `_). + Thanks `@oxidase (Michael Krasnyk) `_. + +* Worked around a broken implementation of ``std::is_constant_evaluated`` in + some versions of libstdc++ on clang + (`#3247 `_, + `#3281 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Fixed formatting of volatile variables + (`#3068 `_). + +* Fixed various warnings and compilation issues + (`#3057 `_, + `#3066 `_, + `#3072 `_, + `#3082 `_, + `#3091 `_, + `#3092 `_, + `#3093 `_, + `#3095 `_, + `#3096 `_, + `#3097 `_, + `#3128 `_, + `#3129 `_, + `#3137 `_, + `#3139 `_, + `#3140 `_, + `#3142 `_, + `#3149 `_, + `#3150 `_, + `#3154 `_, + `#3163 `_, + `#3178 `_, + `#3184 `_, + `#3196 `_, + `#3204 `_, + `#3206 `_, + `#3208 `_, + `#3213 `_, + `#3216 `_, + `#3224 `_, + `#3226 `_, + `#3228 `_, + `#3229 `_, + `#3259 `_, + `#3274 `_, + `#3287 `_, + `#3288 `_, + `#3292 `_, + `#3295 `_, + `#3296 `_, + `#3298 `_, + `#3325 `_, + `#3326 `_, + `#3334 `_, + `#3342 `_, + `#3343 `_, + `#3351 `_, + `#3352 `_, + `#3362 `_, + `#3365 `_, + `#3366 `_, + `#3374 `_, + `#3377 `_, + `#3378 `_, + `#3381 `_, + `#3398 `_, + `#3413 `_, + `#3415 `_). + Thanks `@phprus (Vladislav Shchapov) `_, + `@gsjaardema (Greg Sjaardema) `_, + `@NewbieOrange `_, + `@EngineLessCC (VivyaCC) `_, + `@asmaloney (Andy Maloney) `_, + `@HazardyKnusperkeks (Björn Schäpers) + `_, + `@sergiud (Sergiu Deitsch) `_, + `@Youw (Ihor Dutchak) `_, + `@thesmurph `_, + `@czudziakm (Maksymilian Czudziak) `_, + `@Roman-Koshelev `_, + `@chronoxor (Ivan Shynkarenka) `_, + `@ShawnZhong (Shawn Zhong) `_, + `@russelltg (Russell Greene) `_, + `@glebm (Gleb Mazovetskiy) `_, + `@tmartin-gh `_, + `@Zhaojun-Liu (June Liu) `_, + `@louiswins (Louis Wilson) `_, + `@mogemimi `_. + 9.1.0 - 2022-08-27 ------------------ @@ -105,6 +536,7 @@ `#2982 `_, `#2985 `_, `#2988 `_, + `#2989 `_, `#3000 `_, `#3006 `_, `#3014 `_, @@ -2260,7 +2692,7 @@ `_. * Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ`` - macro to prevent interferring with fuzzing of projects using {fmt} + macro to prevent interfering with fuzzing of projects using {fmt} (`#1650 `_). Thanks `@asraa (Asra Ali) `_. diff --git a/third_party/fmt-9.1.0/LICENSE.rst b/third_party/fmt-10.0.0/LICENSE.rst similarity index 95% rename from third_party/fmt-9.1.0/LICENSE.rst rename to third_party/fmt-10.0.0/LICENSE.rst index f0ec3db4..1cd1ef92 100644 --- a/third_party/fmt-9.1.0/LICENSE.rst +++ b/third_party/fmt-10.0.0/LICENSE.rst @@ -1,4 +1,4 @@ -Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/third_party/fmt-9.1.0/README.rst b/third_party/fmt-10.0.0/README.rst similarity index 94% rename from third_party/fmt-9.1.0/README.rst rename to third_party/fmt-10.0.0/README.rst index cc6d7c41..6a639b6b 100644 --- a/third_party/fmt-9.1.0/README.rst +++ b/third_party/fmt-10.0.0/README.rst @@ -47,7 +47,8 @@ Features * `Format string syntax `_ similar to Python's `format `_ * Fast IEEE 754 floating-point formatter with correct rounding, shortness and - round-trip guarantees + round-trip guarantees using the `Dragonbox `_ + algorithm * Safe `printf implementation `_ including the POSIX extension for positional arguments @@ -191,24 +192,24 @@ Speed tests ================= ============= =========== Library Method Run Time, s ================= ============= =========== -libc printf 1.04 -libc++ std::ostream 3.05 -{fmt} 6.1.1 fmt::print 0.75 -Boost Format 1.67 boost::format 7.24 -Folly Format folly::format 2.23 +libc printf 0.91 +libc++ std::ostream 2.49 +{fmt} 9.1 fmt::print 0.74 +Boost Format 1.80 boost::format 6.26 +Folly Format folly::format 1.87 ================= ============= =========== -{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``. +{fmt} is the fastest of the benchmarked methods, ~20% faster than ``printf``. The above results were generated by building ``tinyformat_test.cpp`` on macOS -10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the +12.6.1 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"`` or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for further details refer to the `source `_. {fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on -floating-point formatting (`dtoa-benchmark `_) +IEEE754 ``float`` and ``double`` formatting (`dtoa-benchmark `_) and faster than `double-conversion `_ and `ryu `_: @@ -322,8 +323,10 @@ Projects using this library * `ccache `_: a compiler cache -* `ClickHouse `_: analytical database +* `ClickHouse `_: an analytical database management system + +* `Contour `_: a modern terminal emulator * `CUAUV `_: Cornell University's autonomous underwater vehicle @@ -360,6 +363,10 @@ Projects using this library * `Knuth `_: high-performance Bitcoin full-node +* `libunicode `_: a modern C++17 Unicode library + +* `MariaDB `_: relational database management system + * `Microsoft Verona `_: research programming language for concurrent ownership @@ -413,6 +420,9 @@ Projects using this library * `TrinityCore `_: open-source MMORPG framework +* `🐙 userver framework `_: open-source asynchronous + framework with a rich set of abstractions and database drivers + * `Windows Terminal `_: the new Windows terminal @@ -523,8 +533,7 @@ Maintainers ----------- The {fmt} library is maintained by Victor Zverovich (`vitaut -`_) and Jonathan Müller (`foonathan -`_) with contributions from many other people. +`_) with contributions from many other people. See `Contributors `_ and `Releases `_ for some of the names. Let us know if your contribution is not listed or mentioned incorrectly and diff --git a/third_party/fmt-9.1.0/include/fmt/args.h b/third_party/fmt-10.0.0/include/fmt/args.h similarity index 100% rename from third_party/fmt-9.1.0/include/fmt/args.h rename to third_party/fmt-10.0.0/include/fmt/args.h diff --git a/third_party/fmt-9.1.0/include/fmt/chrono.h b/third_party/fmt-10.0.0/include/fmt/chrono.h similarity index 78% rename from third_party/fmt-9.1.0/include/fmt/chrono.h rename to third_party/fmt-10.0.0/include/fmt/chrono.h index b112f76e..55e8a506 100644 --- a/third_party/fmt-9.1.0/include/fmt/chrono.h +++ b/third_party/fmt-10.0.0/include/fmt/chrono.h @@ -22,6 +22,24 @@ FMT_BEGIN_NAMESPACE +// Check if std::chrono::local_t is available. +#ifndef FMT_USE_LOCAL_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_LOCAL_TIME 0 +# endif +#endif + +// Check if std::chrono::utc_timestamp is available. +#ifndef FMT_USE_UTC_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_UTC_TIME 0 +# endif +#endif + // Enable tzset. #ifndef FMT_USE_TZSET // UWP doesn't provide _tzset. @@ -203,7 +221,8 @@ To safe_duration_cast(std::chrono::duration from, } const auto min1 = (std::numeric_limits::min)() / Factor::num; - if (!std::is_unsigned::value && count < min1) { + if (detail::const_check(!std::is_unsigned::value) && + count < min1) { ec = 1; return {}; } @@ -358,37 +377,11 @@ auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) unit_t unit; write_codecvt(unit, in, loc); // In UTF-8 is used one to four one-byte code units. - auto&& buf = basic_memory_buffer(); - for (code_unit* p = unit.buf; p != unit.end; ++p) { - uint32_t c = static_cast(*p); - if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { - // surrogate pair - ++p; - if (p == unit.end || (c & 0xfc00) != 0xd800 || - (*p & 0xfc00) != 0xdc00) { - FMT_THROW(format_error("failed to format time")); - } - c = (c << 10) + static_cast(*p) - 0x35fdc00; - } - if (c < 0x80) { - buf.push_back(static_cast(c)); - } else if (c < 0x800) { - buf.push_back(static_cast(0xc0 | (c >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - buf.push_back(static_cast(0xe0 | (c >> 12))); - buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else if (c >= 0x10000 && c <= 0x10ffff) { - buf.push_back(static_cast(0xf0 | (c >> 18))); - buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else { - FMT_THROW(format_error("failed to format time")); - } - } - return copy_str(buf.data(), buf.data() + buf.size(), out); + unicode_to_utf8> + u; + if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) + FMT_THROW(format_error("failed to format time")); + return copy_str(u.c_str(), u.c_str() + u.size(), out); } return copy_str(in.data(), in.data() + in.size(), out); } @@ -427,7 +420,7 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, char format, char modifier = 0) -> OutputIt { auto&& buf = get_buffer(out); do_write(buf, time, loc, format, modifier); - return buf.out(); + return get_iterator(buf, out); } template time_point) { - return localtime(std::chrono::system_clock::to_time_t(time_point)); +#if FMT_USE_LOCAL_TIME +template +inline auto localtime(std::chrono::local_time time) -> std::tm { + return localtime(std::chrono::system_clock::to_time_t( + std::chrono::current_zone()->to_sys(time))); } +#endif /** Converts given time since epoch as ``std::time_t`` value into calendar time, @@ -536,6 +532,49 @@ inline std::tm gmtime( FMT_BEGIN_DETAIL_NAMESPACE +// DEPRECATED! +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + format_specs& specs) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '}') return begin; + if (c == '{') { + throw_format_error("invalid fill character '{'"); + return begin; + } + specs.fill = {begin, to_unsigned(p - begin)}; + begin = p + 1; + } else { + ++begin; + } + break; + } else if (p == begin) { + break; + } + p = begin; + } + specs.align = align; + return begin; +} + // Writes two-digit numbers a, b and c separated by sep to buf. // The method by Pavel Novikov based on // https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. @@ -599,12 +638,39 @@ enum class numeric_system { alternative }; +// Glibc extensions for formatting numeric values. +enum class pad_type { + unspecified, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with zeros even if the conversion specifier + // character uses space-padding by default. + zero, + // Pad a numeric result string with spaces. + space, +}; + +template +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + // Parses a put_time-like format string and invokes handler actions. template FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, const Char* end, Handler&& handler) { + if (begin == end || *begin == '}') return begin; + if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; + pad_type pad = pad_type::unspecified; while (ptr != end) { auto c = *ptr; if (c == '}') break; @@ -615,6 +681,22 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + case '0': + pad = pad_type::zero; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case '%': @@ -691,16 +773,16 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, break; // Hour, minute, second: case 'H': - handler.on_24_hour(numeric_system::standard); + handler.on_24_hour(numeric_system::standard, pad); break; case 'I': - handler.on_12_hour(numeric_system::standard); + handler.on_12_hour(numeric_system::standard, pad); break; case 'M': - handler.on_minute(numeric_system::standard); + handler.on_minute(numeric_system::standard, pad); break; case 'S': - handler.on_second(numeric_system::standard); + handler.on_second(numeric_system::standard, pad); break; // Other: case 'c': @@ -737,7 +819,7 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_duration_unit(); break; case 'z': - handler.on_utc_offset(); + handler.on_utc_offset(numeric_system::standard); break; case 'Z': handler.on_tz_name(); @@ -765,6 +847,9 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, case 'X': handler.on_loc_time(numeric_system::alternative); break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); + break; default: FMT_THROW(format_error("invalid format")); } @@ -802,16 +887,19 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_dec1_weekday(numeric_system::alternative); break; case 'H': - handler.on_24_hour(numeric_system::alternative); + handler.on_24_hour(numeric_system::alternative, pad); break; case 'I': - handler.on_12_hour(numeric_system::alternative); + handler.on_12_hour(numeric_system::alternative, pad); break; case 'M': - handler.on_minute(numeric_system::alternative); + handler.on_minute(numeric_system::alternative, pad); break; case 'S': - handler.on_second(numeric_system::alternative); + handler.on_second(numeric_system::alternative, pad); + break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); @@ -864,7 +952,7 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void on_am_pm() { unsupported(); } FMT_CONSTEXPR void on_duration_value() { unsupported(); } FMT_CONSTEXPR void on_duration_unit() { unsupported(); } - FMT_CONSTEXPR void on_utc_offset() { unsupported(); } + FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; @@ -892,10 +980,10 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_day_of_year() {} FMT_CONSTEXPR void on_day_of_month(numeric_system) {} FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} FMT_CONSTEXPR void on_datetime(numeric_system) {} FMT_CONSTEXPR void on_loc_date(numeric_system) {} FMT_CONSTEXPR void on_loc_time(numeric_system) {} @@ -905,7 +993,7 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_utc_offset() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) {} FMT_CONSTEXPR void on_tz_name() {} }; @@ -957,13 +1045,130 @@ inline void tzset_once() { } #endif -template class tm_writer { +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { + FMT_ASSERT(std::is_unsigned::value || + (value >= 0 && to_unsigned(value) <= to_unsigned(upper)), + "invalid value"); + (void)upper; + return static_cast(value); +} +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { + if (value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return static_cast(value); +} + +constexpr long long pow10(std::uint32_t n) { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template () / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +// Format subseconds which are given as an integer type with an appropriate +// number of digits. +template +void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(num_fractional_digits)>>; + + const auto fractional = + d - std::chrono::duration_cast(d); + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : std::chrono::duration_cast(fractional).count(); + auto n = static_cast>(subseconds); + const int num_digits = detail::count_digits(n); + + int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); + if (precision < 0) { + FMT_ASSERT(!std::is_floating_point::value, ""); + if (std::ratio_less::value) { + *out++ = '.'; + out = std::fill_n(out, leading_zeroes, '0'); + out = format_decimal(out, n, num_digits).end; + } + } else { + *out++ = '.'; + leading_zeroes = (std::min)(leading_zeroes, precision); + out = std::fill_n(out, leading_zeroes, '0'); + int remaining = precision - leading_zeroes; + if (remaining != 0 && remaining < num_digits) { + n /= to_unsigned(detail::pow10(to_unsigned(num_digits - remaining))); + out = format_decimal(out, n, remaining).end; + return; + } + out = format_decimal(out, n, num_digits).end; + remaining -= num_digits; + out = std::fill_n(out, remaining, '0'); + } +} + +// Format subseconds which are given as a floating point type with an +// appropriate number of digits. We cannot pass the Duration here, as we +// explicitly need to pass the Rep value in the chrono_formatter. +template +void write_floating_seconds(memory_buffer& buf, Duration duration, + int num_fractional_digits = -1) { + using rep = typename Duration::rep; + FMT_ASSERT(std::is_floating_point::value, ""); + + auto val = duration.count(); + + if (num_fractional_digits < 0) { + // For `std::round` with fallback to `round`: + // On some toolchains `std::round` is not available (e.g. GCC 6). + using namespace std; + num_fractional_digits = + count_fractional_digits::value; + if (num_fractional_digits < 6 && static_cast(round(val)) != val) + num_fractional_digits = 6; + } + + format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast(Duration::period::num) / + static_cast(Duration::period::den), + static_cast(60)), + num_fractional_digits); +} + +template +class tm_writer { private: static constexpr int days_per_week = 7; const std::locale& loc_; const bool is_classic_; OutputIt out_; + const Duration* subsecs_; const std::tm& tm_; auto tm_sec() const noexcept -> int { @@ -1051,6 +1256,17 @@ template class tm_writer { *out_++ = *d++; *out_++ = *d; } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast('0' + v); + } + } void write_year_extended(long long year) { // At least 4 characters. @@ -1074,7 +1290,7 @@ template class tm_writer { } } - void write_utc_offset(long offset) { + void write_utc_offset(long offset, numeric_system ns) { if (offset < 0) { *out_++ = '-'; offset = -offset; @@ -1083,14 +1299,15 @@ template class tm_writer { } offset /= 60; write2(static_cast(offset / 60)); + if (ns != numeric_system::standard) *out_++ = ':'; write2(static_cast(offset % 60)); } template ::value)> - void format_utc_offset_impl(const T& tm) { - write_utc_offset(tm.tm_gmtoff); + void format_utc_offset_impl(const T& tm, numeric_system ns) { + write_utc_offset(tm.tm_gmtoff, ns); } template ::value)> - void format_utc_offset_impl(const T& tm) { + void format_utc_offset_impl(const T& tm, numeric_system ns) { #if defined(_WIN32) && defined(_UCRT) # if FMT_USE_TZSET tzset_once(); @@ -1102,10 +1319,17 @@ template class tm_writer { _get_dstbias(&dstbias); offset += dstbias; } - write_utc_offset(-offset); + write_utc_offset(-offset, ns); #else - ignore_unused(tm); - format_localized('z'); + if (ns == numeric_system::standard) return format_localized('z'); + + // Extract timezone offset from timezone conversion functions. + std::tm gtm = tm; + std::time_t gt = std::mktime(>m); + std::tm ltm = gmtime(gt); + std::time_t lt = std::mktime(<m); + long offset = gt - lt; + write_utc_offset(offset, ns); #endif } @@ -1126,10 +1350,12 @@ template class tm_writer { } public: - tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm) + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, + const Duration* subsecs = nullptr) : loc_(loc), is_classic_(loc_ == get_classic_locale()), out_(out), + subsecs_(subsecs), tm_(tm) {} OutputIt out() const { return out_; } @@ -1227,7 +1453,7 @@ template class tm_writer { out_ = copy_str(std::begin(buf) + offset, std::end(buf), out_); } - void on_utc_offset() { format_utc_offset_impl(tm_); } + void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } void on_tz_name() { format_tz_name_impl(tm_); } void on_year(numeric_system ns) { @@ -1315,22 +1541,41 @@ template class tm_writer { } } - void on_24_hour(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour()); + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); format_localized('H', 'O'); } - void on_12_hour(numeric_system ns) { + void on_12_hour(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) - return write2(tm_hour12()); + return write2(tm_hour12(), pad); format_localized('I', 'O'); } - void on_minute(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_min()); + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); format_localized('M', 'O'); } - void on_second(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_sec()); - format_localized('S', 'O'); + + void on_second(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec(), pad); + if (subsecs_) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, *subsecs_); + if (buf.size() > 1) { + // Remove the leading "0", write something like ".123". + out_ = std::copy(buf.begin() + 1, buf.end(), out_); + } + } else { + write_fractional_seconds(out_, *subsecs_); + } + } + } else { + // Currently no formatting of subseconds when a locale is set. + format_localized('S', 'O'); + } } void on_12_hour_time() { @@ -1351,10 +1596,9 @@ template class tm_writer { write2(tm_min()); } void on_iso_time() { - char buf[8]; - write_digit2_separated(buf, to_unsigned(tm_hour()), to_unsigned(tm_min()), - to_unsigned(tm_sec()), ':'); - out_ = copy_str(std::begin(buf), std::end(buf), out_); + on_24_hour_time(); + *out_++ = ':'; + on_second(numeric_system::standard, pad_type::unspecified); } void on_am_pm() { @@ -1372,43 +1616,34 @@ template class tm_writer { }; struct chrono_format_checker : null_chrono_spec_handler { + bool has_precision_integral = false; + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_duration_value() {} + FMT_CONSTEXPR void on_duration_value() const { + if (has_precision_integral) { + FMT_THROW(format_error("precision not allowed for this argument type")); + } + } FMT_CONSTEXPR void on_duration_unit() {} }; -template ::value)> +template ::value&& has_isfinite::value)> inline bool isfinite(T) { return true; } -// Converts value to Int and checks that it's in the range [0, upper). -template ::value)> -inline Int to_nonnegative_int(T value, Int upper) { - FMT_ASSERT(std::is_unsigned::value || - (value >= 0 && to_unsigned(value) <= to_unsigned(upper)), - "invalid value"); - (void)upper; - return static_cast(value); -} -template ::value)> -inline Int to_nonnegative_int(T value, Int upper) { - if (value < 0 || value > static_cast(upper)) - FMT_THROW(format_error("invalid value")); - return static_cast(value); -} - template ::value)> inline T mod(T x, int y) { return x % static_cast(y); @@ -1463,47 +1698,6 @@ inline std::chrono::duration get_milliseconds( #endif } -// Counts the number of fractional digits in the range [0, 18] according to the -// C++20 spec. If more than 18 fractional digits are required then returns 6 for -// microseconds precision. -template () / 10)> -struct count_fractional_digits { - static constexpr int value = - Num % Den == 0 ? N : count_fractional_digits::value; -}; - -// Base case that doesn't instantiate any more templates -// in order to avoid overflow. -template -struct count_fractional_digits { - static constexpr int value = (Num % Den == 0) ? N : 6; -}; - -constexpr long long pow10(std::uint32_t n) { - return n == 0 ? 1 : 10 * pow10(n - 1); -} - -template ::is_signed)> -constexpr std::chrono::duration abs( - std::chrono::duration d) { - // We need to compare the duration using the count() method directly - // due to a compiler bug in clang-11 regarding the spaceship operator, - // when -Wzero-as-null-pointer-constant is enabled. - // In clang-12 the bug has been fixed. See - // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example: - // https://www.godbolt.org/z/Knbb5joYx. - return d.count() >= d.zero().count() ? d : -d; -} - -template ::is_signed)> -constexpr std::chrono::duration abs( - std::chrono::duration d) { - return d; -} - template ::value)> OutputIt format_duration_value(OutputIt out, Rep val, int) { @@ -1513,7 +1707,7 @@ OutputIt format_duration_value(OutputIt out, Rep val, int) { template ::value)> OutputIt format_duration_value(OutputIt out, Rep val, int precision) { - auto specs = basic_format_specs(); + auto specs = format_specs(); specs.precision = precision; specs.type = precision >= 0 ? presentation_type::fixed_lower : presentation_type::general_lower; @@ -1654,44 +1848,16 @@ struct chrono_formatter { } } - void write(Rep value, int width) { + void write(Rep value, int width, pad_type pad = pad_type::unspecified) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); int num_digits = detail::count_digits(n); - if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); - out = format_decimal(out, n, num_digits).end; - } - - template void write_fractional_seconds(Duration d) { - FMT_ASSERT(!std::is_floating_point::value, ""); - constexpr auto num_fractional_digits = - count_fractional_digits::value; - - using subsecond_precision = std::chrono::duration< - typename std::common_type::type, - std::ratio<1, detail::pow10(num_fractional_digits)>>; - if (std::ratio_less::value) { - *out++ = '.'; - auto fractional = - detail::abs(d) - std::chrono::duration_cast(d); - auto subseconds = - std::chrono::treat_as_floating_point< - typename subsecond_precision::rep>::value - ? fractional.count() - : std::chrono::duration_cast(fractional) - .count(); - uint32_or_64_or_128_t n = - to_unsigned(to_nonnegative_int(subseconds, max_value())); - int num_digits = detail::count_digits(n); - if (num_fractional_digits > num_digits) - out = std::fill_n(out, num_fractional_digits - num_digits, '0'); - out = format_decimal(out, n, num_digits).end; + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); } + out = format_decimal(out, n, num_digits).end; } void write_nan() { std::copy_n("nan", 3, out); } @@ -1723,7 +1889,7 @@ struct chrono_formatter { void on_loc_time(numeric_system) {} void on_us_date() {} void on_iso_date() {} - void on_utc_offset() {} + void on_utc_offset(numeric_system) {} void on_tz_name() {} void on_year(numeric_system) {} void on_short_year(numeric_system) {} @@ -1739,58 +1905,56 @@ struct chrono_formatter { void on_day_of_month(numeric_system) {} void on_day_of_month_space(numeric_system) {} - void on_24_hour(numeric_system ns) { + void on_24_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour(), 2); + if (ns == numeric_system::standard) return write(hour(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); - format_tm(time, &tm_writer_type::on_24_hour, ns); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); } - void on_12_hour(numeric_system ns) { + void on_12_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour12(), 2); + if (ns == numeric_system::standard) return write(hour12(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); - format_tm(time, &tm_writer_type::on_12_hour, ns); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); } - void on_minute(numeric_system ns) { + void on_minute(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(minute(), 2); + if (ns == numeric_system::standard) return write(minute(), 2, pad); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); - format_tm(time, &tm_writer_type::on_minute, ns); + format_tm(time, &tm_writer_type::on_minute, ns, pad); } - void on_second(numeric_system ns) { + void on_second(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) { if (std::is_floating_point::value) { - constexpr auto num_fractional_digits = - count_fractional_digits::value; auto buf = memory_buffer(); - format_to(std::back_inserter(buf), runtime("{:.{}f}"), - std::fmod(val * static_cast(Period::num) / - static_cast(Period::den), - static_cast(60)), - num_fractional_digits); + write_floating_seconds(buf, std::chrono::duration(val), + precision); if (negative) *out++ = '-'; - if (buf.size() < 2 || buf[1] == '.') *out++ = '0'; + if (buf.size() < 2 || buf[1] == '.') { + out = detail::write_padding(out, pad); + } out = std::copy(buf.begin(), buf.end(), out); } else { - write(second(), 2); - write_fractional_seconds(std::chrono::duration(val)); + write(second(), 2, pad); + write_fractional_seconds( + out, std::chrono::duration(val), precision); } return; } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); - format_tm(time, &tm_writer_type::on_second, ns); + format_tm(time, &tm_writer_type::on_second, ns, pad); } void on_12_hour_time() { @@ -1814,7 +1978,7 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - on_second(numeric_system::standard); + on_second(numeric_system::standard, pad_type::unspecified); } void on_am_pm() { @@ -1883,7 +2047,7 @@ template struct formatter { template struct formatter, Char> { private: - basic_format_specs specs; + format_specs specs; int precision = -1; using arg_ref_type = detail::arg_ref; arg_ref_type width_ref; @@ -1892,45 +2056,6 @@ struct formatter, Char> { basic_string_view format_str; using duration = std::chrono::duration; - struct spec_handler { - formatter& f; - basic_format_parse_context& context; - basic_string_view format_str; - - template FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { - return arg_ref_type(context.next_arg_id()); - } - - void on_error(const char* msg) { FMT_THROW(format_error(msg)); } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - f.specs.fill = fill; - } - FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; } - FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; } - FMT_CONSTEXPR void on_precision(int _precision) { - f.precision = _precision; - } - FMT_CONSTEXPR void end_precision() {} - - template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - f.width_ref = make_arg_ref(arg_id); - } - - template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - f.precision_ref = make_arg_ref(arg_id); - } - }; - using iterator = typename basic_format_parse_context::iterator; struct parse_range { iterator begin; @@ -1940,23 +2065,24 @@ struct formatter, Char> { FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context& ctx) { auto begin = ctx.begin(), end = ctx.end(); if (begin == end || *begin == '}') return {begin, begin}; - spec_handler handler{*this, ctx, format_str}; - begin = detail::parse_align(begin, end, handler); + + begin = detail::parse_align(begin, end, specs); if (begin == end) return {begin, begin}; - begin = detail::parse_width(begin, end, handler); + + begin = detail::parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); if (begin == end) return {begin, begin}; + + auto checker = detail::chrono_format_checker(); if (*begin == '.') { - if (std::is_floating_point::value) - begin = detail::parse_precision(begin, end, handler); - else - handler.on_error("precision not allowed for this argument type"); + checker.has_precision_integral = !std::is_floating_point::value; + begin = + detail::parse_precision(begin, end, precision, precision_ref, ctx); } if (begin != end && *begin == 'L') { ++begin; localized = true; } - end = detail::parse_chrono_format(begin, end, - detail::chrono_format_checker()); + end = detail::parse_chrono_format(begin, end, checker); return {begin, end}; } @@ -2002,68 +2128,140 @@ template struct formatter, Char> : formatter { FMT_CONSTEXPR formatter() { - basic_string_view default_specs = - detail::string_literal{}; - this->do_parse(default_specs.begin(), default_specs.end()); + this->format_str = detail::string_literal{}; + } + + template + auto format(std::chrono::time_point val, + FormatContext& ctx) const -> decltype(ctx.out()) { + using period = typename Duration::period; + if (period::num != 1 || period::den != 1 || + std::is_floating_point::value) { + const auto epoch = val.time_since_epoch(); + auto subsecs = std::chrono::duration_cast( + epoch - std::chrono::duration_cast(epoch)); + + if (subsecs.count() < 0) { + auto second = std::chrono::seconds(1); + if (epoch.count() < ((Duration::min)() + second).count()) + FMT_THROW(format_error("duration is too small")); + subsecs += second; + val -= second; + } + + return formatter::do_format( + gmtime(std::chrono::time_point_cast(val)), ctx, + &subsecs); + } + + return formatter::format( + gmtime(std::chrono::time_point_cast(val)), ctx); + } +}; + +#if FMT_USE_LOCAL_TIME +template +struct formatter, Char> + : formatter { + FMT_CONSTEXPR formatter() { + this->format_str = detail::string_literal{}; + } + + template + auto format(std::chrono::local_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + using period = typename Duration::period; + if (period::num != 1 || period::den != 1 || + std::is_floating_point::value) { + const auto epoch = val.time_since_epoch(); + const auto subsecs = std::chrono::duration_cast( + epoch - std::chrono::duration_cast(epoch)); + + return formatter::do_format( + localtime(std::chrono::time_point_cast(val)), + ctx, &subsecs); + } + + return formatter::format( + localtime(std::chrono::time_point_cast(val)), + ctx); } +}; +#endif +#if FMT_USE_UTC_TIME +template +struct formatter, + Char> + : formatter, + Char> { template - auto format(std::chrono::time_point val, + auto format(std::chrono::time_point val, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter::format(localtime(val), ctx); + return formatter< + std::chrono::time_point, + Char>::format(std::chrono::utc_clock::to_sys(val), ctx); } }; +#endif template struct formatter { private: - enum class spec { - unknown, - year_month_day, - hh_mm_ss, - }; - spec spec_ = spec::unknown; - basic_string_view specs; + format_specs specs; + detail::arg_ref width_ref; protected: - template FMT_CONSTEXPR auto do_parse(It begin, It end) -> It { - if (begin != end && *begin == ':') ++begin; + basic_string_view format_str; + + FMT_CONSTEXPR auto do_parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin == end || *begin == '}') return begin; + + begin = detail::parse_align(begin, end, specs); + if (begin == end) return end; + + begin = detail::parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); + if (begin == end) return end; + end = detail::parse_chrono_format(begin, end, detail::tm_format_checker()); - // Replace default spec only if the new spec is not empty. - if (end != begin) specs = {begin, detail::to_unsigned(end - begin)}; + // Replace default format_str only if the new spec is not empty. + if (end != begin) format_str = {begin, detail::to_unsigned(end - begin)}; return end; } + template + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs_copy = specs; + basic_memory_buffer buf; + auto out = std::back_inserter(buf); + detail::handle_dynamic_spec(specs_copy.width, + width_ref, ctx); + + const auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = + detail::tm_writer(loc, out, tm, subsecs); + detail::parse_chrono_format(format_str.begin(), format_str.end(), w); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs_copy); + } + public: FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - auto end = this->do_parse(ctx.begin(), ctx.end()); - // basic_string_view<>::compare isn't constexpr before C++17. - if (specs.size() == 2 && specs[0] == Char('%')) { - if (specs[1] == Char('F')) - spec_ = spec::year_month_day; - else if (specs[1] == Char('T')) - spec_ = spec::hh_mm_ss; - } - return end; + return this->do_parse(ctx); } template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { - const auto loc_ref = ctx.locale(); - detail::get_locale loc(static_cast(loc_ref), loc_ref); - auto w = detail::tm_writer(loc, ctx.out(), tm); - if (spec_ == spec::year_month_day) - w.on_iso_date(); - else if (spec_ == spec::hh_mm_ss) - w.on_iso_time(); - else - detail::parse_chrono_format(specs.begin(), specs.end(), w); - return w.out(); + return do_format(tm, ctx, nullptr); } }; -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_CHRONO_H_ diff --git a/third_party/fmt-9.1.0/include/fmt/color.h b/third_party/fmt-10.0.0/include/fmt/color.h similarity index 95% rename from third_party/fmt-9.1.0/include/fmt/color.h rename to third_party/fmt-10.0.0/include/fmt/color.h index 4c163277..d175448a 100644 --- a/third_party/fmt-9.1.0/include/fmt/color.h +++ b/third_party/fmt-10.0.0/include/fmt/color.h @@ -11,7 +11,7 @@ #include "format.h" FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT enum class color : uint32_t { alice_blue = 0xF0F8FF, // rgb(240,248,255) @@ -423,26 +423,6 @@ FMT_CONSTEXPR ansi_color_escape make_emphasis(emphasis em) noexcept { return ansi_color_escape(em); } -template inline void fputs(const Char* chars, FILE* stream) { - int result = std::fputs(chars, stream); - if (result < 0) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); -} - -template <> inline void fputs(const wchar_t* chars, FILE* stream) { - int result = std::fputws(chars, stream); - if (result < 0) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); -} - -template inline void reset_color(FILE* stream) { - fputs("\x1b[0m", stream); -} - -template <> inline void reset_color(FILE* stream) { - fputs(L"\x1b[0m", stream); -} - template inline void reset_color(buffer& buffer) { auto reset_color = string_view("\x1b[0m"); buffer.append(reset_color.begin(), reset_color.end()); @@ -479,17 +459,19 @@ void vformat_to(buffer& buf, const text_style& ts, FMT_END_DETAIL_NAMESPACE -template > -void vprint(std::FILE* f, const text_style& ts, const S& format, - basic_format_args>> args) { - basic_memory_buffer buf; - detail::vformat_to(buf, ts, detail::to_string_view(format), args); +inline void vprint(std::FILE* f, const text_style& ts, string_view fmt, + format_args args) { + // Legacy wide streams are not supported. + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); if (detail::is_utf8()) { - detail::print(f, basic_string_view(buf.begin(), buf.size())); - } else { - buf.push_back(Char(0)); - detail::fputs(buf.data(), f); + detail::print(f, string_view(buf.begin(), buf.size())); + return; } + buf.push_back('\0'); + int result = std::fputs(buf.data(), f); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } /** @@ -566,7 +548,7 @@ OutputIt vformat_to( basic_format_args>> args) { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, ts, format_str, args); - return detail::get_iterator(buf); + return detail::get_iterator(buf, out); } /** @@ -645,7 +627,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts) return detail::styled_arg>{value, ts}; } -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COLOR_H_ diff --git a/third_party/fmt-9.1.0/include/fmt/compile.h b/third_party/fmt-10.0.0/include/fmt/compile.h similarity index 97% rename from third_party/fmt-9.1.0/include/fmt/compile.h rename to third_party/fmt-10.0.0/include/fmt/compile.h index 933668c4..94e13c02 100644 --- a/third_party/fmt-9.1.0/include/fmt/compile.h +++ b/third_party/fmt-10.0.0/include/fmt/compile.h @@ -331,14 +331,14 @@ template struct parse_specs_result { int next_arg_id; }; -constexpr int manual_indexing_id = -1; +enum { manual_indexing_id = -1 }; template constexpr parse_specs_result parse_specs(basic_string_view str, size_t pos, int next_arg_id) { str.remove_prefix(pos); - auto ctx = compile_parse_context(str, max_value(), nullptr, {}, - next_arg_id); + auto ctx = + compile_parse_context(str, max_value(), nullptr, next_arg_id); auto f = formatter(); auto end = f.parse(ctx); return {f, pos + fmt::detail::to_unsigned(end - str.data()), @@ -348,22 +348,18 @@ constexpr parse_specs_result parse_specs(basic_string_view str, template struct arg_id_handler { arg_ref arg_id; - constexpr int operator()() { + constexpr int on_auto() { FMT_ASSERT(false, "handler cannot be used with automatic indexing"); return 0; } - constexpr int operator()(int id) { + constexpr int on_index(int id) { arg_id = arg_ref(id); return 0; } - constexpr int operator()(basic_string_view id) { + constexpr int on_name(basic_string_view id) { arg_id = arg_ref(id); return 0; } - - constexpr void on_error(const char* message) { - FMT_THROW(format_error(message)); - } }; template struct parse_arg_id_result { @@ -501,7 +497,7 @@ constexpr auto compile(S format_str) { #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) } // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) @@ -605,7 +601,7 @@ template constexpr auto operator""_cf() { } // namespace literals #endif -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ diff --git a/third_party/fmt-9.1.0/include/fmt/core.h b/third_party/fmt-10.0.0/include/fmt/core.h similarity index 70% rename from third_party/fmt-9.1.0/include/fmt/core.h rename to third_party/fmt-10.0.0/include/fmt/core.h index f6a37af9..46723d59 100644 --- a/third_party/fmt-9.1.0/include/fmt/core.h +++ b/third_party/fmt-10.0.0/include/fmt/core.h @@ -17,7 +17,7 @@ #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 90100 +#define FMT_VERSION 100000 #if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) @@ -69,9 +69,7 @@ # define FMT_HAS_FEATURE(x) 0 #endif -#if (defined(__has_include) || FMT_ICC_VERSION >= 1600 || \ - FMT_MSC_VERSION > 1900) && \ - !defined(__INTELLISENSE__) +#if defined(__has_include) || FMT_ICC_VERSION >= 1600 || FMT_MSC_VERSION > 1900 # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 @@ -140,22 +138,7 @@ # endif #endif -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 -# define FMT_DEPRECATED [[deprecated]] -# else -# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VERSION -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif -# endif -#endif - -// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code -// warnings. +// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. #if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && \ !defined(__NVCC__) # define FMT_NORETURN [[noreturn]] @@ -163,17 +146,6 @@ # define FMT_NORETURN #endif -#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) -# define FMT_FALLTHROUGH [[fallthrough]] -#elif defined(__clang__) -# define FMT_FALLTHROUGH [[clang::fallthrough]] -#elif FMT_GCC_VERSION >= 700 && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) -# define FMT_FALLTHROUGH [[gnu::fallthrough]] -#else -# define FMT_FALLTHROUGH -#endif - #ifndef FMT_NODISCARD # if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) # define FMT_NODISCARD [[nodiscard]] @@ -182,16 +154,6 @@ # endif #endif -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 -#endif -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 -#endif -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 -#endif - #ifndef FMT_INLINE # if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_INLINE inline __attribute__((always_inline)) @@ -213,7 +175,7 @@ #ifndef FMT_BEGIN_NAMESPACE # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ - inline namespace v9 { + inline namespace v10 { # define FMT_END_NAMESPACE \ } \ } @@ -221,22 +183,18 @@ #ifndef FMT_MODULE_EXPORT # define FMT_MODULE_EXPORT -# define FMT_MODULE_EXPORT_BEGIN -# define FMT_MODULE_EXPORT_END -# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { -# define FMT_END_DETAIL_NAMESPACE } +# define FMT_BEGIN_EXPORT +# define FMT_END_EXPORT #endif #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) -# ifdef FMT_EXPORT +# ifdef FMT_LIB_EXPORT # define FMT_API __declspec(dllexport) # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # endif #else -# define FMT_CLASS_API -# if defined(FMT_EXPORT) || defined(FMT_SHARED) +# if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) # if defined(__GNUC__) || defined(__clang__) # define FMT_API __attribute__((visibility("default"))) # endif @@ -261,11 +219,13 @@ #endif #ifndef FMT_CONSTEVAL -# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ - FMT_CPLUSPLUS >= 202002L && !defined(__apple_build_version__)) || \ - (defined(__cpp_consteval) && \ +# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ + (!defined(__apple_build_version__) || \ + __apple_build_version__ >= 14000029L) && \ + FMT_CPLUSPLUS >= 202002L) || \ + (defined(__cpp_consteval) && \ (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704)) -// consteval is broken in MSVC before VS2022 and Apple clang 13. +// consteval is broken in MSVC before VS2022 and Apple clang before 14. # define FMT_CONSTEVAL consteval # define FMT_HAS_CONSTEVAL # else @@ -277,21 +237,27 @@ # if defined(__cpp_nontype_template_args) && \ ((FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L) || \ __cpp_nontype_template_args >= 201911L) && \ - !defined(__NVCOMPILER) + !defined(__NVCOMPILER) && !defined(__LCC__) # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 # else # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 # endif #endif +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline +#else +# define FMT_INLINE_VARIABLE +#endif + // Enable minimal optimizations for more compact code in debug mode. FMT_GCC_PRAGMA("GCC push_options") -#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) +#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) && !defined(__LCC__) && \ + !defined(__CUDACC__) FMT_GCC_PRAGMA("GCC optimize(\"Og\")") #endif FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN // Implementations of enable_if_t and other metafunctions for older systems. template @@ -310,18 +276,6 @@ template using type_identity_t = typename type_identity::type; template using underlying_t = typename std::underlying_type::type; -template struct disjunction : std::false_type {}; -template struct disjunction

: P {}; -template -struct disjunction - : conditional_t> {}; - -template struct conjunction : std::true_type {}; -template struct conjunction

: P {}; -template -struct conjunction - : conditional_t, P1> {}; - struct monostate { constexpr monostate() {} }; @@ -332,11 +286,16 @@ struct monostate { #ifdef FMT_DOC # define FMT_ENABLE_IF(...) #else -# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 #endif -FMT_BEGIN_DETAIL_NAMESPACE +#ifdef __cpp_lib_byte +inline auto format_as(std::byte b) -> unsigned char { + return static_cast(b); +} +#endif +namespace detail { // Suppresses "unused variable" warnings with the method described in // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. // (void)var does not work on many Intel compilers. @@ -344,7 +303,15 @@ template FMT_CONSTEXPR void ignore_unused(const T&...) {} constexpr FMT_INLINE auto is_constant_evaluated( bool default_value = false) noexcept -> bool { -#ifdef __cpp_lib_is_constant_evaluated +// Workaround for incompatibility between libstdc++ consteval-based +// std::is_constant_evaluated() implementation and clang-14. +// https://github.com/fmtlib/fmt/issues/3247 +#if FMT_CPLUSPLUS >= 202002L && defined(_GLIBCXX_RELEASE) && \ + _GLIBCXX_RELEASE >= 12 && \ + (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) + ignore_unused(default_value); + return __builtin_is_constant_evaluated(); +#elif defined(__cpp_lib_is_constant_evaluated) ignore_unused(default_value); return std::is_constant_evaluated(); #else @@ -364,12 +331,12 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, # ifdef NDEBUG // FMT_ASSERT is not empty to avoid -Wempty-body. # define FMT_ASSERT(condition, message) \ - ::fmt::detail::ignore_unused((condition), (message)) + fmt::detail::ignore_unused((condition), (message)) # else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ? (void)0 \ - : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) + : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) # endif #endif @@ -410,15 +377,15 @@ FMT_CONSTEXPR auto to_unsigned(Int value) -> return static_cast::type>(value); } -FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; +FMT_CONSTEXPR inline auto is_utf8() -> bool { + FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char section[] = "\u00A7"; -constexpr auto is_utf8() -> bool { // Avoid buggy sign extensions in MSVC's constant evaluation mode (#2297). using uchar = unsigned char; - return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && - uchar(micro[1]) == 0xB5); + return FMT_UNICODE || (sizeof(section) == 3 && uchar(section[0]) == 0xC2 && + uchar(section[1]) == 0xA7); } -FMT_END_DETAIL_NAMESPACE +} // namespace detail /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a @@ -427,6 +394,7 @@ FMT_END_DETAIL_NAMESPACE compiled with a different ``-std`` option than the client code (which is not recommended). */ +FMT_MODULE_EXPORT template class basic_string_view { private: const Char* data_; @@ -486,6 +454,18 @@ template class basic_string_view { size_ -= n; } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with( + basic_string_view sv) const noexcept { + return size_ >= sv.size_ && + std::char_traits::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept { + return size_ >= 1 && std::char_traits::eq(*data_, c); + } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const { + return starts_with(basic_string_view(s)); + } + // Lexicographically compare this string reference to other. FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { size_t str_size = size_ < other.size_ ? size_ : other.size_; @@ -517,13 +497,15 @@ template class basic_string_view { } }; +FMT_MODULE_EXPORT using string_view = basic_string_view; /** Specifies if ``T`` is a character type. Can be specialized by users. */ +FMT_MODULE_EXPORT template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // A base class for compile-time strings. struct compile_string {}; @@ -531,7 +513,6 @@ struct compile_string {}; template struct is_compile_string : std::is_base_of {}; -// Returns a string view of `s`. template ::value)> FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { return s; @@ -561,10 +542,10 @@ void to_string_view(...); // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in // enable_if and MSVC 2015 fails to compile it as an alias template. -// ADL invocation of to_string_view is DEPRECATED! +// ADL is intentionally disabled as to_string_view is not an extension point. template -struct is_string : std::is_class()))> { -}; +struct is_string + : std::is_class()))> {}; template struct char_t_impl {}; template struct char_t_impl::value>> { @@ -622,23 +603,41 @@ FMT_TYPE_CONSTANT(const void*, pointer_type); constexpr bool is_integral_type(type t) { return t > type::none_type && t <= type::last_integer_type; } - constexpr bool is_arithmetic_type(type t) { return t > type::none_type && t <= type::last_numeric_type; } +constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } +constexpr auto in(type t, int set) -> bool { + return ((set >> static_cast(t)) & 1) != 0; +} + +// Bitsets of types. +enum { + sint_set = + set(type::int_type) | set(type::long_long_type) | set(type::int128_type), + uint_set = set(type::uint_type) | set(type::ulong_long_type) | + set(type::uint128_type), + bool_set = set(type::bool_type), + char_set = set(type::char_type), + float_set = set(type::float_type) | set(type::double_type) | + set(type::long_double_type), + string_set = set(type::string_type), + cstring_set = set(type::cstring_type), + pointer_set = set(type::pointer_type) +}; + FMT_NORETURN FMT_API void throw_format_error(const char* message); struct error_handler { constexpr error_handler() = default; - constexpr error_handler(const error_handler&) = default; // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN void on_error(const char* message) { throw_format_error(message); } }; -FMT_END_DETAIL_NAMESPACE +} // namespace detail /** String's character type. */ template using char_t = typename detail::char_t_impl::type; @@ -650,8 +649,8 @@ template using char_t = typename detail::char_t_impl::type; You can use the ``format_parse_context`` type alias for ``char`` instead. \endrst */ -template -class basic_format_parse_context : private ErrorHandler { +FMT_MODULE_EXPORT +template class basic_format_parse_context { private: basic_string_view format_str_; int next_arg_id_; @@ -660,12 +659,11 @@ class basic_format_parse_context : private ErrorHandler { public: using char_type = Char; - using iterator = typename basic_string_view::iterator; + using iterator = const Char*; explicit constexpr basic_format_parse_context( - basic_string_view format_str, ErrorHandler eh = {}, - int next_arg_id = 0) - : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} + basic_string_view format_str, int next_arg_id = 0) + : format_str_(format_str), next_arg_id_(next_arg_id) {} /** Returns an iterator to the beginning of the format string range being @@ -691,7 +689,8 @@ class basic_format_parse_context : private ErrorHandler { */ FMT_CONSTEXPR auto next_arg_id() -> int { if (next_arg_id_ < 0) { - on_error("cannot switch from manual to automatic argument indexing"); + detail::throw_format_error( + "cannot switch from manual to automatic argument indexing"); return 0; } int id = next_arg_id_++; @@ -705,7 +704,8 @@ class basic_format_parse_context : private ErrorHandler { */ FMT_CONSTEXPR void check_arg_id(int id) { if (next_arg_id_ > 0) { - on_error("cannot switch from automatic to manual argument indexing"); + detail::throw_format_error( + "cannot switch from automatic to manual argument indexing"); return; } next_arg_id_ = -1; @@ -713,80 +713,79 @@ class basic_format_parse_context : private ErrorHandler { } FMT_CONSTEXPR void check_arg_id(basic_string_view) {} FMT_CONSTEXPR void check_dynamic_spec(int arg_id); - - FMT_CONSTEXPR void on_error(const char* message) { - ErrorHandler::on_error(message); - } - - constexpr auto error_handler() const -> ErrorHandler { return *this; } }; +FMT_MODULE_EXPORT using format_parse_context = basic_format_parse_context; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // A parse context with extra data used only in compile-time checks. -template -class compile_parse_context - : public basic_format_parse_context { +template +class compile_parse_context : public basic_format_parse_context { private: int num_args_; const type* types_; - using base = basic_format_parse_context; + using base = basic_format_parse_context; public: explicit FMT_CONSTEXPR compile_parse_context( basic_string_view format_str, int num_args, const type* types, - ErrorHandler eh = {}, int next_arg_id = 0) - : base(format_str, eh, next_arg_id), num_args_(num_args), types_(types) {} + int next_arg_id = 0) + : base(format_str, next_arg_id), num_args_(num_args), types_(types) {} constexpr auto num_args() const -> int { return num_args_; } constexpr auto arg_type(int id) const -> type { return types_[id]; } FMT_CONSTEXPR auto next_arg_id() -> int { int id = base::next_arg_id(); - if (id >= num_args_) this->on_error("argument not found"); + if (id >= num_args_) throw_format_error("argument not found"); return id; } FMT_CONSTEXPR void check_arg_id(int id) { base::check_arg_id(id); - if (id >= num_args_) this->on_error("argument not found"); + if (id >= num_args_) throw_format_error("argument not found"); } using base::check_arg_id; FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + detail::ignore_unused(arg_id); +#if !defined(__LCC__) if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) - this->on_error("width/precision is not integer"); + throw_format_error("width/precision is not integer"); +#endif } }; -FMT_END_DETAIL_NAMESPACE +} // namespace detail -template -FMT_CONSTEXPR void -basic_format_parse_context::do_check_arg_id(int id) { +template +FMT_CONSTEXPR void basic_format_parse_context::do_check_arg_id(int id) { // Argument id is only checked at compile-time during parsing because // formatting has its own validation. - if (detail::is_constant_evaluated() && FMT_GCC_VERSION >= 1200) { - using context = detail::compile_parse_context; + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context; if (id >= static_cast(this)->num_args()) - on_error("argument not found"); + detail::throw_format_error("argument not found"); } } -template -FMT_CONSTEXPR void -basic_format_parse_context::check_dynamic_spec(int arg_id) { - if (detail::is_constant_evaluated()) { - using context = detail::compile_parse_context; +template +FMT_CONSTEXPR void basic_format_parse_context::check_dynamic_spec( + int arg_id) { + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context; static_cast(this)->check_dynamic_spec(arg_id); } } -template class basic_format_arg; -template class basic_format_args; -template class dynamic_format_arg_store; +FMT_MODULE_EXPORT template class basic_format_arg; +FMT_MODULE_EXPORT template class basic_format_args; +FMT_MODULE_EXPORT template class dynamic_format_arg_store; // A formatter for objects of type T. +FMT_MODULE_EXPORT template struct formatter { // A deleted default constructor indicates a disabled formatter. @@ -806,7 +805,7 @@ struct is_contiguous> : std::true_type {}; class appender; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { template constexpr auto has_const_formatter_impl(T*) @@ -849,7 +848,7 @@ template U* { if (is_constant_evaluated()) return copy_str(begin, end, out); auto size = to_unsigned(end - begin); - memcpy(out, begin, size * sizeof(U)); + if (size > 0) memcpy(out, begin, size * sizeof(U)); return out + size; } @@ -892,11 +891,11 @@ template class buffer { buffer(const buffer&) = delete; void operator=(const buffer&) = delete; - auto begin() noexcept -> T* { return ptr_; } - auto end() noexcept -> T* { return ptr_ + size_; } + FMT_INLINE auto begin() noexcept -> T* { return ptr_; } + FMT_INLINE auto end() noexcept -> T* { return ptr_ + size_; } - auto begin() const noexcept -> const T* { return ptr_; } - auto end() const noexcept -> const T* { return ptr_ + size_; } + FMT_INLINE auto begin() const noexcept -> const T* { return ptr_; } + FMT_INLINE auto end() const noexcept -> const T* { return ptr_ + size_; } /** Returns the size of this buffer. */ constexpr auto size() const noexcept -> size_t { return size_; } @@ -1110,29 +1109,21 @@ template auto get_buffer(OutputIt out) -> iterator_buffer { return iterator_buffer(out); } +template , Buf>::value)> +auto get_buffer(std::back_insert_iterator out) -> buffer& { + return get_container(out); +} -template -auto get_iterator(Buffer& buf) -> decltype(buf.out()) { +template +FMT_INLINE auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { return buf.out(); } -template auto get_iterator(buffer& buf) -> buffer_appender { - return buffer_appender(buf); +template +auto get_iterator(buffer&, OutputIt out) -> OutputIt { + return out; } -template -struct fallback_formatter { - fallback_formatter() = delete; -}; - -// Specifies if T has an enabled fallback_formatter specialization. -template -using has_fallback_formatter = -#ifdef FMT_DEPRECATED_OSTREAM - std::is_constructible>; -#else - std::false_type; -#endif - struct view {}; template struct named_arg : view { @@ -1217,7 +1208,6 @@ constexpr auto count_statically_named_args() -> size_t { struct unformattable {}; struct unformattable_char : unformattable {}; -struct unformattable_const : unformattable {}; struct unformattable_pointer : unformattable {}; template struct string_value { @@ -1291,14 +1281,10 @@ template class value { // have different extension points, e.g. `formatter` for `format` and // `printf_formatter` for `printf`. custom.format = format_custom_arg< - value_type, - conditional_t::value, - typename Context::template formatter_type, - fallback_formatter>>; + value_type, typename Context::template formatter_type>; } value(unformattable); value(unformattable_char); - value(unformattable_const); value(unformattable_pointer); private: @@ -1324,20 +1310,19 @@ enum { long_short = sizeof(long) == sizeof(int) }; using long_type = conditional_t; using ulong_type = conditional_t; -#ifdef __cpp_lib_byte -inline auto format_as(std::byte b) -> unsigned char { - return static_cast(b); -} -#endif - -template struct has_format_as { - template ::value&& std::is_integral::value)> - static auto check(U*) -> std::true_type; - static auto check(...) -> std::false_type; +template struct format_as_result { + template ::value || std::is_class::value)> + static auto map(U*) -> decltype(format_as(std::declval())); + static auto map(...) -> void; - enum { value = decltype(check(static_cast(nullptr)))::value }; + using type = decltype(map(static_cast(nullptr))); }; +template using format_as_t = typename format_as_result::type; + +template +struct has_format_as + : bool_constant, void>::value> {}; // Maps formatting arguments to core types. // arg_mapper reports errors by returning unformattable instead of using @@ -1414,25 +1399,6 @@ template struct arg_mapper { FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { return {}; } - template >::value && - !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return basic_string_view(val); - } - template >::value && - !std::is_convertible>::value && - !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return std_string_view(val); - } FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { @@ -1442,8 +1408,8 @@ template struct arg_mapper { return val; } - // We use SFINAE instead of a const T* parameter to avoid conflicting with - // the C array overload. + // Use SFINAE instead of a const T* parameter to avoid a conflict with the + // array overload. template < typename T, FMT_ENABLE_IF( @@ -1462,54 +1428,34 @@ template struct arg_mapper { return values; } - template ::value&& std::is_convertible::value && - !has_format_as::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> decltype(std::declval().map( - static_cast>(val))) { - return map(static_cast>(val)); - } - - template ::value && - !has_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> decltype(std::declval().map(format_as(T()))) { + // Only map owning types because mapping views can be unsafe. + template , + FMT_ENABLE_IF(std::is_arithmetic::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(this->map(U())) { return map(format_as(val)); } template > struct formattable : bool_constant() || - !std::is_const>::value || - has_fallback_formatter::value> {}; + (has_formatter::value && + !std::is_const>::value)> {}; -#if (FMT_MSC_VERSION != 0 && FMT_MSC_VERSION < 1910) || \ - FMT_ICC_VERSION != 0 || defined(__NVCC__) - // Workaround a bug in MSVC and Intel (Issue 2746). - template FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { - return val; - } -#else template ::value)> FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { return val; } template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { + FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable { return {}; } -#endif template , - FMT_ENABLE_IF(!is_string::value && !is_char::value && - !std::is_array::value && - !std::is_pointer::value && - !has_format_as::value && - (has_formatter::value || - has_fallback_formatter::value))> + FMT_ENABLE_IF((std::is_class::value || std::is_enum::value || + std::is_union::value) && + !is_string::value && !is_char::value && + !is_named_arg::value && + !std::is_arithmetic>::value)> FMT_CONSTEXPR FMT_INLINE auto map(T&& val) -> decltype(this->do_map(std::forward(val))) { return do_map(std::forward(val)); @@ -1517,7 +1463,7 @@ template struct arg_mapper { template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) - -> decltype(std::declval().map(named_arg.value)) { + -> decltype(this->map(named_arg.value)) { return map(named_arg.value); } @@ -1535,19 +1481,13 @@ enum { packed_arg_bits = 4 }; enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; - -FMT_END_DETAIL_NAMESPACE +} // namespace detail // An output iterator that appends to a buffer. // It is used to reduce symbol sizes for the common case. class appender : public std::back_insert_iterator> { using base = std::back_insert_iterator>; - template - friend auto get_buffer(appender out) -> detail::buffer& { - return detail::get_container(out); - } - public: using std::back_insert_iterator>::back_insert_iterator; appender(base it) noexcept : base(it) {} @@ -1619,6 +1559,7 @@ template class basic_format_arg { ``vis(value)`` will be called with the value of type ``double``. \endrst */ +FMT_MODULE_EXPORT template FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { @@ -1660,7 +1601,7 @@ FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( return vis(monostate()); } -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { template auto copy_str(InputIt begin, InputIt end, appender out) -> appender { @@ -1675,9 +1616,8 @@ FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt { #if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 // A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; -template -using void_t = typename detail::void_t_impl::type; +template struct void_t_impl { using type = void; }; +template using void_t = typename void_t_impl::type; #else template using void_t = void; #endif @@ -1692,13 +1632,12 @@ struct is_output_iterator< decltype(*std::declval() = std::declval())>> : std::true_type {}; -template -struct is_back_insert_iterator : std::false_type {}; +template struct is_back_insert_iterator : std::false_type {}; template struct is_back_insert_iterator> : std::true_type {}; -template +template struct is_contiguous_back_insert_iterator : std::false_type {}; template struct is_contiguous_back_insert_iterator> @@ -1712,7 +1651,7 @@ class locale_ref { const void* locale_; // A type-erased pointer to std::locale. public: - constexpr locale_ref() : locale_(nullptr) {} + constexpr FMT_INLINE locale_ref() : locale_(nullptr) {} template explicit locale_ref(const Locale& loc); explicit operator bool() const noexcept { return locale_ != nullptr; } @@ -1732,27 +1671,22 @@ constexpr auto encode_types() -> unsigned long long { template FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value { - const auto& arg = arg_mapper().map(FMT_FORWARD(val)); + auto&& arg = arg_mapper().map(FMT_FORWARD(val)); + using arg_type = remove_cvref_t; constexpr bool formattable_char = - !std::is_same::value; + !std::is_same::value; static_assert(formattable_char, "Mixing character types is disallowed."); - constexpr bool formattable_const = - !std::is_same::value; - static_assert(formattable_const, "Cannot format a const argument."); - - // Formatting of arbitrary pointers is disallowed. If you want to output - // a pointer cast it to "void *" or "const void *". In particular, this - // forbids formatting of "[const] volatile char *" which is printed as bool - // by iostreams. + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. constexpr bool formattable_pointer = - !std::is_same::value; + !std::is_same::value; static_assert(formattable_pointer, "Formatting of non-void pointers is disallowed."); - constexpr bool formattable = - !std::is_same::value; + constexpr bool formattable = !std::is_same::value; static_assert( formattable, "Cannot format an argument. To make type T formattable provide a " @@ -1762,15 +1696,15 @@ FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value { template FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg { - basic_format_arg arg; + auto arg = basic_format_arg(); arg.type_ = mapped_type_constant::value; arg.value_ = make_value(value); return arg; } -// The type template parameter is there to avoid an ODR violation when using -// a fallback formatter in one translation unit and an implicit conversion in -// another (not recommended). +// The DEPRECATED type template parameter is there to avoid an ODR violation +// when using a fallback formatter in one translation unit and an implicit +// conversion in another (not recommended). template FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { @@ -1782,14 +1716,11 @@ template basic_format_arg { return make_arg(value); } -FMT_END_DETAIL_NAMESPACE +} // namespace detail +FMT_BEGIN_EXPORT // Formatting context. template class basic_format_context { - public: - /** The character type for the output. */ - using char_type = Char; - private: OutputIt out_; basic_format_args args_; @@ -1798,31 +1729,32 @@ template class basic_format_context { public: using iterator = OutputIt; using format_arg = basic_format_arg; + using format_args = basic_format_args; using parse_context_type = basic_format_parse_context; - template using formatter_type = formatter; + template using formatter_type = formatter; + + /** The character type for the output. */ + using char_type = Char; basic_format_context(basic_format_context&&) = default; basic_format_context(const basic_format_context&) = delete; void operator=(const basic_format_context&) = delete; /** - Constructs a ``basic_format_context`` object. References to the arguments are - stored in the object so make sure they have appropriate lifetimes. + Constructs a ``basic_format_context`` object. References to the arguments + are stored in the object so make sure they have appropriate lifetimes. */ - constexpr basic_format_context( - OutputIt out, basic_format_args ctx_args, - detail::locale_ref loc = detail::locale_ref()) + constexpr basic_format_context(OutputIt out, format_args ctx_args, + detail::locale_ref loc = {}) : out_(out), args_(ctx_args), loc_(loc) {} constexpr auto arg(int id) const -> format_arg { return args_.get(id); } - FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { + FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { return args_.get(name); } - FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { + FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { return args_.get_id(name); } - auto args() const -> const basic_format_args& { - return args_; - } + auto args() const -> const format_args& { return args_; } FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } void on_error(const char* message) { error_handler().on_error(message); } @@ -1843,16 +1775,10 @@ using buffer_context = basic_format_context, Char>; using format_context = buffer_context; -// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. -#define FMT_BUFFER_CONTEXT(Char) \ - basic_format_context, Char> - template -using is_formattable = bool_constant< - !std::is_base_of>().map( - std::declval()))>::value && - !detail::has_fallback_formatter::value>; +using is_formattable = bool_constant>() + .map(std::declval()))>::value>; /** \rst @@ -1912,9 +1838,9 @@ class format_arg_store See `~fmt::arg` for lifetime considerations. \endrst */ -template -constexpr auto make_format_args(Args&&... args) - -> format_arg_store...> { +template +constexpr auto make_format_args(T&&... args) + -> format_arg_store...> { return {FMT_FORWARD(args)...}; } @@ -1934,6 +1860,7 @@ inline auto arg(const Char* name, const T& arg) -> detail::named_arg { static_assert(!detail::is_named_arg(), "nested named arguments"); return {name, arg}; } +FMT_END_EXPORT /** \rst @@ -2059,7 +1986,7 @@ template class basic_format_args { /** An alias to ``basic_format_args``. */ // A separate type would result in shorter symbols but break ABI compatibility // between clang and gcc on ARM (#1919). -using format_args = basic_format_args; +FMT_MODULE_EXPORT using format_args = basic_format_args; // We cannot use enum classes as bit fields because of a gcc bug, so we put them // in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). @@ -2080,7 +2007,7 @@ enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; } using sign_t = sign::type; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // Workaround an array initialization issue in gcc 4.8. template struct fill_t { @@ -2092,7 +2019,7 @@ template struct fill_t { public: FMT_CONSTEXPR void operator=(basic_string_view s) { auto size = s.size(); - if (size > max_size) return throw_format_error("invalid fill"); + FMT_ASSERT(size <= max_size, "invalid fill"); for (size_t i = 0; i < size; ++i) data_[i] = s[i]; size_ = static_cast(size); } @@ -2105,11 +2032,10 @@ template struct fill_t { return data_[index]; } }; -FMT_END_DETAIL_NAMESPACE +} // namespace detail enum class presentation_type : unsigned char { none, - // Integer types should go first, dec, // 'd' oct, // 'o' hex_lower, // 'x' @@ -2131,7 +2057,7 @@ enum class presentation_type : unsigned char { }; // Format specifiers for built-in and string types. -template struct basic_format_specs { +template struct format_specs { int width; int precision; presentation_type type; @@ -2141,7 +2067,7 @@ template struct basic_format_specs { bool localized : 1; detail::fill_t fill; - constexpr basic_format_specs() + constexpr format_specs() : width(0), precision(-1), type(presentation_type::none), @@ -2151,9 +2077,7 @@ template struct basic_format_specs { localized(false) {} }; -using format_specs = basic_format_specs; - -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { enum class arg_id_kind { none, index, name }; @@ -2174,7 +2098,7 @@ template struct arg_ref { arg_id_kind kind; union value { - FMT_CONSTEXPR value(int id = 0) : index{id} {} + FMT_CONSTEXPR value(int idx = 0) : index(idx) {} FMT_CONSTEXPR value(basic_string_view n) : name(n) {} int index; @@ -2183,134 +2107,30 @@ template struct arg_ref { }; // Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow re-using the same parsed specifiers with +// than parsing time to allow reusing the same parsed specifiers with // different sets of arguments (precompilation of format strings). -template -struct dynamic_format_specs : basic_format_specs { +template +struct dynamic_format_specs : format_specs { arg_ref width_ref; arg_ref precision_ref; }; -struct auto_id {}; - -// A format specifier handler that sets fields in basic_format_specs. -template class specs_setter { - protected: - basic_format_specs& specs_; - - public: - explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) - : specs_(specs) {} - - FMT_CONSTEXPR specs_setter(const specs_setter& other) - : specs_(other.specs_) {} - - FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - specs_.fill = fill; - } - FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } - FMT_CONSTEXPR void on_hash() { specs_.alt = true; } - FMT_CONSTEXPR void on_localized() { specs_.localized = true; } - - FMT_CONSTEXPR void on_zero() { - if (specs_.align == align::none) specs_.align = align::numeric; - specs_.fill[0] = Char('0'); - } - - FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } - FMT_CONSTEXPR void on_precision(int precision) { - specs_.precision = precision; - } - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } -}; - -// Format spec handler that saves references to arguments representing dynamic -// width and precision to be resolved at formatting time. -template -class dynamic_specs_handler - : public specs_setter { - public: - using char_type = typename ParseContext::char_type; - - FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs& specs, - ParseContext& ctx) - : specs_setter(specs), specs_(specs), context_(ctx) {} - - FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) - : specs_setter(other), - specs_(other.specs_), - context_(other.context_) {} - - template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - specs_.width_ref = make_arg_ref(arg_id); - } - - template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - specs_.precision_ref = make_arg_ref(arg_id); - } - - FMT_CONSTEXPR void on_error(const char* message) { - context_.on_error(message); - } - - private: - dynamic_format_specs& specs_; - ParseContext& context_; - - using arg_ref_type = arg_ref; - - FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { - context_.check_arg_id(arg_id); - context_.check_dynamic_spec(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { - int arg_id = context_.next_arg_id(); - context_.check_dynamic_spec(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR auto make_arg_ref(basic_string_view arg_id) - -> arg_ref_type { - context_.check_arg_id(arg_id); - basic_string_view format_str( - context_.begin(), to_unsigned(context_.end() - context_.begin())); - return arg_ref_type(arg_id); - } -}; - -template constexpr bool is_ascii_letter(Char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -// Converts a character to ASCII. Returns a number > 127 on conversion failure. +// Converts a character to ASCII. Returns '\0' on conversion failure. template ::value)> -constexpr auto to_ascii(Char c) -> Char { - return c; +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast(c) : '\0'; } template ::value)> -constexpr auto to_ascii(Char c) -> underlying_t { - return c; -} - -FMT_CONSTEXPR inline auto code_point_length_impl(char c) -> int { - return "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" - [static_cast(c) >> 3]; +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast(c) : '\0'; } +// Returns the number of code units in a code point or 1 on error. template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { if (const_check(sizeof(Char) != 1)) return 1; - int len = code_point_length_impl(static_cast(*begin)); - - // Compute the pointer to the next character early so that the next - // iteration can start working on the next character. Neither Clang - // nor GCC figure out this reordering on their own. - return len + !len; + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 0x3) + 1; } // Return the result via the out param to workaround gcc bug 77539. @@ -2355,279 +2175,284 @@ FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, : error_value; } -// Parses fill and alignment. -template -FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); - auto align = align::none; - auto p = begin + code_point_length(begin); - if (end - p <= 0) p = begin; - for (;;) { - switch (to_ascii(*p)) { - case '<': - align = align::left; - break; - case '>': - align = align::right; - break; - case '^': - align = align::center; - break; - default: - break; - } - if (align != align::none) { - if (p != begin) { - auto c = *begin; - if (c == '{') - return handler.on_error("invalid fill character '{'"), begin; - handler.on_fill(basic_string_view(begin, to_unsigned(p - begin))); - begin = p + 1; - } else - ++begin; - handler.on_align(align); - break; - } else if (p == begin) { - break; - } - p = begin; +FMT_CONSTEXPR inline auto parse_align(char c) -> align_t { + switch (c) { + case '<': + return align::left; + case '>': + return align::right; + case '^': + return align::center; } - return begin; + return align::none; } -template FMT_CONSTEXPR bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +template constexpr auto is_name_start(Char c) -> bool { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; } -template +template FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); + Handler&& handler) -> const Char* { Char c = *begin; if (c >= '0' && c <= '9') { int index = 0; + constexpr int max = (std::numeric_limits::max)(); if (c != '0') - index = - parse_nonnegative_int(begin, end, (std::numeric_limits::max)()); + index = parse_nonnegative_int(begin, end, max); else ++begin; if (begin == end || (*begin != '}' && *begin != ':')) - handler.on_error("invalid format string"); + throw_format_error("invalid format string"); else - handler(index); + handler.on_index(index); return begin; } if (!is_name_start(c)) { - handler.on_error("invalid format string"); + throw_format_error("invalid format string"); return begin; } auto it = begin; do { ++it; - } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); - handler(basic_string_view(begin, to_unsigned(it - begin))); + } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); + handler.on_name({begin, to_unsigned(it - begin)}); return it; } -template +template FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) -> const Char* { + Handler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); Char c = *begin; if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); - handler(); + handler.on_auto(); return begin; } -template -FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - using detail::auto_id; - struct width_adapter { - Handler& handler; +template struct dynamic_spec_id_handler { + basic_format_parse_context& ctx; + arg_ref& ref; - FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } - FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - handler.on_dynamic_width(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; + FMT_CONSTEXPR void on_auto() { + int id = ctx.next_arg_id(); + ref = arg_ref(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_index(int id) { + ref = arg_ref(id); + ctx.check_arg_id(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_name(basic_string_view id) { + ref = arg_ref(id); + ctx.check_arg_id(id); + } +}; +// Parses [integer | "{" [arg_id] "}"]. +template +FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, + int& value, arg_ref& ref, + basic_format_parse_context& ctx) + -> const Char* { FMT_ASSERT(begin != end, ""); if ('0' <= *begin && *begin <= '9') { - int width = parse_nonnegative_int(begin, end, -1); - if (width != -1) - handler.on_width(width); + int val = parse_nonnegative_int(begin, end, -1); + if (val != -1) + value = val; else - handler.on_error("number is too big"); + throw_format_error("number is too big"); } else if (*begin == '{') { ++begin; - if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); - if (begin == end || *begin != '}') - return handler.on_error("invalid format string"), begin; - ++begin; - } - return begin; -} - -template -FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - using detail::auto_id; - struct precision_adapter { - Handler& handler; - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } - FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - handler.on_dynamic_precision(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; - - ++begin; - auto c = begin != end ? *begin : Char(); - if ('0' <= c && c <= '9') { - auto precision = parse_nonnegative_int(begin, end, -1); - if (precision != -1) - handler.on_precision(precision); - else - handler.on_error("number is too big"); - } else if (c == '{') { - ++begin; - if (begin != end) - begin = parse_arg_id(begin, end, precision_adapter{handler}); - if (begin == end || *begin++ != '}') - return handler.on_error("invalid format string"), begin; - } else { - return handler.on_error("missing precision specifier"), begin; + auto handler = dynamic_spec_id_handler{ctx, ref}; + if (begin != end) begin = parse_arg_id(begin, end, handler); + if (begin != end && *begin == '}') return ++begin; + throw_format_error("invalid format string"); } - handler.end_precision(); return begin; } template -FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { - switch (to_ascii(type)) { - case 'd': - return presentation_type::dec; - case 'o': - return presentation_type::oct; - case 'x': - return presentation_type::hex_lower; - case 'X': - return presentation_type::hex_upper; - case 'b': - return presentation_type::bin_lower; - case 'B': - return presentation_type::bin_upper; - case 'a': - return presentation_type::hexfloat_lower; - case 'A': - return presentation_type::hexfloat_upper; - case 'e': - return presentation_type::exp_lower; - case 'E': - return presentation_type::exp_upper; - case 'f': - return presentation_type::fixed_lower; - case 'F': - return presentation_type::fixed_upper; - case 'g': - return presentation_type::general_lower; - case 'G': - return presentation_type::general_upper; - case 'c': - return presentation_type::chr; - case 's': - return presentation_type::string; - case 'p': - return presentation_type::pointer; - case '?': - return presentation_type::debug; - default: - return presentation_type::none; - } -} - -// Parses standard format specifiers and sends notifications about parsed -// components to handler. -template -FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, - const Char* end, - SpecHandler&& handler) +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + int& value, arg_ref& ref, + basic_format_parse_context& ctx) -> const Char* { - if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && - *begin != 'L') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); + ++begin; + if (begin == end || *begin == '}') { + throw_format_error("invalid precision"); return begin; } + return parse_dynamic_spec(begin, end, value, ref, ctx); +} - if (begin == end) return begin; - - begin = parse_align(begin, end, handler); - if (begin == end) return begin; - - // Parse sign. - switch (to_ascii(*begin)) { - case '+': - handler.on_sign(sign::plus); - ++begin; - break; - case '-': - handler.on_sign(sign::minus); - ++begin; - break; - case ' ': - handler.on_sign(sign::space); - ++begin; - break; - default: - break; - } - if (begin == end) return begin; - - if (*begin == '#') { - handler.on_hash(); - if (++begin == end) return begin; - } - - // Parse zero flag. - if (*begin == '0') { - handler.on_zero(); - if (++begin == end) return begin; - } - - begin = parse_width(begin, end, handler); - if (begin == end) return begin; +enum class state { start, align, sign, hash, zero, width, precision, locale }; - // Parse precision. - if (*begin == '.') { - begin = parse_precision(begin, end, handler); +// Parses standard format specifiers. +template +FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( + const Char* begin, const Char* end, dynamic_format_specs& specs, + basic_format_parse_context& ctx, type arg_type) -> const Char* { + auto c = '\0'; + if (end - begin > 1) { + auto next = to_ascii(begin[1]); + c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; + } else { if (begin == end) return begin; + c = to_ascii(*begin); } - if (*begin == 'L') { - handler.on_localized(); - ++begin; - } + struct { + state current_state = state::start; + FMT_CONSTEXPR void operator()(state s, bool valid = true) { + if (current_state >= s || !valid) + throw_format_error("invalid format specifier"); + current_state = s; + } + } enter_state; + + using pres = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + struct { + const Char*& begin; + dynamic_format_specs& specs; + type arg_type; + + FMT_CONSTEXPR auto operator()(pres type, int set) -> const Char* { + if (!in(arg_type, set)) throw_format_error("invalid format specifier"); + specs.type = type; + return begin + 1; + } + } parse_presentation_type{begin, specs, arg_type}; - // Parse type. - if (begin != end && *begin != '}') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); + for (;;) { + switch (c) { + case '<': + case '>': + case '^': + enter_state(state::align); + specs.align = parse_align(c); + ++begin; + break; + case '+': + case '-': + case ' ': + enter_state(state::sign, in(arg_type, sint_set | float_set)); + switch (c) { + case '+': + specs.sign = sign::plus; + break; + case '-': + specs.sign = sign::minus; + break; + case ' ': + specs.sign = sign::space; + break; + } + ++begin; + break; + case '#': + enter_state(state::hash, is_arithmetic_type(arg_type)); + specs.alt = true; + ++begin; + break; + case '0': + enter_state(state::zero); + if (!is_arithmetic_type(arg_type)) + throw_format_error("format specifier requires numeric argument"); + if (specs.align == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.align = align::numeric; + specs.fill[0] = Char('0'); + } + ++begin; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '{': + enter_state(state::width); + begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); + break; + case '.': + enter_state(state::precision, + in(arg_type, float_set | string_set | cstring_set)); + begin = parse_precision(begin, end, specs.precision, specs.precision_ref, + ctx); + break; + case 'L': + enter_state(state::locale, is_arithmetic_type(arg_type)); + specs.localized = true; + ++begin; + break; + case 'd': + return parse_presentation_type(pres::dec, integral_set); + case 'o': + return parse_presentation_type(pres::oct, integral_set); + case 'x': + return parse_presentation_type(pres::hex_lower, integral_set); + case 'X': + return parse_presentation_type(pres::hex_upper, integral_set); + case 'b': + return parse_presentation_type(pres::bin_lower, integral_set); + case 'B': + return parse_presentation_type(pres::bin_upper, integral_set); + case 'a': + return parse_presentation_type(pres::hexfloat_lower, float_set); + case 'A': + return parse_presentation_type(pres::hexfloat_upper, float_set); + case 'e': + return parse_presentation_type(pres::exp_lower, float_set); + case 'E': + return parse_presentation_type(pres::exp_upper, float_set); + case 'f': + return parse_presentation_type(pres::fixed_lower, float_set); + case 'F': + return parse_presentation_type(pres::fixed_upper, float_set); + case 'g': + return parse_presentation_type(pres::general_lower, float_set); + case 'G': + return parse_presentation_type(pres::general_upper, float_set); + case 'c': + return parse_presentation_type(pres::chr, integral_set); + case 's': + return parse_presentation_type(pres::string, + bool_set | string_set | cstring_set); + case 'p': + return parse_presentation_type(pres::pointer, pointer_set | cstring_set); + case '?': + return parse_presentation_type(pres::debug, + char_set | string_set | cstring_set); + case '}': + return begin; + default: { + if (*begin == '}') return begin; + // Parse fill and alignment. + auto fill_end = begin + code_point_length(begin); + if (end - fill_end <= 0) { + throw_format_error("invalid format specifier"); + return begin; + } + if (*begin == '{') { + throw_format_error("invalid fill character '{'"); + return begin; + } + auto align = parse_align(to_ascii(*fill_end)); + enter_state(state::align, align != align::none); + specs.fill = {begin, to_unsigned(fill_end - begin)}; + specs.align = align; + begin = fill_end + 1; + } + } + if (begin == end) return begin; + c = to_ascii(*begin); } - return begin; } template @@ -2637,14 +2462,11 @@ FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, Handler& handler; int arg_id; - FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } - FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { + FMT_CONSTEXPR void on_auto() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_name(basic_string_view id) { arg_id = handler.on_arg_id(id); } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } }; ++begin; @@ -2673,9 +2495,6 @@ FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, template FMT_CONSTEXPR FMT_INLINE void parse_format_string( basic_string_view format_str, Handler&& handler) { - // Workaround a name-lookup bug in MSVC's modules implementation. - using detail::find; - auto begin = format_str.data(); auto end = begin + format_str.size(); if (end - begin < 32) { @@ -2735,183 +2554,32 @@ FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) -> decltype(ctx.begin()) { using char_type = typename ParseContext::char_type; using context = buffer_context; - using stripped_type = typename strip_named_arg::type; using mapped_type = conditional_t< mapped_type_constant::value != type::custom_type, decltype(arg_mapper().map(std::declval())), - stripped_type>; - auto f = conditional_t::value, - formatter, - fallback_formatter>(); - return f.parse(ctx); -} - -template -FMT_CONSTEXPR void check_int_type_spec(presentation_type type, - ErrorHandler&& eh) { - if (type > presentation_type::bin_upper && type != presentation_type::chr) - eh.on_error("invalid type specifier"); + typename strip_named_arg::type>; + return formatter().parse(ctx); } -// Checks char specs and returns true if the type spec is char (and not int). -template -FMT_CONSTEXPR auto check_char_specs(const basic_format_specs& specs, - ErrorHandler&& eh = {}) -> bool { +// Checks char specs and returns true iff the presentation type is char-like. +template +FMT_CONSTEXPR auto check_char_specs(const format_specs& specs) -> bool { if (specs.type != presentation_type::none && specs.type != presentation_type::chr && specs.type != presentation_type::debug) { - check_int_type_spec(specs.type, eh); return false; } if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) - eh.on_error("invalid format specifier for char"); + throw_format_error("invalid format specifier for char"); return true; } -// A floating-point presentation format. -enum class float_format : unsigned char { - general, // General: exponent notation or fixed point based on magnitude. - exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. - fixed, // Fixed point with the default precision of 6, e.g. 0.0012. - hex -}; - -struct float_specs { - int precision; - float_format format : 8; - sign_t sign : 8; - bool upper : 1; - bool locale : 1; - bool binary32 : 1; - bool showpoint : 1; -}; - -template -FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, - ErrorHandler&& eh = {}) - -> float_specs { - auto result = float_specs(); - result.showpoint = specs.alt; - result.locale = specs.localized; - switch (specs.type) { - case presentation_type::none: - result.format = float_format::general; - break; - case presentation_type::general_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::general_lower: - result.format = float_format::general; - break; - case presentation_type::exp_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::exp_lower: - result.format = float_format::exp; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::fixed_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::fixed_lower: - result.format = float_format::fixed; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::hexfloat_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::hexfloat_lower: - result.format = float_format::hex; - break; - default: - eh.on_error("invalid type specifier"); - break; - } - return result; -} - -template -FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type, - ErrorHandler&& eh = {}) -> bool { - if (type == presentation_type::none || type == presentation_type::string || - type == presentation_type::debug) - return true; - if (type != presentation_type::pointer) eh.on_error("invalid type specifier"); - return false; -} - -template -FMT_CONSTEXPR void check_string_type_spec(presentation_type type, - ErrorHandler&& eh = {}) { - if (type != presentation_type::none && type != presentation_type::string && - type != presentation_type::debug) - eh.on_error("invalid type specifier"); -} - -template -FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, - ErrorHandler&& eh) { - if (type != presentation_type::none && type != presentation_type::pointer) - eh.on_error("invalid type specifier"); -} - -// A parse_format_specs handler that checks if specifiers are consistent with -// the argument type. -template class specs_checker : public Handler { - private: - detail::type arg_type_; - - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic_type(arg_type_)) - this->on_error("format specifier requires numeric argument"); - } - - public: - FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) - : Handler(handler), arg_type_(arg_type) {} - - FMT_CONSTEXPR void on_align(align_t align) { - if (align == align::numeric) require_numeric_argument(); - Handler::on_align(align); - } - - FMT_CONSTEXPR void on_sign(sign_t s) { - require_numeric_argument(); - if (is_integral_type(arg_type_) && arg_type_ != type::int_type && - arg_type_ != type::long_long_type && arg_type_ != type::int128_type && - arg_type_ != type::char_type) { - this->on_error("format specifier requires signed argument"); - } - Handler::on_sign(s); - } - - FMT_CONSTEXPR void on_hash() { - require_numeric_argument(); - Handler::on_hash(); - } - - FMT_CONSTEXPR void on_localized() { - require_numeric_argument(); - Handler::on_localized(); - } - - FMT_CONSTEXPR void on_zero() { - require_numeric_argument(); - Handler::on_zero(); - } - - FMT_CONSTEXPR void end_precision() { - if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) - this->on_error("precision not allowed for this argument type"); - } -}; - -constexpr int invalid_arg_index = -1; +constexpr FMT_INLINE_VARIABLE int invalid_arg_index = -1; #if FMT_USE_NONTYPE_TEMPLATE_ARGS template constexpr auto get_arg_index_by_name(basic_string_view name) -> int { - if constexpr (detail::is_statically_named_arg()) { + if constexpr (is_statically_named_arg()) { if (name == T::name) return N; } if constexpr (sizeof...(Args) > 0) @@ -2931,16 +2599,15 @@ FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { return invalid_arg_index; } -template -class format_string_checker { +template class format_string_checker { private: - // In the future basic_format_parse_context will replace compile_parse_context - // here and will use is_constant_evaluated and downcasting to access the data - // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. - using parse_context_type = compile_parse_context; + using parse_context_type = compile_parse_context; static constexpr int num_args = sizeof...(Args); // Format specifier parsing function. + // In the future basic_format_parse_context will replace compile_parse_context + // here and will use is_constant_evaluated and downcasting to access the data + // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. using parse_func = const Char* (*)(parse_context_type&); parse_context_type context_; @@ -2948,14 +2615,10 @@ class format_string_checker { type types_[num_args > 0 ? static_cast(num_args) : 1]; public: - explicit FMT_CONSTEXPR format_string_checker( - basic_string_view format_str, ErrorHandler eh) - : context_(format_str, num_args, types_, eh), + explicit FMT_CONSTEXPR format_string_checker(basic_string_view fmt) + : context_(fmt, num_args, types_), parse_funcs_{&parse_format_specs...}, - types_{ - mapped_type_constant>::value...} { - } + types_{mapped_type_constant>::value...} {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {} @@ -2967,7 +2630,7 @@ class format_string_checker { #if FMT_USE_NONTYPE_TEMPLATE_ARGS auto index = get_arg_index_by_name(id); if (index == invalid_arg_index) on_error("named argument is not found"); - return context_.check_arg_id(index), index; + return index; #else (void)id; on_error("compile-time checks for named arguments require C++20 support"); @@ -2979,13 +2642,13 @@ class format_string_checker { FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) -> const Char* { - context_.advance_to(context_.begin() + (begin - &*context_.begin())); + context_.advance_to(begin); // id >= 0 check is a workaround for gcc 10 bug (#2065). return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; } FMT_CONSTEXPR void on_error(const char* message) { - context_.on_error(message); + throw_format_error(message); } }; @@ -3001,28 +2664,33 @@ FMT_INLINE void check_format_string(const S&) { template ::value)> void check_format_string(S format_str) { - FMT_CONSTEXPR auto s = basic_string_view(format_str); - using checker = format_string_checker...>; - FMT_CONSTEXPR bool invalid_format = - (parse_format_string(s, checker(s, {})), true); - ignore_unused(invalid_format); + using char_t = typename S::char_type; + FMT_CONSTEXPR auto s = basic_string_view(format_str); + using checker = format_string_checker...>; + FMT_CONSTEXPR bool error = (parse_format_string(s, checker(s)), true); + ignore_unused(error); } +template struct vformat_args { + using type = basic_format_args< + basic_format_context>, Char>>; +}; +template <> struct vformat_args { using type = format_args; }; + +// Use vformat_args and avoid type_identity to keep symbols short. template -void vformat_to( - buffer& buf, basic_string_view fmt, - basic_format_args)> args, - locale_ref loc = {}); +void vformat_to(buffer& buf, basic_string_view fmt, + typename vformat_args::type args, locale_ref loc = {}); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif -FMT_END_DETAIL_NAMESPACE +} // namespace detail + +FMT_BEGIN_EXPORT -// A formatter specialization for the core types corresponding to detail::type -// constants. +// A formatter specialization for natively supported types. template struct formatter::value != @@ -3031,81 +2699,21 @@ struct formatter specs_; public: - // Parses format specifiers stopping either at the end of the range or at the - // terminating '}'. template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end) return begin; - using handler_type = detail::dynamic_specs_handler; + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* { auto type = detail::type_constant::value; - auto checker = - detail::specs_checker(handler_type(specs_, ctx), type); - auto it = detail::parse_format_specs(begin, end, checker); - auto eh = ctx.error_handler(); - switch (type) { - case detail::type::none_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case detail::type::bool_type: - if (specs_.type == presentation_type::none || - specs_.type == presentation_type::string) { - break; - } - FMT_FALLTHROUGH; - case detail::type::int_type: - case detail::type::uint_type: - case detail::type::long_long_type: - case detail::type::ulong_long_type: - case detail::type::int128_type: - case detail::type::uint128_type: - detail::check_int_type_spec(specs_.type, eh); - break; - case detail::type::char_type: - detail::check_char_specs(specs_, eh); - break; - case detail::type::float_type: - if (detail::const_check(FMT_USE_FLOAT)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "float support disabled"); - break; - case detail::type::double_type: - if (detail::const_check(FMT_USE_DOUBLE)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "double support disabled"); - break; - case detail::type::long_double_type: - if (detail::const_check(FMT_USE_LONG_DOUBLE)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "long double support disabled"); - break; - case detail::type::cstring_type: - detail::check_cstring_type_spec(specs_.type, eh); - break; - case detail::type::string_type: - detail::check_string_type_spec(specs_.type, eh); - break; - case detail::type::pointer_type: - detail::check_pointer_type_spec(specs_.type, eh); - break; - case detail::type::custom_type: - // Custom format specifiers are checked in parse functions of - // formatter specializations. - break; - } - return it; + auto end = + detail::parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, type); + if (type == detail::type::char_type) detail::check_char_specs(specs_); + return end; } template ::value, - enable_if_t<(U == detail::type::string_type || - U == detail::type::cstring_type || - U == detail::type::char_type), - int> = 0> - FMT_CONSTEXPR void set_debug_format() { - specs_.type = presentation_type::debug; + FMT_ENABLE_IF(U == detail::type::string_type || + U == detail::type::cstring_type || + U == detail::type::char_type)> + FMT_CONSTEXPR void set_debug_format(bool set = true) { + specs_.type = set ? presentation_type::debug : presentation_type::none; } template @@ -3117,7 +2725,7 @@ struct formatter \ struct formatter : formatter { \ template \ - auto format(Type const& val, FormatContext& ctx) const \ + auto format(const Type& val, FormatContext& ctx) const \ -> decltype(ctx.out()) { \ return formatter::format(static_cast(val), ctx); \ } \ @@ -3134,7 +2742,9 @@ FMT_FORMAT_AS(std::basic_string, basic_string_view); FMT_FORMAT_AS(std::nullptr_t, const void*); FMT_FORMAT_AS(detail::std_string_view, basic_string_view); -template struct basic_runtime { basic_string_view str; }; +template struct runtime_format_string { + basic_string_view str; +}; /** A compile-time format string. */ template class basic_format_string { @@ -3154,17 +2764,18 @@ template class basic_format_string { #ifdef FMT_HAS_CONSTEVAL if constexpr (detail::count_named_args() == detail::count_statically_named_args()) { - using checker = detail::format_string_checker...>; - detail::parse_format_string(str_, checker(s, {})); + using checker = + detail::format_string_checker...>; + detail::parse_format_string(str_, checker(s)); } #else detail::check_format_string(s); #endif } - basic_format_string(basic_runtime r) : str_(r.str) {} + basic_format_string(runtime_format_string fmt) : str_(fmt.str) {} FMT_INLINE operator basic_string_view() const { return str_; } + FMT_INLINE auto get() const -> basic_string_view { return str_; } }; #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 @@ -3184,7 +2795,7 @@ using format_string = basic_format_string...>; fmt::print(fmt::runtime("{:d}"), "I am not a number"); \endrst */ -inline auto runtime(string_view s) -> basic_runtime { return {{s}}; } +inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } #endif FMT_API auto vformat(string_view fmt, format_args args) -> std::string; @@ -3210,10 +2821,9 @@ FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) template ::value)> auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { - using detail::get_buffer; - auto&& buf = get_buffer(out); + auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, fmt, args, {}); - return detail::get_iterator(buf); + return detail::get_iterator(buf, out); } /** @@ -3272,7 +2882,7 @@ template FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {}); + detail::vformat_to(buf, fmt, fmt::make_format_args(args...), {}); return buf.count(); } @@ -3313,7 +2923,25 @@ FMT_INLINE void print(std::FILE* f, format_string fmt, T&&... args) { : detail::vprint_mojibake(f, fmt, vargs); } -FMT_MODULE_EXPORT_END +/** + Formats ``args`` according to specifications in ``fmt`` and writes the + output to the file ``f`` followed by a newline. + */ +template +FMT_INLINE void println(std::FILE* f, format_string fmt, T&&... args) { + return fmt::print(f, "{}\n", fmt::format(fmt, std::forward(args)...)); +} + +/** + Formats ``args`` according to specifications in ``fmt`` and writes the output + to ``stdout`` followed by a newline. + */ +template +FMT_INLINE void println(format_string fmt, T&&... args) { + return fmt::println(stdout, fmt, std::forward(args)...); +} + +FMT_END_EXPORT FMT_GCC_PRAGMA("GCC pop_options") FMT_END_NAMESPACE diff --git a/third_party/fmt-9.1.0/include/fmt/format-inl.h b/third_party/fmt-10.0.0/include/fmt/format-inl.h similarity index 94% rename from third_party/fmt-9.1.0/include/fmt/format-inl.h rename to third_party/fmt-10.0.0/include/fmt/format-inl.h index 22b1ec8d..5bae3c7b 100644 --- a/third_party/fmt-9.1.0/include/fmt/format-inl.h +++ b/third_party/fmt-10.0.0/include/fmt/format-inl.h @@ -9,13 +9,9 @@ #define FMT_FORMAT_INL_H_ #include -#include #include // errno #include #include -#include -#include // std::memmove -#include #include #ifndef FMT_STATIC_THOUSANDS_SEPARATOR @@ -115,16 +111,43 @@ template FMT_FUNC Char decimal_point_impl(locale_ref) { return '.'; } #endif + +FMT_FUNC auto write_loc(appender out, loc_value value, + const format_specs<>& specs, locale_ref loc) -> bool { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + auto locale = loc.get(); + // We cannot use the num_put facet because it may produce output in + // a wrong encoding. + using facet = format_facet; + if (std::has_facet(locale)) + return std::use_facet(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); +#endif + return false; +} } // namespace detail -#if !FMT_MSC_VERSION -FMT_API FMT_FUNC format_error::~format_error() noexcept = default; +template typename Locale::id format_facet::id; + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template format_facet::format_facet(Locale& loc) { + auto& numpunct = std::use_facet>(loc); + grouping_ = numpunct.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); +} + +template <> +FMT_API FMT_FUNC auto format_facet::do_put( + appender out, loc_value val, const format_specs<>& specs) const -> bool { + return val.visit( + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); +} #endif -FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, +FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt, format_args args) { auto ec = std::error_code(error_code, std::generic_category()); - return std::system_error(ec, vformat(format_str, args)); + return std::system_error(ec, vformat(fmt, args)); } namespace detail { @@ -143,58 +166,8 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { return (n >> r) | (n << (64 - r)); } -// Computes 128-bit result of multiplication of two 64-bit unsigned integers. -inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { -#if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); - return {static_cast(p >> 64), static_cast(p)}; -#elif defined(_MSC_VER) && defined(_M_X64) - auto result = uint128_fallback(); - result.lo_ = _umul128(x, y, &result.hi_); - return result; -#else - const uint64_t mask = static_cast(max_value()); - - uint64_t a = x >> 32; - uint64_t b = x & mask; - uint64_t c = y >> 32; - uint64_t d = y & mask; - - uint64_t ac = a * c; - uint64_t bc = b * c; - uint64_t ad = a * d; - uint64_t bd = b * d; - - uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); - - return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), - (intermediate << 32) + (bd & mask)}; -#endif -} - // Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. namespace dragonbox { -// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. -inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { -#if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); - return static_cast(p >> 64); -#elif defined(_MSC_VER) && defined(_M_X64) - return __umulh(x, y); -#else - return umul128(x, y).high(); -#endif -} - -// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a -// 128-bit unsigned integer. -inline uint128_fallback umul192_upper128(uint64_t x, - uint128_fallback y) noexcept { - uint128_fallback r = umul128(x, y.high()); - r += umul128_upper64(x, y.low()); - return r; -} - // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept { @@ -216,25 +189,13 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept { return x * y; } -// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from -// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. -inline int floor_log10_pow2(int e) noexcept { - FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); - static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); - return (e * 315653) >> 20; -} - // Various fast log computations. -inline int floor_log2_pow10(int e) noexcept { - FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); - return (e * 1741647) >> 19; -} inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept { FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); return (e * 631305 - 261663) >> 21; } -static constexpr struct { +FMT_INLINE_VARIABLE constexpr struct { uint32_t divisor; int shift_amount; } div_small_pow10_infos[] = {{10, 16}, {100, 16}}; @@ -288,7 +249,7 @@ inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept { } // Various subroutines using pow10 cache -template struct cache_accessor; +template struct cache_accessor; template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; @@ -1009,8 +970,23 @@ template <> struct cache_accessor { {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, {0xc5a05277621be293, 0xc7098b7305241886}, - { 0xf70867153aa2db38, - 0xb8cbee4fc66d1ea8 } + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, + {0x9a65406d44a5c903, 0x737f74f1dc043329}, + {0xc0fe908895cf3b44, 0x505f522e53053ff3}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, + {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, + {0xbc789925624c5fe0, 0xb67d16413d132073}, + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, + {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, + {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, + {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, + { 0xdb68c2ca82ed2a05, + 0xa67398db9f6820e2 } #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, @@ -1034,8 +1010,8 @@ template <> struct cache_accessor { {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, - { 0x95527a5202df0ccb, - 0x0f37801e0c43ebc9 } + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0} #endif }; @@ -1138,8 +1114,12 @@ template <> struct cache_accessor { } }; +FMT_FUNC uint128_fallback get_cached_power(int k) noexcept { + return cache_accessor::get_cached_power(k); +} + // Various integer checks -template +template bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_upper_threshold = 3; @@ -1150,8 +1130,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { // Remove trailing zeros from n and return the number of zeros removed (float) FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { FMT_ASSERT(n != 0, ""); + // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. + // See https://github.com/fmtlib/fmt/issues/3163 for more details. const uint32_t mod_inv_5 = 0xcccccccd; - const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5; + // Casts are needed to workaround a bug in MSVC 19.22 and older. + const uint32_t mod_inv_25 = + static_cast(uint64_t(mod_inv_5) * mod_inv_5); int s = 0; while (true) { @@ -1165,7 +1149,6 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { n = q; s |= 1; } - return s; } @@ -1223,7 +1206,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { } // The main algorithm for shorter interval case -template +template FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { decimal_fp ret_value; // Compute k and beta @@ -1394,17 +1377,6 @@ template decimal_fp to_decimal(T x) noexcept { return ret_value; } } // namespace dragonbox - -#ifdef _MSC_VER -FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) - -> int { - auto args = va_list(); - va_start(args, fmt); - int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args); - va_end(args); - return result; -} -#endif } // namespace detail template <> struct formatter { @@ -1413,9 +1385,8 @@ template <> struct formatter { return ctx.begin(); } - template - auto format(const detail::bigint& n, FormatContext& ctx) const -> - typename FormatContext::iterator { + auto format(const detail::bigint& n, format_context& ctx) const + -> format_context::iterator { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { @@ -1474,57 +1445,44 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) { } namespace detail { -#ifdef _WIN32 +#ifndef _WIN32 +FMT_FUNC bool write_console(std::FILE*, string_view) { return false; } +#else using dword = conditional_t; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); FMT_FUNC bool write_console(std::FILE* f, string_view text) { auto fd = _fileno(f); - if (_isatty(fd)) { - detail::utf8_to_utf16 u16(string_view(text.data(), text.size())); - auto written = detail::dword(); - if (detail::WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), - u16.c_str(), static_cast(u16.size()), - &written, nullptr)) { - return true; - } - } - // We return false if the file descriptor was not TTY, or it was but - // SetConsoleW failed which can happen if the output has been redirected to - // NUL. In both cases when we return false, we should attempt to do regular - // write via fwrite or std::ostream::write. - return false; + if (!_isatty(fd)) return false; + auto u16 = utf8_to_utf16(text); + auto written = dword(); + return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), + static_cast(u16.size()), &written, nullptr); +} + +// Print assuming legacy (non-Unicode) encoding. +FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, + basic_format_args>(args)); + fwrite_fully(buffer.data(), 1, buffer.size(), f); } #endif FMT_FUNC void print(std::FILE* f, string_view text) { -#ifdef _WIN32 - if (write_console(f, text)) return; -#endif - detail::fwrite_fully(text.data(), 1, text.size(), f); + if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f); } } // namespace detail -FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, args); +FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); detail::print(f, {buffer.data(), buffer.size()}); } -#ifdef _WIN32 -// Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, - format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, - basic_format_args>(args)); - fwrite_fully(buffer.data(), 1, buffer.size(), f); -} -#endif - -FMT_FUNC void vprint(string_view format_str, format_args args) { - vprint(stdout, format_str, args); +FMT_FUNC void vprint(string_view fmt, format_args args) { + vprint(stdout, fmt, args); } namespace detail { diff --git a/third_party/fmt-9.1.0/include/fmt/format.h b/third_party/fmt-10.0.0/include/fmt/format.h similarity index 79% rename from third_party/fmt-9.1.0/include/fmt/format.h rename to third_party/fmt-10.0.0/include/fmt/format.h index 7c607dbd..ed8b29eb 100644 --- a/third_party/fmt-9.1.0/include/fmt/format.h +++ b/third_party/fmt-10.0.0/include/fmt/format.h @@ -33,13 +33,14 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include // std::signbit -#include // uint32_t -#include // std::memcpy -#include // std::numeric_limits -#include // std::uninitialized_copy -#include // std::runtime_error -#include // std::system_error +#include // std::signbit +#include // uint32_t +#include // std::memcpy +#include // std::initializer_list +#include // std::numeric_limits +#include // std::uninitialized_copy +#include // std::runtime_error +#include // std::system_error #ifdef __cpp_lib_bit_cast # include // std::bitcast @@ -47,6 +48,36 @@ #include "core.h" +#ifndef FMT_BEGIN_DETAIL_NAMESPACE +# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { +# define FMT_END_DETAIL_NAMESPACE } +#endif + +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +#ifndef FMT_DEPRECATED +# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 +# define FMT_DEPRECATED [[deprecated]] +# else +# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) +# define FMT_DEPRECATED __attribute__((deprecated)) +# elif FMT_MSC_VERSION +# define FMT_DEPRECATED __declspec(deprecated) +# else +# define FMT_DEPRECATED /* deprecated */ +# endif +# endif +#endif + #if FMT_GCC_VERSION # define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) #else @@ -71,12 +102,6 @@ # define FMT_NOINLINE #endif -#if FMT_MSC_VERSION -# define FMT_MSC_DEFAULT = default -#else -# define FMT_MSC_DEFAULT -#endif - #ifndef FMT_THROW # if FMT_EXCEPTIONS # if FMT_MSC_VERSION || defined(__NVCC__) @@ -200,7 +225,8 @@ inline auto clzll(uint64_t x) -> int { _BitScanReverse64(&r, x); # else // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ (r + 32); + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 ^ static_cast(r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif @@ -240,6 +266,19 @@ FMT_END_NAMESPACE #endif FMT_BEGIN_NAMESPACE + +template struct disjunction : std::false_type {}; +template struct disjunction

: P {}; +template +struct disjunction + : conditional_t> {}; + +template struct conjunction : std::true_type {}; +template struct conjunction

: P {}; +template +struct conjunction + : conditional_t, P1> {}; + namespace detail { FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { @@ -359,6 +398,10 @@ class uint128_fallback { -> uint128_fallback { return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; } + friend constexpr auto operator~(const uint128_fallback& n) + -> uint128_fallback { + return {~n.hi_, ~n.lo_}; + } friend auto operator+(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { auto result = uint128_fallback(lhs); @@ -397,6 +440,10 @@ class uint128_fallback { lo_ = new_lo; hi_ = new_hi; } + FMT_CONSTEXPR void operator&=(uint128_fallback n) { + lo_ &= n.lo_; + hi_ &= n.hi_; + } FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept { if (is_constant_evaluated()) { @@ -463,6 +510,28 @@ inline auto bit_cast(const From& from) -> To { return result; } +template +FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { + int lz = 0; + constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); + for (; (n & msb_mask) == 0; n <<= 1) lz++; + return lz; +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); +#endif + return countl_zero_fallback(n); +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); +#endif + return countl_zero_fallback(n); +} + FMT_INLINE void assume(bool condition) { (void)condition; #if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION @@ -607,7 +676,8 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) constexpr const int shiftc[] = {0, 18, 12, 6, 0}; constexpr const int shifte[] = {0, 6, 4, 2, 0}; - int len = code_point_length_impl(*s); + int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast(*s) >> 3]; // Compute the pointer to the next character early so that the next // iteration can start working on the next character. Neither Clang // nor GCC figure out this reordering on their own. @@ -636,7 +706,7 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) return next; } -constexpr uint32_t invalid_code_point = ~uint32_t(); +constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); // Invokes f(cp, sv) for every code point cp in s with sv being the string view // corresponding to the code point. cp is invalid_code_point on error. @@ -706,6 +776,7 @@ FMT_CONSTEXPR inline size_t compute_width(string_view s) { return true; } }; + // We could avoid branches by using utf8_decode directly. for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } @@ -737,13 +808,48 @@ inline auto code_point_index(basic_string_view s, size_t n) string_view(reinterpret_cast(s.data()), s.size()), n); } +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#endif + #ifndef FMT_USE_FLOAT128 -# ifdef __SIZEOF_FLOAT128__ -# define FMT_USE_FLOAT128 1 -# else +# ifdef __clang__ +// Clang emulates GCC, so it has to appear early. +# if FMT_HAS_INCLUDE() +# define FMT_USE_FLOAT128 1 +# endif +# elif defined(__GNUC__) +// GNU C++: +# if defined(_GLIBCXX_USE_FLOAT128) && !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +# endif +# endif +# ifndef FMT_USE_FLOAT128 # define FMT_USE_FLOAT128 0 # endif #endif + #if FMT_USE_FLOAT128 using float128 = __float128; #else @@ -787,7 +893,7 @@ template struct is_locale> : std::true_type {}; } // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. @@ -830,7 +936,27 @@ class basic_memory_buffer final : public detail::buffer { } protected: - FMT_CONSTEXPR20 void grow(size_t size) override; + FMT_CONSTEXPR20 void grow(size_t size) override { + detail::abort_fuzzing_if(size > 5000); + const size_t max_size = std::allocator_traits::max_size(alloc_); + size_t old_capacity = this->capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = size > max_size ? size : max_size; + T* old_data = this->data(); + T* new_data = + std::allocator_traits::allocate(alloc_, new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(old_data, old_data + this->size(), + detail::make_checked(new_data, new_capacity)); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) alloc_.deallocate(old_data, old_capacity); + } public: using value_type = T; @@ -907,55 +1033,28 @@ class basic_memory_buffer final : public detail::buffer { } }; -template -FMT_CONSTEXPR20 void basic_memory_buffer::grow( - size_t size) { - detail::abort_fuzzing_if(size > 5000); - const size_t max_size = std::allocator_traits::max_size(alloc_); - size_t old_capacity = this->capacity(); - size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) - new_capacity = size; - else if (new_capacity > max_size) - new_capacity = size > max_size ? size : max_size; - T* old_data = this->data(); - T* new_data = - std::allocator_traits::allocate(alloc_, new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - detail::make_checked(new_data, new_capacity)); - this->set(new_data, new_capacity); - // deallocate must not throw according to the standard, but even if it does, - // the buffer already uses the new storage and will deallocate it in - // destructor. - if (old_data != store_) alloc_.deallocate(old_data, old_capacity); -} - using memory_buffer = basic_memory_buffer; template struct is_contiguous> : std::true_type { }; +FMT_END_EXPORT namespace detail { -#ifdef _WIN32 FMT_API bool write_console(std::FILE* f, string_view text); -#endif FMT_API void print(std::FILE*, string_view); } // namespace detail +FMT_BEGIN_EXPORT -/** A formatting error such as invalid format string. */ -FMT_CLASS_API +// Suppress a misleading warning in older versions of clang. +#if FMT_CLANG_VERSION +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +/** An error reported from a formatting function. */ class FMT_API format_error : public std::runtime_error { public: - explicit format_error(const char* message) : std::runtime_error(message) {} - explicit format_error(const std::string& message) - : std::runtime_error(message) {} - format_error(const format_error&) = default; - format_error& operator=(const format_error&) = default; - format_error(format_error&&) = default; - format_error& operator=(format_error&&) = default; - ~format_error() noexcept override FMT_MSC_DEFAULT; + using std::runtime_error::runtime_error; }; namespace detail_exported { @@ -984,16 +1083,52 @@ constexpr auto compile_string_to_view(detail::std_string_view s) } } // namespace detail_exported -FMT_BEGIN_DETAIL_NAMESPACE +class loc_value { + private: + basic_format_arg value_; -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; + public: + template ::value)> + loc_value(T value) : value_(detail::make_arg(value)) {} -template -using is_signed = - std::integral_constant::is_signed || - std::is_same::value>; + template ::value)> + loc_value(T) {} + + template auto visit(Visitor&& vis) -> decltype(vis(0)) { + return visit_format_arg(vis, value_); + } +}; + +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; + + protected: + virtual auto do_put(appender out, loc_value val, + const format_specs<>& specs) const -> bool; + + public: + static FMT_API typename Locale::id id; + + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", + std::initializer_list g = {3}, + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(g.begin(), g.end()), + decimal_point_(decimal_point) {} + + auto put(appender out, loc_value val, const format_specs<>& specs) const + -> bool { + return do_put(out, val, specs); + } +}; + +FMT_BEGIN_DETAIL_NAMESPACE // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. @@ -1238,7 +1373,7 @@ template format_decimal_result { // Buffer is large enough to hold all digits (digits10 + 1). - Char buffer[digits10() + 1]; + Char buffer[digits10() + 1] = {}; auto end = format_decimal(buffer, value, size).end; return {out, detail::copy_str_noinline(buffer, end, out)}; } @@ -1283,7 +1418,133 @@ class utf8_to_utf16 { auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; +// A converter from UTF-16/UTF-32 (host endian) to UTF-8. +template +class unicode_to_utf8 { + private: + Buffer buffer_; + + public: + unicode_to_utf8() {} + explicit unicode_to_utf8(basic_string_view s) { + static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, + "Expect utf16 or utf32"); + + if (!convert(s)) + FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" + : "invalid utf32")); + } + operator string_view() const { return string_view(&buffer_[0], size()); } + size_t size() const { return buffer_.size() - 1; } + const char* c_str() const { return &buffer_[0]; } + std::string str() const { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a bool instead of throwing exception on + // conversion error. This method may still throw in case of memory allocation + // error. + bool convert(basic_string_view s) { + if (!convert(buffer_, s)) return false; + buffer_.push_back(0); + return true; + } + static bool convert(Buffer& buf, basic_string_view s) { + for (auto p = s.begin(); p != s.end(); ++p) { + uint32_t c = static_cast(*p); + if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { + // surrogate pair + ++p; + if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + return false; + } + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + if (c < 0x80) { + buf.push_back(static_cast(c)); + } else if (c < 0x800) { + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else { + return false; + } + } + return true; + } +}; + +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return {static_cast(p >> 64), static_cast(p)}; +#elif defined(_MSC_VER) && defined(_M_X64) + auto result = uint128_fallback(); + result.lo_ = _umul128(x, y, &result.hi_); + return result; +#else + const uint64_t mask = static_cast(max_value()); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + namespace dragonbox { +// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from +// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. +inline int floor_log10_pow2(int e) noexcept { + FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); + static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); + return (e * 315653) >> 20; +} + +inline int floor_log2_pow10(int e) noexcept { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + return (e * 1741647) >> 19; +} + +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return static_cast(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline uint128_fallback umul192_upper128(uint64_t x, + uint128_fallback y) noexcept { + uint128_fallback r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; +} + +FMT_API uint128_fallback get_cached_power(int k) noexcept; // Type-specific information that Dragonbox uses. template struct float_info; @@ -1307,7 +1568,7 @@ template <> struct float_info { static const int big_divisor = 1000; static const int small_divisor = 100; static const int min_k = -292; - static const int max_k = 326; + static const int max_k = 341; static const int shorter_interval_tie_lower_threshold = -77; static const int shorter_interval_tie_upper_threshold = -77; }; @@ -1354,8 +1615,8 @@ template constexpr int num_significand_bits() { template constexpr auto exponent_mask() -> typename dragonbox::float_info::carrier_uint { - using uint = typename dragonbox::float_info::carrier_uint; - return ((uint(1) << dragonbox::float_info::exponent_bits) - 1) + using float_uint = typename dragonbox::float_info::carrier_uint; + return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) << num_significand_bits(); } template constexpr auto exponent_bias() -> int { @@ -1532,12 +1793,28 @@ template struct basic_data { static constexpr uint64_t power_of_10_64[20] = { 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL), 10000000000000000000ULL}; + + // For checking rounding thresholds. + // The kth entry is chosen to be the smallest integer such that the + // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. + static constexpr uint32_t fractional_part_rounding_thresholds[8] = { + 2576980378, // ceil(2^31 + 2^32/10^1) + 2190433321, // ceil(2^31 + 2^32/10^2) + 2151778616, // ceil(2^31 + 2^32/10^3) + 2147913145, // ceil(2^31 + 2^32/10^4) + 2147526598, // ceil(2^31 + 2^32/10^5) + 2147487943, // ceil(2^31 + 2^32/10^6) + 2147484078, // ceil(2^31 + 2^32/10^7) + 2147483691 // ceil(2^31 + 2^32/10^8) + }; }; #if FMT_CPLUSPLUS < 201703L template constexpr uint64_t basic_data::pow10_significands[]; template constexpr int16_t basic_data::pow10_exponents[]; template constexpr uint64_t basic_data::power_of_10_64[]; +template +constexpr uint32_t basic_data::fractional_part_rounding_thresholds[]; #endif // This is a struct rather than an alias to avoid shadowing warnings in gcc. @@ -1567,65 +1844,11 @@ FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, *(data::pow10_exponents + index)}; } -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else -FMT_API auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) -> int; -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -// Formats a floating-point number with snprintf using the hexfloat format. -template -auto snprintf_float(T value, int precision, float_specs specs, - buffer& buf) -> int { - // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. - FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); - FMT_ASSERT(specs.format == float_format::hex, ""); - static_assert(!std::is_same::value, ""); - - // Build the format string. - char format[7]; // The longest format is "%#.*Le". - char* format_ptr = format; - *format_ptr++ = '%'; - if (specs.showpoint) *format_ptr++ = '#'; - if (precision >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - if (std::is_same()) *format_ptr++ = 'L'; - *format_ptr++ = specs.upper ? 'A' : 'a'; - *format_ptr = '\0'; - - // Format using snprintf. - auto offset = buf.size(); - for (;;) { - auto begin = buf.data() + offset; - auto capacity = buf.capacity() - offset; - abort_fuzzing_if(precision > 100000); - // Suppress the warning about a nonliteral format string. - // Cannot use auto because of a bug in MinGW (#1532). - int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; - int result = precision >= 0 - ? snprintf_ptr(begin, capacity, format, precision, value) - : snprintf_ptr(begin, capacity, format, value); - if (result < 0) { - // The buffer will grow exponentially. - buf.try_reserve(buf.capacity() + 1); - continue; - } - auto size = to_unsigned(result); - // Size equal to capacity means that the last character was truncated. - if (size < capacity) { - buf.try_resize(size + offset); - return 0; - } - buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. - } -} - template using convert_float_result = - conditional_t::value || sizeof(T) == sizeof(double), + conditional_t::value || + std::numeric_limits::digits == + std::numeric_limits::digits, double, T>; template @@ -1649,8 +1872,7 @@ FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, // width: output display width in (terminal) column positions. template -FMT_CONSTEXPR auto write_padded(OutputIt out, - const basic_format_specs& specs, +FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, size_t size, size_t width, F&& f) -> OutputIt { static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); @@ -1669,15 +1891,14 @@ FMT_CONSTEXPR auto write_padded(OutputIt out, template -constexpr auto write_padded(OutputIt out, const basic_format_specs& specs, +constexpr auto write_padded(OutputIt out, const format_specs& specs, size_t size, F&& f) -> OutputIt { return write_padded(out, specs, size, size, f); } template FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, - const basic_format_specs& specs) - -> OutputIt { + const format_specs& specs) -> OutputIt { return write_padded( out, specs, bytes.size(), [bytes](reserve_iterator it) { const char* data = bytes.data(); @@ -1686,8 +1907,8 @@ FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, } template -auto write_ptr(OutputIt out, UIntPtr value, - const basic_format_specs* specs) -> OutputIt { +auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) + -> OutputIt { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); auto write = [=](reserve_iterator it) { @@ -1806,16 +2027,14 @@ auto write_escaped_cp(OutputIt out, const find_escape_result& escape) *out++ = static_cast('\\'); break; default: - if (is_utf8()) { - if (escape.cp < 0x100) { - return write_codepoint<2, Char>(out, 'x', escape.cp); - } - if (escape.cp < 0x10000) { - return write_codepoint<4, Char>(out, 'u', escape.cp); - } - if (escape.cp < 0x110000) { - return write_codepoint<8, Char>(out, 'U', escape.cp); - } + if (escape.cp < 0x100) { + return write_codepoint<2, Char>(out, 'x', escape.cp); + } + if (escape.cp < 0x10000) { + return write_codepoint<4, Char>(out, 'u', escape.cp); + } + if (escape.cp < 0x110000) { + return write_codepoint<8, Char>(out, 'U', escape.cp); } for (Char escape_char : basic_string_view( escape.begin, to_unsigned(escape.end - escape.begin))) { @@ -1860,8 +2079,7 @@ auto write_escaped_char(OutputIt out, Char v) -> OutputIt { template FMT_CONSTEXPR auto write_char(OutputIt out, Char value, - const basic_format_specs& specs) - -> OutputIt { + const format_specs& specs) -> OutputIt { bool is_debug = specs.type == presentation_type::debug; return write_padded(out, specs, 1, [=](reserve_iterator it) { if (is_debug) return write_escaped_char(it, value); @@ -1871,11 +2089,14 @@ FMT_CONSTEXPR auto write_char(OutputIt out, Char value, } template FMT_CONSTEXPR auto write(OutputIt out, Char value, - const basic_format_specs& specs, - locale_ref loc = {}) -> OutputIt { + const format_specs& specs, locale_ref loc = {}) + -> OutputIt { + // char is formatted as unsigned char for consistency across platforms. + using unsigned_type = + conditional_t::value, unsigned char, unsigned>; return check_char_specs(specs) ? write_char(out, value, specs) - : write(out, static_cast(value), specs, loc); + : write(out, static_cast(value), specs, loc); } // Data for write_int that doesn't depend on output iterator type. It is used to @@ -1885,7 +2106,7 @@ template struct write_int_data { size_t padding; FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, - const basic_format_specs& specs) + const format_specs& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); @@ -1907,7 +2128,7 @@ template struct write_int_data { template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, unsigned prefix, - const basic_format_specs& specs, + const format_specs& specs, W write_digits) -> OutputIt { // Slightly faster check for specs.width == 0 && specs.precision == -1. if ((specs.width | (specs.precision + 1)) == 0) { @@ -1930,19 +2151,19 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, template class digit_grouping { private: - thousands_sep_result sep_; + std::string grouping_; + std::basic_string thousands_sep_; struct next_state { std::string::const_iterator group; int pos; }; - next_state initial_state() const { return {sep_.grouping.begin(), 0}; } + next_state initial_state() const { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. int next(next_state& state) const { - if (!sep_.thousands_sep) return max_value(); - if (state.group == sep_.grouping.end()) - return state.pos += sep_.grouping.back(); + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); if (*state.group <= 0 || *state.group == max_value()) return max_value(); state.pos += *state.group++; @@ -1951,14 +2172,15 @@ template class digit_grouping { public: explicit digit_grouping(locale_ref loc, bool localized = true) { - if (localized) - sep_ = thousands_sep(loc); - else - sep_.thousands_sep = Char(); + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } - explicit digit_grouping(thousands_sep_result sep) : sep_(sep) {} + digit_grouping(std::string grouping, std::basic_string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} - Char separator() const { return sep_.thousands_sep; } + bool has_separator() const { return !thousands_sep_.empty(); } int count_separators(int num_digits) const { int count = 0; @@ -1981,7 +2203,9 @@ template class digit_grouping { for (int i = 0, sep_index = static_cast(separators.size() - 1); i < num_digits; ++i) { if (num_digits - i == separators[sep_index]) { - *out++ = separator(); + out = + copy_str(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); --sep_index; } *out++ = static_cast(digits[to_unsigned(i)]); @@ -1990,10 +2214,11 @@ template class digit_grouping { } }; +// Writes a decimal integer with digit grouping. template -auto write_int_localized(OutputIt out, UInt value, unsigned prefix, - const basic_format_specs& specs, - const digit_grouping& grouping) -> OutputIt { +auto write_int(OutputIt out, UInt value, unsigned prefix, + const format_specs& specs, + const digit_grouping& grouping) -> OutputIt { static_assert(std::is_same, UInt>::value, ""); int num_digits = count_digits(value); char digits[40]; @@ -2010,13 +2235,13 @@ auto write_int_localized(OutputIt out, UInt value, unsigned prefix, }); } -template -auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, - const basic_format_specs& specs, locale_ref loc) - -> bool { - auto grouping = digit_grouping(loc); - out = write_int_localized(out, value, prefix, specs, grouping); - return true; +// Writes a localized value. +FMT_API auto write_loc(appender out, loc_value value, + const format_specs<>& specs, locale_ref loc) -> bool; +template +inline auto write_loc(OutputIt, loc_value, const format_specs&, + locale_ref) -> bool { + return false; } FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { @@ -2045,21 +2270,37 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) return {abs_value, prefix}; } +template struct loc_writer { + buffer_appender out; + const format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; + + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } +}; + template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, - const basic_format_specs& specs, - locale_ref loc) -> OutputIt { + const format_specs& specs, + locale_ref) -> OutputIt { static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; switch (specs.type) { case presentation_type::none: case presentation_type::dec: { - if (specs.localized && - write_int_localized(out, static_cast>(abs_value), - prefix, specs, loc)) { - return out; - } auto num_digits = count_digits(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { @@ -2102,13 +2343,13 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, case presentation_type::chr: return write_char(out, static_cast(abs_value), specs); default: - throw_format_error("invalid type specifier"); + throw_format_error("invalid format specifier"); } return out; } template FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline( - OutputIt out, write_int_arg arg, const basic_format_specs& specs, + OutputIt out, write_int_arg arg, const format_specs& specs, locale_ref loc) -> OutputIt { return write_int(out, arg, specs, loc); } @@ -2117,8 +2358,9 @@ template ::value && std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const basic_format_specs& specs, + const format_specs& specs, locale_ref loc) -> OutputIt { + if (specs.localized && write_loc(out, value, specs, loc)) return out; return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2128,8 +2370,9 @@ template ::value && !std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const basic_format_specs& specs, + const format_specs& specs, locale_ref loc) -> OutputIt { + if (specs.localized && write_loc(out, value, specs, loc)) return out; return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2175,7 +2418,7 @@ class counting_iterator { template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, - const basic_format_specs& specs) -> OutputIt { + const format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) @@ -2197,16 +2440,15 @@ FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view> s, - const basic_format_specs& specs, locale_ref) + const format_specs& specs, locale_ref) -> OutputIt { - check_string_type_spec(specs.type); return write(out, s, specs); } template FMT_CONSTEXPR auto write(OutputIt out, const Char* s, - const basic_format_specs& specs, locale_ref) + const format_specs& specs, locale_ref) -> OutputIt { - return check_cstring_type_spec(specs.type) + return specs.type != presentation_type::pointer ? write(out, basic_string_view(s), specs, {}) : write_ptr(out, bit_cast(s), &specs); } @@ -2233,9 +2475,71 @@ FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { return base_iterator(out, it); } +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed, // Fixed point with the default precision of 6, e.g. 0.0012. + hex +}; + +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool upper : 1; + bool locale : 1; + bool binary32 : 1; + bool showpoint : 1; +}; + +template +FMT_CONSTEXPR auto parse_float_type_spec(const format_specs& specs, + ErrorHandler&& eh = {}) + -> float_specs { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + case presentation_type::none: + result.format = float_format::general; + break; + case presentation_type::general_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::general_lower: + result.format = float_format::general; + break; + case presentation_type::exp_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::exp_lower: + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::fixed_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::fixed_lower: + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::hexfloat_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::hexfloat_lower: + result.format = float_format::hex; + break; + default: + eh.on_error("invalid format specifier"); + break; + } + return result; +} + template FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, - basic_format_specs specs, + format_specs specs, const float_specs& fspecs) -> OutputIt { auto str = isnan ? (fspecs.upper ? "NAN" : "nan") : (fspecs.upper ? "INF" : "inf"); @@ -2281,7 +2585,7 @@ template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int exponent, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { out = write_significand(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast('0')); } @@ -2343,7 +2647,7 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int integral_size, Char decimal_point, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); } @@ -2359,7 +2663,7 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, template > FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, - const basic_format_specs& specs, + const format_specs& specs, float_specs fspecs, locale_ref loc) -> OutputIt { auto significand = f.significand; @@ -2418,7 +2722,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, abort_fuzzing_if(num_zeros > 5000); if (fspecs.showpoint) { ++size; - if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; + if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 0; if (num_zeros > 0) size += to_unsigned(num_zeros); } auto grouping = Grouping(loc, fspecs.locale); @@ -2436,7 +2740,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); auto grouping = Grouping(loc, fspecs.locale); - size += to_unsigned(grouping.count_separators(significand_size)); + size += to_unsigned(grouping.count_separators(exp)); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); it = write_significand(it, significand, significand_size, exp, @@ -2466,7 +2770,7 @@ template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr Char separator() const { return Char(); } + constexpr bool has_separator() const { return false; } constexpr int count_separators(int) const { return 0; } @@ -2478,7 +2782,7 @@ template class fallback_digit_grouping { template FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, - const basic_format_specs& specs, + const format_specs& specs, float_specs fspecs, locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { @@ -2506,14 +2810,14 @@ template ::value&& FMT_CONSTEXPR20 bool isfinite(T value) { constexpr T inf = T(std::numeric_limits::infinity()); if (is_constant_evaluated()) - return !detail::isnan(value) && value != inf && value != -inf; + return !detail::isnan(value) && value < inf && value > -inf; return std::isfinite(value); } template ::value)> FMT_CONSTEXPR bool isfinite(T value) { T inf = T(std::numeric_limits::infinity()); // std::isfinite doesn't support __float128. - return !detail::isnan(value) && value != inf && value != -inf; + return !detail::isnan(value) && value < inf && value > -inf; } template ::value)> @@ -3094,6 +3398,94 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, buf[num_digits - 1] = static_cast('0' + digit); } +// Formats a floating-point number using the hexfloat format. +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, + float_specs specs, buffer& buf) { + // float is passed as double to reduce the number of instantiations and to + // simplify implementation. + static_assert(!std::is_same::value, ""); + + using info = dragonbox::float_info; + + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename info::carrier_uint; + + constexpr auto num_float_significand_bits = + detail::num_significand_bits(); + + basic_fp f(value); + f.e += num_float_significand_bits; + if (!has_implicit_bit()) --f.e; + + constexpr auto num_fraction_bits = + num_float_significand_bits + (has_implicit_bit() ? 1 : 0); + constexpr auto num_xdigits = (num_fraction_bits + 3) / 4; + + constexpr auto leading_shift = ((num_xdigits - 1) * 4); + const auto leading_mask = carrier_uint(0xF) << leading_shift; + const auto leading_xdigit = + static_cast((f.f & leading_mask) >> leading_shift); + if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); + + int print_xdigits = num_xdigits - 1; + if (precision >= 0 && print_xdigits > precision) { + const int shift = ((print_xdigits - precision - 1) * 4); + const auto mask = carrier_uint(0xF) << shift; + const auto v = static_cast((f.f & mask) >> shift); + + if (v >= 8) { + const auto inc = carrier_uint(1) << (shift + 4); + f.f += inc; + f.f &= ~(inc - 1); + } + + // Check long double overflow + if (!has_implicit_bit()) { + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + if ((f.f & implicit_bit) == implicit_bit) { + f.f >>= 4; + f.e += 4; + } + } + + print_xdigits = precision; + } + + char xdigits[num_bits() / 4]; + detail::fill_n(xdigits, sizeof(xdigits), '0'); + format_uint<4>(xdigits, f.f, num_xdigits, specs.upper); + + // Remove zero tail + while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; + + buf.push_back('0'); + buf.push_back(specs.upper ? 'X' : 'x'); + buf.push_back(xdigits[0]); + if (specs.showpoint || print_xdigits > 0 || print_xdigits < precision) + buf.push_back('.'); + buf.append(xdigits + 1, xdigits + 1 + print_xdigits); + for (; print_xdigits < precision; ++print_xdigits) buf.push_back('0'); + + buf.push_back(specs.upper ? 'P' : 'p'); + + uint32_t abs_e; + if (f.e < 0) { + buf.push_back('-'); + abs_e = static_cast(-f.e); + } else { + buf.push_back('+'); + abs_e = static_cast(f.e); + } + format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); +} + +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, + float_specs specs, buffer& buf) { + format_hexfloat(static_cast(value), precision, specs, buf); +} + template FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, buffer& buf) -> int { @@ -3137,7 +3529,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, auto dec = dragonbox::to_decimal(static_cast(value)); write(buffer_appender(buf), dec.significand); return dec.exponent; - } else { + } else if (is_constant_evaluated()) { // Use Grisu + Dragon4 for the given precision: // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. const int min_exp = -60; // alpha in Grisu. @@ -3156,6 +3548,248 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, exp += handler.size - cached_exp10 - 1; precision = handler.precision; } + } else { + // Extract significand bits and exponent bits. + using info = dragonbox::float_info; + auto br = bit_cast(static_cast(value)); + + const uint64_t significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + uint64_t significand = (br & significand_mask); + int exponent = static_cast((br & exponent_mask()) >> + num_significand_bits()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); + significand |= + (static_cast(1) << num_significand_bits()); + significand <<= 1; + } else { + // Normalize subnormal inputs. + FMT_ASSERT(significand != 0, "zeros should not appear hear"); + int shift = countl_zero(significand); + FMT_ASSERT(shift >= num_bits() - num_significand_bits(), + ""); + shift -= (num_bits() - num_significand_bits() - 2); + exponent = (std::numeric_limits::min_exponent - + num_significand_bits()) - + shift; + significand <<= shift; + } + + // Compute the first several nonzero decimal significand digits. + // We call the number we get the first segment. + const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); + exp = -k; + const int beta = exponent + dragonbox::floor_log2_pow10(k); + uint64_t first_segment; + bool has_more_segments; + int digits_in_the_first_segment; + { + const auto r = dragonbox::umul192_upper128( + significand << beta, dragonbox::get_cached_power(k)); + first_segment = r.high(); + has_more_segments = r.low() != 0; + + // The first segment can have 18 ~ 19 digits. + if (first_segment >= 1000000000000000000ULL) { + digits_in_the_first_segment = 19; + } else { + // When it is of 18-digits, we align it to 19-digits by adding a bogus + // zero at the end. + digits_in_the_first_segment = 18; + first_segment *= 10; + } + } + + // Compute the actual number of decimal digits to print. + if (fixed) { + adjust_precision(precision, exp + digits_in_the_first_segment); + } + + // Use Dragon4 only when there might be not enough digits in the first + // segment. + if (digits_in_the_first_segment > precision) { + use_dragon = false; + + if (precision <= 0) { + exp += digits_in_the_first_segment; + + if (precision < 0) { + // Nothing to do, since all we have are just leading zeros. + buf.try_resize(0); + } else { + // We may need to round-up. + buf.try_resize(1); + if ((first_segment | static_cast(has_more_segments)) > + 5000000000000000000ULL) { + buf[0] = '1'; + } else { + buf[0] = '0'; + } + } + } // precision <= 0 + else { + exp += digits_in_the_first_segment - precision; + + // When precision > 0, we divide the first segment into three + // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits + // in 32-bits which usually allows faster calculation than in + // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize + // division-by-constant for large 64-bit divisors, we do it here + // manually. The magic number 7922816251426433760 below is equal to + // ceil(2^(64+32) / 10^10). + const uint32_t first_subsegment = static_cast( + dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> + 32); + const uint64_t second_third_subsegments = + first_segment - first_subsegment * 10000000000ULL; + + uint64_t prod; + uint32_t digits; + bool should_round_up; + int number_of_digits_to_print = precision > 9 ? 9 : precision; + + // Print a 9-digits subsegment, either the first or the second. + auto print_subsegment = [&](uint32_t subsegment, char* buffer) { + int number_of_digits_printed = 0; + + // If we want to print an odd number of digits from the subsegment, + if ((number_of_digits_to_print & 1) != 0) { + // Convert to 64-bit fixed-point fractional form with 1-digit + // integer part. The magic number 720575941 is a good enough + // approximation of 2^(32 + 24) / 10^8; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(720575941)) >> 24) + 1; + digits = static_cast(prod >> 32); + *buffer = static_cast('0' + digits); + number_of_digits_printed++; + } + // If we want to print an even number of digits from the + // first_subsegment, + else { + // Convert to 64-bit fixed-point fractional form with 2-digits + // integer part. The magic number 450359963 is a good enough + // approximation of 2^(32 + 20) / 10^7; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(450359963)) >> 20) + 1; + digits = static_cast(prod >> 32); + copy2(buffer, digits2(digits)); + number_of_digits_printed += 2; + } + + // Print all digit pairs. + while (number_of_digits_printed < number_of_digits_to_print) { + prod = static_cast(prod) * static_cast(100); + digits = static_cast(prod >> 32); + copy2(buffer + number_of_digits_printed, digits2(digits)); + number_of_digits_printed += 2; + } + }; + + // Print first subsegment. + print_subsegment(first_subsegment, buf.data()); + + // Perform rounding if the first subsegment is the last subsegment to + // print. + if (precision <= 9) { + // Rounding inside the subsegment. + // We round-up if: + // - either the fractional part is strictly larger than 1/2, or + // - the fractional part is exactly 1/2 and the last digit is odd. + // We rely on the following observations: + // - If fractional_part >= threshold, then the fractional part is + // strictly larger than 1/2. + // - If the MSB of fractional_part is set, then the fractional part + // must be at least 1/2. + // - When the MSB of fractional_part is set, either + // second_third_subsegments being nonzero or has_more_segments + // being true means there are further digits not printed, so the + // fractional part is strictly larger than 1/2. + if (precision < 9) { + uint32_t fractional_part = static_cast(prod); + should_round_up = fractional_part >= + data::fractional_part_rounding_thresholds + [8 - number_of_digits_to_print] || + ((fractional_part >> 31) & + ((digits & 1) | (second_third_subsegments != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + // In this case, the fractional part is at least 1/2 if and only if + // second_third_subsegments >= 5000000000ULL, and is strictly larger + // than 1/2 if we further have either second_third_subsegments > + // 5000000000ULL or has_more_segments == true. + else { + should_round_up = second_third_subsegments > 5000000000ULL || + (second_third_subsegments == 5000000000ULL && + ((digits & 1) != 0 || has_more_segments)); + } + } + // Otherwise, print the second subsegment. + else { + // Compilers are not aware of how to leverage the maximum value of + // second_third_subsegments to find out a better magic number which + // allows us to eliminate an additional shift. 1844674407370955162 = + // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). + const uint32_t second_subsegment = + static_cast(dragonbox::umul128_upper64( + second_third_subsegments, 1844674407370955162ULL)); + const uint32_t third_subsegment = + static_cast(second_third_subsegments) - + second_subsegment * 10; + + number_of_digits_to_print = precision - 9; + print_subsegment(second_subsegment, buf.data() + 9); + + // Rounding inside the subsegment. + if (precision < 18) { + // The condition third_subsegment != 0 implies that the segment was + // of 19 digits, so in this case the third segment should be + // consisting of a genuine digit from the input. + uint32_t fractional_part = static_cast(prod); + should_round_up = fractional_part >= + data::fractional_part_rounding_thresholds + [8 - number_of_digits_to_print] || + ((fractional_part >> 31) & + ((digits & 1) | (third_subsegment != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + else { + // In this case, the segment must be of 19 digits, thus + // the third subsegment should be consisting of a genuine digit from + // the input. + should_round_up = third_subsegment > 5 || + (third_subsegment == 5 && + ((digits & 1) != 0 || has_more_segments)); + } + } + + // Round-up if necessary. + if (should_round_up) { + ++buf[precision - 1]; + for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[precision++] = '0'; + else + ++exp; + } + } + buf.try_resize(to_unsigned(precision)); + } + } // if (digits_in_the_first_segment > precision) + else { + // Adjust the exponent for its use in Dragon4. + exp += digits_in_the_first_segment - 1; + } } if (use_dragon) { auto f = basic_fp(); @@ -3181,13 +3815,10 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, } return exp; } - -template ::value)> -FMT_CONSTEXPR20 auto write(OutputIt out, T value, - basic_format_specs specs, locale_ref loc = {}) +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, + format_specs specs, locale_ref loc) -> OutputIt { - if (const_check(!is_supported_floating_point(value))) return out; float_specs fspecs = parse_float_type_spec(specs); fspecs.sign = specs.sign; if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit. @@ -3211,7 +3842,7 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value, memory_buffer buffer; if (fspecs.format == float_format::hex) { if (fspecs.sign) buffer.push_back(detail::sign(fspecs.sign)); - snprintf_float(convert_float(value), specs.precision, fspecs, buffer); + format_hexfloat(convert_float(value), specs.precision, fspecs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } @@ -3233,11 +3864,20 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value, return write_float(out, f, specs, fspecs, loc); } +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, + locale_ref loc = {}) -> OutputIt { + if (const_check(!is_supported_floating_point(value))) return out; + return specs.localized && write_loc(out, value, specs, loc) + ? out + : write_float(out, value, specs, loc); +} + template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { - if (is_constant_evaluated()) - return write(out, value, basic_format_specs()); + if (is_constant_evaluated()) return write(out, value, format_specs()); if (const_check(!is_supported_floating_point(value))) return out; auto fspecs = float_specs(); @@ -3246,11 +3886,11 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { value = -value; } - constexpr auto specs = basic_format_specs(); + constexpr auto specs = format_specs(); using floaty = conditional_t::value, double, T>; - using uint = typename dragonbox::float_info::carrier_uint; - uint mask = exponent_mask(); - if ((bit_cast(value) & mask) == mask) + using floaty_uint = typename dragonbox::float_info::carrier_uint; + floaty_uint mask = exponent_mask(); + if ((bit_cast(value) & mask) == mask) return write_nonfinite(out, std::isnan(value), specs, fspecs); auto dec = dragonbox::to_decimal(static_cast(value)); @@ -3261,12 +3901,12 @@ template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { - return write(out, value, basic_format_specs()); + return write(out, value, format_specs()); } template -auto write(OutputIt out, monostate, basic_format_specs = {}, - locale_ref = {}) -> OutputIt { +auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) + -> OutputIt { FMT_ASSERT(false, ""); return out; } @@ -3300,8 +3940,8 @@ FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { template ::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, - const basic_format_specs& specs = {}, - locale_ref = {}) -> OutputIt { + const format_specs& specs = {}, locale_ref = {}) + -> OutputIt { return specs.type != presentation_type::none && specs.type != presentation_type::string ? write(out, value ? 1 : 0, specs, {}) @@ -3318,20 +3958,15 @@ FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { template FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) -> OutputIt { - if (!value) { - throw_format_error("string pointer is null"); - } else { - out = write(out, basic_string_view(value)); - } + if (value) return write(out, basic_string_view(value)); + throw_format_error("string pointer is null"); return out; } template ::value)> -auto write(OutputIt out, const T* value, - const basic_format_specs& specs = {}, locale_ref = {}) - -> OutputIt { - check_pointer_type_spec(specs.type, error_handler()); +auto write(OutputIt out, const T* value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { return write_ptr(out, bit_cast(value), &specs); } @@ -3341,8 +3976,8 @@ template enable_if_t< std::is_class::value && !is_string::value && !is_floating_point::value && !std::is_same::value && - !std::is_same().map(value))>::value, + !std::is_same().map( + value))>>::value, OutputIt> { return write(out, arg_mapper().map(value)); } @@ -3352,12 +3987,8 @@ template enable_if_t::value == type::custom_type, OutputIt> { - using formatter_type = - conditional_t::value, - typename Context::template formatter_type, - fallback_formatter>; auto ctx = Context(out, {}, {}); - return formatter_type().format(value, ctx); + return typename Context::template formatter_type().format(value, ctx); } // An argument visitor that formats the argument and writes it via the output @@ -3386,7 +4017,7 @@ template struct arg_formatter { using context = buffer_context; iterator out; - const basic_format_specs& specs; + const format_specs& specs; locale_ref locale; template @@ -3411,12 +4042,6 @@ template struct custom_formatter { template void operator()(T) const {} }; -template -using is_integer = - bool_constant::value && !std::is_same::value && - !std::is_same::value && - !std::is_same::value>; - template class width_checker { public: explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} @@ -3473,48 +4098,6 @@ FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> return arg; } -// The standard format specifier handler with checking. -template class specs_handler : public specs_setter { - private: - basic_format_parse_context& parse_context_; - buffer_context& context_; - - // This is only needed for compatibility with gcc 4.4. - using format_arg = basic_format_arg>; - - FMT_CONSTEXPR auto get_arg(auto_id) -> format_arg { - return detail::get_arg(context_, parse_context_.next_arg_id()); - } - - FMT_CONSTEXPR auto get_arg(int arg_id) -> format_arg { - parse_context_.check_arg_id(arg_id); - return detail::get_arg(context_, arg_id); - } - - FMT_CONSTEXPR auto get_arg(basic_string_view arg_id) -> format_arg { - parse_context_.check_arg_id(arg_id); - return detail::get_arg(context_, arg_id); - } - - public: - FMT_CONSTEXPR specs_handler(basic_format_specs& specs, - basic_format_parse_context& parse_ctx, - buffer_context& ctx) - : specs_setter(specs), parse_context_(parse_ctx), context_(ctx) {} - - template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - this->specs_.width = get_dynamic_spec( - get_arg(arg_id), context_.error_handler()); - } - - template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - this->specs_.precision = get_dynamic_spec( - get_arg(arg_id), context_.error_handler()); - } - - void on_error(const char* message) { context_.on_error(message); } -}; - template