From d88e8e4332c7054159deed6bc611858cc65a3e18 Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Fri, 21 Mar 2025 12:15:09 -0700 Subject: [PATCH 1/9] Export functions implemented in impl.cpp --- CMakeLists.txt | 13 +- cmake/install.cmake | 6 + include/scn/chrono.h | 30 ++++- include/scn/fwd.h | 5 + include/scn/scan.h | 280 ++++++++++++++++++++++--------------------- src/scn/impl.cpp | 5 + src/scn/impl.h | 24 ++-- 7 files changed, 216 insertions(+), 147 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99fc383d..4acb054c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ set(SCN_PUBLIC_HEADERS include/scn/regex.h include/scn/istream.h include/scn/xchar.h + "${CMAKE_CURRENT_BINARY_DIR}/include/scn/scn_export.h" ) set(SCN_PRIVATE_HEADERS src/scn/impl.h @@ -60,6 +61,7 @@ add_library(scn target_include_directories(scn PUBLIC $ + $ $ PRIVATE $ @@ -70,7 +72,15 @@ target_link_libraries(scn PRIVATE ) set_library_flags(scn) -set_property(TARGET scn PROPERTY SOVERSION 4) +include(GenerateExportHeader) +generate_export_header(scn + EXPORT_MACRO_NAME SCN_EXPORT + EXPORT_FILE_NAME include/scn/scn_export.h) + +set_target_properties(scn PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + SOVERSION 4) add_library(scn::scn ALIAS scn) add_library(scn_internal INTERFACE) @@ -82,6 +92,7 @@ target_link_libraries(scn_internal INTERFACE target_include_directories(scn_internal INTERFACE $ + $ $ ) set_interface_flags(scn_internal) diff --git a/cmake/install.cmake b/cmake/install.cmake index 07ab483e..99adb189 100644 --- a/cmake/install.cmake +++ b/cmake/install.cmake @@ -61,6 +61,12 @@ install(FILES COMPONENT scnlib_Development ) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/include/scn/scn_export.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/scn" + COMPONENT scnlib_Development +) + export(EXPORT scn-targets FILE "${CMAKE_CURRENT_BINARY_DIR}/scn-targets.cmake" NAMESPACE scn:: diff --git a/include/scn/chrono.h b/include/scn/chrono.h index cb25ac56..749f28fb 100644 --- a/include/scn/chrono.h +++ b/include/scn/chrono.h @@ -1512,9 +1512,37 @@ constexpr auto chrono_parse_impl(ParseCtx& pctx, } template -auto chrono_scan_impl(std::basic_string_view fmt_str, T& t, Context& ctx) +SCN_EXPORT auto chrono_scan_impl(std::basic_string_view fmt_str, + T& t, + Context& ctx) -> scan_expected; +extern template SCN_EXPORT auto chrono_scan_impl(std::string_view, + std::tm&, + scan_context&) + -> scan_expected; +extern template SCN_EXPORT auto chrono_scan_impl(std::string_view, + tm_with_tz&, + scan_context&) + -> scan_expected; +extern template SCN_EXPORT auto chrono_scan_impl(std::string_view, + datetime_components&, + scan_context&) + -> scan_expected; + +extern template SCN_EXPORT auto chrono_scan_impl(std::wstring_view, + std::tm&, + wscan_context&) + -> scan_expected; +extern template SCN_EXPORT auto chrono_scan_impl(std::wstring_view, + tm_with_tz&, + wscan_context&) + -> scan_expected; +extern template SCN_EXPORT auto chrono_scan_impl(std::wstring_view, + datetime_components&, + wscan_context&) + -> scan_expected; + template struct chrono_datetime_scanner { template diff --git a/include/scn/fwd.h b/include/scn/fwd.h index 02df47b0..c3fe7992 100644 --- a/include/scn/fwd.h +++ b/include/scn/fwd.h @@ -363,6 +363,11 @@ SCN_GCC_IGNORE("-Wrestrict") SCN_GCC_POP +///////////////////////////////////////////////////////////////// +// Export include +///////////////////////////////////////////////////////////////// +#include + ///////////////////////////////////////////////////////////////// // Environment detection (preprocessor only) ///////////////////////////////////////////////////////////////// diff --git a/include/scn/scan.h b/include/scn/scan.h index 3a811b92..8f449558 100644 --- a/include/scn/scan.h +++ b/include/scn/scan.h @@ -415,8 +415,8 @@ static constexpr deferred_init_tag_t deferred_init_tag{}; template || std::is_trivially_destructible_v)&&std:: - is_trivially_destructible_v> + (std::is_void_v || std::is_trivially_destructible_v) && + std::is_trivially_destructible_v> struct expected_storage_base; template @@ -849,9 +849,8 @@ template struct SCN_TRIVIAL_ABI expected_operations_base< T, E, - std::enable_if_t<( - std::is_void_v || - std::is_trivially_copyable_v)&&std::is_trivially_copyable_v>> + std::enable_if_t<(std::is_void_v || std::is_trivially_copyable_v) && + std::is_trivially_copyable_v>> : expected_storage_base { using expected_storage_base::expected_storage_base; }; @@ -1181,10 +1180,10 @@ struct SCN_TRIVIAL_ABI template < typename T, typename E, - bool EnableCopy = ((std::is_copy_constructible_v || - std::is_void_v)&&std::is_copy_constructible_v), - bool EnableMove = ((std::is_move_constructible_v || - std::is_void_v)&&std::is_move_constructible_v)> + bool EnableCopy = ((std::is_copy_constructible_v || std::is_void_v) && + std::is_copy_constructible_v), + bool EnableMove = ((std::is_move_constructible_v || std::is_void_v) && + std::is_move_constructible_v)> struct expected_delete_ctor_base; // Implementation for types that are both copy and move @@ -1241,14 +1240,14 @@ struct SCN_TRIVIAL_ABI expected_delete_ctor_base { template < typename T, typename E, - bool EnableCopy = ((std::is_copy_constructible_v || - std::is_void_v)&&std::is_copy_constructible_v && - (std::is_copy_assignable_v || - std::is_void_v)&&std::is_copy_assignable_v), - bool EnableMove = ((std::is_move_constructible_v || - std::is_void_v)&&std::is_move_constructible_v && - (std::is_move_assignable_v || - std::is_void_v)&&std::is_move_assignable_v)> + bool EnableCopy = ((std::is_copy_constructible_v || std::is_void_v) && + std::is_copy_constructible_v && + (std::is_copy_assignable_v || std::is_void_v) && + std::is_copy_assignable_v), + bool EnableMove = ((std::is_move_constructible_v || std::is_void_v) && + std::is_move_constructible_v && + (std::is_move_assignable_v || std::is_void_v) && + std::is_move_assignable_v)> struct expected_delete_assign_base; template @@ -3817,7 +3816,7 @@ constexpr bool operator!=(enum scan_error::code a, scan_error b) noexcept namespace detail { // Intentionally not constexpr, to give out a compile-time error -SCN_COLD scan_error handle_error(scan_error e); +SCN_EXPORT SCN_COLD scan_error handle_error(scan_error e); } // namespace detail #if SCN_HAS_EXCEPTIONS @@ -5086,7 +5085,7 @@ using stdio_file_interface = stdio_file_interface_impl; template -class basic_scan_file_buffer : public basic_scan_buffer { +class SCN_EXPORT basic_scan_file_buffer : public basic_scan_buffer { using base = basic_scan_buffer; public: @@ -5109,13 +5108,19 @@ struct scan_file_buffer : public basic_scan_file_buffer { } }; -extern template basic_scan_file_buffer< +#if SCN_GCC +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92914 +extern template SCN_EXPORT basic_scan_file_buffer< stdio_file_interface>::basic_scan_file_buffer(stdio_file_interface); -extern template basic_scan_file_buffer< - stdio_file_interface>::~basic_scan_file_buffer(); -extern template bool basic_scan_file_buffer::fill(); -extern template bool basic_scan_file_buffer::sync( - std::ptrdiff_t); +extern template SCN_EXPORT + basic_scan_file_buffer::~basic_scan_file_buffer(); +extern template SCN_EXPORT bool +basic_scan_file_buffer::fill(); +extern template SCN_EXPORT bool +basic_scan_file_buffer::sync(std::ptrdiff_t); +#else +extern template class SCN_EXPORT basic_scan_file_buffer; +#endif template class basic_scan_ref_buffer : public basic_scan_buffer { @@ -5556,7 +5561,7 @@ class arg_value { SCN_STD > SCN_STD_20 constexpr #endif - arg_value() = default; + arg_value() = default; template explicit constexpr arg_value(T& val) : ref_value{std::addressof(val)} @@ -8777,7 +8782,7 @@ class basic_scan_format_string { }; namespace detail { -class locale_ref { +class SCN_EXPORT locale_ref { #if !SCN_DISABLE_LOCALE public: constexpr locale_ref() = default; @@ -8982,7 +8987,7 @@ constexpr typename ParseCtx::iterator scanner_parse_for_builtin_type( format_specs& specs); template -scan_expected +SCN_EXPORT scan_expected scanner_scan_for_builtin_type(T& val, Context& ctx, const format_specs& specs); } // namespace detail @@ -9151,39 +9156,39 @@ struct scanner, CharT> : public scanner { namespace detail { template -scan_expected> internal_skip_classic_whitespace( - Range r, - bool allow_exhaustion); +SCN_EXPORT scan_expected> +internal_skip_classic_whitespace(Range r, bool allow_exhaustion); -#define SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(T, Context) \ - extern template scan_expected \ +#define SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(T, Context) \ + extern template SCN_EXPORT scan_expected \ scanner_scan_for_builtin_type(T&, Context&, const format_specs&); -#define SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_CTX(Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(char, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(wchar_t, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(signed char, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(signed char, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(short, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(int, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(long, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(long long, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned char, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned short, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned int, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned long, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned long long, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(float, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(double, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(long double, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(std::string, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(std::wstring, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE( \ - std::basic_string_view, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(regex_matches, Context) \ - SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(wregex_matches, Context) \ - extern template scan_expected> \ - internal_skip_classic_whitespace(Context::range_type, bool); +#define SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_CTX(Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(char, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(wchar_t, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(signed char, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(signed char, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(short, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(int, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(long, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(long long, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned char, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned short, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned int, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned long, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(unsigned long long, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(float, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(double, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(long double, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(std::string, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(std::wstring, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE( \ + std::basic_string_view, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(regex_matches, Context) \ + SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_TYPE(wregex_matches, Context) \ + extern template SCN_EXPORT \ + scan_expected> \ + internal_skip_classic_whitespace(Context::range_type, bool); SCN_DECLARE_EXTERN_SCANNER_SCAN_FOR_CTX(scan_context) @@ -9337,55 +9342,59 @@ template using vscan_result = scan_expected>; namespace detail { -scan_expected vscan_impl(std::string_view source, - std::string_view format, - scan_args args); -scan_expected vscan_impl(scan_buffer& source, - std::string_view format, - scan_args args); - -scan_expected vscan_impl(std::wstring_view source, - std::wstring_view format, - wscan_args args); -scan_expected vscan_impl(wscan_buffer& source, - std::wstring_view format, - wscan_args args); +SCN_EXPORT scan_expected vscan_impl(std::string_view source, + std::string_view format, + scan_args args); +SCN_EXPORT scan_expected vscan_impl(scan_buffer& source, + std::string_view format, + scan_args args); + +SCN_EXPORT scan_expected vscan_impl(std::wstring_view source, + std::wstring_view format, + wscan_args args); +SCN_EXPORT scan_expected vscan_impl(wscan_buffer& source, + std::wstring_view format, + wscan_args args); #if !SCN_DISABLE_LOCALE template -scan_expected vscan_localized_impl(const Locale& loc, - std::string_view source, - std::string_view format, - scan_args args); +SCN_EXPORT scan_expected vscan_localized_impl( + const Locale& loc, + std::string_view source, + std::string_view format, + scan_args args); template -scan_expected vscan_localized_impl(const Locale& loc, - scan_buffer& source, - std::string_view format, - scan_args args); +SCN_EXPORT scan_expected vscan_localized_impl( + const Locale& loc, + scan_buffer& source, + std::string_view format, + scan_args args); template -scan_expected vscan_localized_impl(const Locale& loc, - std::wstring_view source, - std::wstring_view format, - wscan_args args); +SCN_EXPORT scan_expected vscan_localized_impl( + const Locale& loc, + std::wstring_view source, + std::wstring_view format, + wscan_args args); template -scan_expected vscan_localized_impl(const Locale& loc, - wscan_buffer& source, - std::wstring_view format, - wscan_args args); +SCN_EXPORT scan_expected vscan_localized_impl( + const Locale& loc, + wscan_buffer& source, + std::wstring_view format, + wscan_args args); #endif -scan_expected vscan_value_impl( +SCN_EXPORT scan_expected vscan_value_impl( std::string_view source, basic_scan_arg arg); -scan_expected vscan_value_impl( +SCN_EXPORT scan_expected vscan_value_impl( scan_buffer& source, basic_scan_arg arg); -scan_expected vscan_value_impl( +SCN_EXPORT scan_expected vscan_value_impl( std::wstring_view source, basic_scan_arg arg); -scan_expected vscan_value_impl( +SCN_EXPORT scan_expected vscan_value_impl( wscan_buffer& source, basic_scan_arg arg); @@ -9498,91 +9507,94 @@ auto vscan_value(Source&& source, basic_scan_arg arg) * * \ingroup vscan */ -scan_expected vinput(std::string_view format, scan_args args); +SCN_EXPORT scan_expected vinput(std::string_view format, scan_args args); namespace detail { template -auto scan_int_impl(std::string_view source, T& value, int base) +SCN_EXPORT auto scan_int_impl(std::string_view source, T& value, int base) -> scan_expected; template -auto scan_int_exhaustive_valid_impl(std::string_view source) -> T; +SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view source) -> T; #if !SCN_DISABLE_TYPE_SCHAR -extern template auto scan_int_impl(std::string_view source, - signed char& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + signed char& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) -> signed char; #endif #if !SCN_DISABLE_TYPE_SHORT -extern template auto scan_int_impl(std::string_view source, - short& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + short& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) -> short; +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) + -> short; #endif #if !SCN_DISABLE_TYPE_INT -extern template auto scan_int_impl(std::string_view source, - int& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + int& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) -> int; +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) + -> int; #endif #if !SCN_DISABLE_TYPE_LONG -extern template auto scan_int_impl(std::string_view source, - long& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + long& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) -> long; +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) + -> long; #endif #if !SCN_DISABLE_TYPE_LONG_LONG -extern template auto scan_int_impl(std::string_view source, - long long& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + long long& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) -> long long; #endif #if !SCN_DISABLE_TYPE_UCHAR -extern template auto scan_int_impl(std::string_view source, - unsigned char& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + unsigned char& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) -> unsigned char; #endif #if !SCN_DISABLE_TYPE_USHORT -extern template auto scan_int_impl(std::string_view source, - unsigned short& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + unsigned short& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) -> unsigned short; #endif #if !SCN_DISABLE_TYPE_UINT -extern template auto scan_int_impl(std::string_view source, - unsigned int& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + unsigned int& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) -> unsigned int; #endif #if !SCN_DISABLE_TYPE_ULONG -extern template auto scan_int_impl(std::string_view source, - unsigned long& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + unsigned long& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) -> unsigned long; #endif #if !SCN_DISABLE_TYPE_ULONG_LONG -extern template auto scan_int_impl(std::string_view source, - unsigned long long& value, - int base) +extern template SCN_EXPORT auto scan_int_impl(std::string_view source, + unsigned long long& value, + int base) -> scan_expected; -extern template auto scan_int_exhaustive_valid_impl(std::string_view) +extern template SCN_EXPORT auto scan_int_exhaustive_valid_impl(std::string_view) -> unsigned long long; #endif diff --git a/src/scn/impl.cpp b/src/scn/impl.cpp index aa0d3343..be2bda01 100644 --- a/src/scn/impl.cpp +++ b/src/scn/impl.cpp @@ -286,6 +286,8 @@ SCN_DEFINE_SCANNER_SCAN_FOR_CTX(wscan_context) // scan_buffer implementations ///////////////////////////////////////////////////////////////// +#if SCN_GCC +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92914 template basic_scan_file_buffer::basic_scan_file_buffer( stdio_file_interface); template basic_scan_file_buffer< @@ -293,6 +295,9 @@ template basic_scan_file_buffer< template bool basic_scan_file_buffer::fill(); template bool basic_scan_file_buffer::sync( std::ptrdiff_t); +#else +template class basic_scan_file_buffer; +#endif } // namespace detail diff --git a/src/scn/impl.h b/src/scn/impl.h index 8a9acd94..a516a1ce 100644 --- a/src/scn/impl.h +++ b/src/scn/impl.h @@ -1923,13 +1923,13 @@ struct localized_number_formatting_options { namespace impl { -std::string_view::iterator find_classic_space_narrow_fast( +SCN_EXPORT std::string_view::iterator find_classic_space_narrow_fast( std::string_view source); -std::string_view::iterator find_classic_nonspace_narrow_fast( +SCN_EXPORT std::string_view::iterator find_classic_nonspace_narrow_fast( std::string_view source); -std::string_view::iterator find_nondecimal_digit_narrow_fast( +SCN_EXPORT std::string_view::iterator find_nondecimal_digit_narrow_fast( std::string_view source); template @@ -3453,21 +3453,22 @@ auto parse_integer_digits_with_thsep( } template -auto parse_integer_value(std::basic_string_view source, - T& value, - sign_type sign, - int base) +SCN_EXPORT auto parse_integer_value(std::basic_string_view source, + T& value, + sign_type sign, + int base) -> scan_expected::iterator>; template -void parse_integer_value_exhaustive_valid(std::string_view source, T& value); +SCN_EXPORT void parse_integer_value_exhaustive_valid(std::string_view source, + T& value); #define SCN_DECLARE_INTEGER_READER_TEMPLATE(CharT, IntT) \ - extern template auto parse_integer_value( \ + extern template SCN_EXPORT auto parse_integer_value( \ std::basic_string_view source, IntT& value, sign_type sign, \ int base) \ -> scan_expected::iterator>; \ - extern template void parse_integer_value_exhaustive_valid( \ + extern template SCN_EXPORT void parse_integer_value_exhaustive_valid( \ std::string_view, IntT&); #if !SCN_DISABLE_TYPE_SCHAR @@ -3679,7 +3680,8 @@ struct float_reader_base { }; template -class float_reader : public numeric_reader, public float_reader_base { +class SCN_EXPORT float_reader : public numeric_reader, + public float_reader_base { using numeric_base = numeric_reader; public: From d72918cf9edb91e5dbec937e61f3278a88d96b9b Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Fri, 21 Mar 2025 12:17:01 -0700 Subject: [PATCH 2/9] Use SCN_NODISCARD everywhere --- include/scn/chrono.h | 4 ++-- include/scn/scan.h | 4 ++-- tests/unittests/impl_tests/float_reader_test.cpp | 10 +++++----- tests/unittests/impl_tests/integer_reader_test.h | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/scn/chrono.h b/include/scn/chrono.h index 749f28fb..8cb76ab8 100644 --- a/include/scn/chrono.h +++ b/include/scn/chrono.h @@ -306,7 +306,7 @@ struct datetime_components { * The fields `subsec` and `tz_name` are discarded. * `tz_offset` is set to `tm_gmtoff`, if it's available. */ - [[nodiscard]] std::tm to_tm() const + SCN_NODISCARD std::tm to_tm() const { SCN_UNUSED(subsec); std::tm t{ @@ -1462,7 +1462,7 @@ struct tm_format_checker { st.verify(*this); } - [[nodiscard]] constexpr scan_expected get_error() const + SCN_NODISCARD constexpr scan_expected get_error() const { return err; } diff --git a/include/scn/scan.h b/include/scn/scan.h index 8f449558..c0d6c7c2 100644 --- a/include/scn/scan.h +++ b/include/scn/scan.h @@ -3340,14 +3340,14 @@ class view_interface { public: template - [[nodiscard]] constexpr auto empty() + SCN_NODISCARD constexpr auto empty() -> std::enable_if_t, bool> { return ranges::begin(derived()) == ranges::end(derived()); } template - [[nodiscard]] constexpr auto empty() const + SCN_NODISCARD constexpr auto empty() const -> std::enable_if_t, bool> { return ranges::begin(derived()) == ranges::end(derived()); diff --git a/tests/unittests/impl_tests/float_reader_test.cpp b/tests/unittests/impl_tests/float_reader_test.cpp index ba1a8e7f..247be76e 100644 --- a/tests/unittests/impl_tests/float_reader_test.cpp +++ b/tests/unittests/impl_tests/float_reader_test.cpp @@ -136,7 +136,7 @@ constexpr T float_zero() } template -[[nodiscard]] testing::AssertionResult +SCN_NODISCARD testing::AssertionResult check_floating_eq(T a, T b, bool allow_approx = false) { SCN_GCC_COMPAT_PUSH @@ -540,7 +540,7 @@ class FloatValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult check_generic_success( + SCN_NODISCARD testing::AssertionResult check_generic_success( const Result& result) const { if (!result) { @@ -562,7 +562,7 @@ class FloatValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult check_value_success( + SCN_NODISCARD testing::AssertionResult check_value_success( const Result& result, float_type val, float_type expected) const @@ -577,7 +577,7 @@ class FloatValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult check_failure_with_code( + SCN_NODISCARD testing::AssertionResult check_failure_with_code( const Result& result, enum scn::scan_error::code c) const { @@ -655,7 +655,7 @@ class FloatValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult simple_default_test( + SCN_NODISCARD testing::AssertionResult simple_default_test( Source&& source, float_type expected_output) { diff --git a/tests/unittests/impl_tests/integer_reader_test.h b/tests/unittests/impl_tests/integer_reader_test.h index 23a3478f..de9586f5 100644 --- a/tests/unittests/impl_tests/integer_reader_test.h +++ b/tests/unittests/impl_tests/integer_reader_test.h @@ -321,7 +321,7 @@ class IntValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult check_generic_success( + SCN_NODISCARD testing::AssertionResult check_generic_success( const Result& result) const { if (!result) { @@ -341,7 +341,7 @@ class IntValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult check_value_success( + SCN_NODISCARD testing::AssertionResult check_value_success( const Result& result, int_type val, int_type expected) const @@ -357,7 +357,7 @@ class IntValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult check_failure_with_code( + SCN_NODISCARD testing::AssertionResult check_failure_with_code( const Result& result, int_type val, enum scn::scan_error::code c) const @@ -379,7 +379,7 @@ class IntValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult check_failure_with_code_and_value( + SCN_NODISCARD testing::AssertionResult check_failure_with_code_and_value( const Result& result, int_type val, enum scn::scan_error::code c, @@ -464,7 +464,7 @@ class IntValueReaderTest : public testing::Test { } template - [[nodiscard]] testing::AssertionResult simple_default_test( + SCN_NODISCARD testing::AssertionResult simple_default_test( Source&& source, int_type expected_output) { From a0e3da42d02863396e3cdbce3f8d230bce4bf3d7 Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Fri, 21 Mar 2025 12:18:55 -0700 Subject: [PATCH 3/9] fast_float: support version 7+ chars_format became an enum class --- cmake/dependencies.cmake | 2 +- src/scn/impl.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 689110b6..a1efa411 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -111,7 +111,7 @@ else () FetchContent_Declare( fast_float GIT_REPOSITORY https://github.com/fastfloat/fast_float.git - GIT_TAG v6.1.6 + GIT_TAG v7.0.0 GIT_SHALLOW TRUE ) diff --git a/src/scn/impl.cpp b/src/scn/impl.cpp index be2bda01..59f107f3 100644 --- a/src/scn/impl.cpp +++ b/src/scn/impl.cpp @@ -726,15 +726,17 @@ scan_expected fast_float_fallback(impl_init_data data, struct fast_float_impl_base : impl_base { fast_float::chars_format get_flags() const { - unsigned format_flags{}; + fast_float::chars_format format_flags{}; if ((m_options & float_reader_base::allow_fixed) != 0) { - format_flags |= fast_float::fixed; + format_flags = static_cast( + format_flags | fast_float::chars_format::fixed); } if ((m_options & float_reader_base::allow_scientific) != 0) { - format_flags |= fast_float::scientific; + format_flags = static_cast( + format_flags | fast_float::chars_format::scientific); } - return static_cast(format_flags); + return format_flags; } }; From 0f11c81074d1f83208a6b75c8c4d58dadecbeffb Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Tue, 1 Apr 2025 13:44:43 -0400 Subject: [PATCH 4/9] fast_float: disable long double tests long double type is not supported by fast_float. As of fast_float 7.0.0 compiling with fast_float leads to a compiler error, rather than selecting the wrong overload. --- benchmark/runtime/float/repeated.cpp | 1 - benchmark/runtime/float/single.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/benchmark/runtime/float/repeated.cpp b/benchmark/runtime/float/repeated.cpp index 0aa0c39a..8a4de0a1 100644 --- a/benchmark/runtime/float/repeated.cpp +++ b/benchmark/runtime/float/repeated.cpp @@ -210,4 +210,3 @@ static void scan_float_repeated_fastfloat(benchmark::State& state) } BENCHMARK_TEMPLATE(scan_float_repeated_fastfloat, float); BENCHMARK_TEMPLATE(scan_float_repeated_fastfloat, double); -BENCHMARK_TEMPLATE(scan_float_repeated_fastfloat, long double); diff --git a/benchmark/runtime/float/single.cpp b/benchmark/runtime/float/single.cpp index e06cd138..6819621b 100644 --- a/benchmark/runtime/float/single.cpp +++ b/benchmark/runtime/float/single.cpp @@ -185,4 +185,3 @@ static void scan_float_single_fastfloat(benchmark::State& state) } BENCHMARK_TEMPLATE(scan_float_single_fastfloat, float); BENCHMARK_TEMPLATE(scan_float_single_fastfloat, double); -BENCHMARK_TEMPLATE(scan_float_single_fastfloat, long double); From 2abc8cf6749f48a3fbf3575f54047a36db227bfb Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Mon, 24 Mar 2025 12:18:56 -0400 Subject: [PATCH 5/9] gcc: fix assert usage in constexpr function --- include/scn/scan.h | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/include/scn/scan.h b/include/scn/scan.h index c0d6c7c2..b7a39e98 100644 --- a/include/scn/scan.h +++ b/include/scn/scan.h @@ -3777,7 +3777,12 @@ class SCN_TRIVIAL_ABI scan_error { return std::errc::result_out_of_range; case max_error: default: - assert(false); +#if !SCN_GCC || SCN_GCC >= SCN_COMPILER(9, 0, 0) + // gcc 7 thinks we'll get here, even when we won't + // gcc 8 has a bug in debug mode, + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678 + SCN_EXPECT(false); +#endif SCN_UNREACHABLE; } } @@ -4155,8 +4160,10 @@ inline constexpr char32_t decode_utf8_code_point_exhaustive( return cp; } -#if !SCN_GCC || SCN_GCC >= SCN_COMPILER(8, 0, 0) +#if !SCN_GCC || SCN_GCC >= SCN_COMPILER(9, 0, 0) // gcc 7 thinks we'll get here, even when we won't + // gcc 8 has a bug in debug mode, + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678 SCN_EXPECT(false); #endif SCN_UNREACHABLE; @@ -4213,8 +4220,10 @@ inline constexpr char32_t decode_utf8_code_point_exhaustive_valid( return cp; } -#if !SCN_GCC || SCN_GCC >= SCN_COMPILER(8, 0, 0) +#if !SCN_GCC || SCN_GCC >= SCN_COMPILER(9, 0, 0) // gcc 7 thinks we'll get here, even when we won't + // gcc 8 has a bug in debug mode, + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678 SCN_EXPECT(false); #endif SCN_UNREACHABLE; @@ -5773,7 +5782,6 @@ constexpr size_t encode_types() (encode_types_impl() << packed_arg_bits); } else { - SCN_EXPECT(false); SCN_UNREACHABLE; } } @@ -6997,7 +7005,12 @@ struct format_specs { return 16; default: +#if !SCN_GCC || SCN_GCC >= SCN_COMPILER(9, 0, 0) + // gcc 7 thinks we'll get here, even when we won't + // gcc 8 has a bug in debug mode, + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678 SCN_EXPECT(false); +#endif SCN_UNREACHABLE; } SCN_GCC_COMPAT_POP @@ -7247,7 +7260,12 @@ constexpr presentation_type parse_presentation_type(CharT type) case '/': // Should be handled by parse_presentation_set and // parse_presentation_regex +#if !SCN_GCC || SCN_GCC >= SCN_COMPILER(9, 0, 0) + // gcc 7 thinks we'll get here, even when we won't + // gcc 8 has a bug in debug mode, + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678 SCN_EXPECT(false); +#endif SCN_UNREACHABLE; default: return presentation_type::none; From f9587506258c2228ec2caa59bab379150cc7b801 Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Fri, 28 Mar 2025 18:37:50 -0400 Subject: [PATCH 6/9] gcc: fix template deduction warning warning: 'basic_string_view' may not intend to support class template argument deduction [-Wctad-maybe-unsupported] --- include/scn/scan.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/scn/scan.h b/include/scn/scan.h index b7a39e98..c974faa3 100644 --- a/include/scn/scan.h +++ b/include/scn/scan.h @@ -5332,7 +5332,8 @@ template ) { if constexpr (is_valid_char_type>) { - return std::basic_string_view{ranges::data(r), ranges::size(r)}; + return std::basic_string_view>{ranges::data(r), + ranges::size(r)}; } else { return invalid_char_type{}; From ebfa8879bad933546d1a81322908136b59929ccd Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Sat, 19 Apr 2025 17:49:49 -0400 Subject: [PATCH 7/9] basic_scan_file_buffer: Remove current_view size 1 assumption basic_scan_file_buffer::sync was assuming that when buffering is off, the current_view's size will always be 1, but fill can return an empty current_view. --- include/scn/scan.h | 6 +++--- src/scn/impl.h | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/scn/scan.h b/include/scn/scan.h index c974faa3..9adf03de 100644 --- a/include/scn/scan.h +++ b/include/scn/scan.h @@ -4892,7 +4892,7 @@ struct stdio_file_interface_impl void lock() {} void unlock() {} - bool has_buffering() const + static constexpr bool has_buffering() { return false; } @@ -4941,7 +4941,7 @@ struct posix_stdio_file_interface : stdio_file_interface_base { funlockfile(this->file); } - static bool has_buffering() + static constexpr bool has_buffering() { return true; } @@ -5051,7 +5051,7 @@ struct stdio_file_interface_impl _unlock_file(this->file); } - static bool has_buffering() + static constexpr bool has_buffering() { return false; } diff --git a/src/scn/impl.h b/src/scn/impl.h index a516a1ce..0bdfa9ee 100644 --- a/src/scn/impl.h +++ b/src/scn/impl.h @@ -1211,12 +1211,11 @@ bool basic_scan_file_buffer::sync(std::ptrdiff_t position) } const auto chars_avail = this->chars_available(); - if (position == chars_avail) { + if (position == chars_avail || SCN_UNLIKELY(m_current_view.empty())) { return true; } putback_wrapper wrapper{m_file}; - SCN_EXPECT(m_current_view.size() == 1); m_file.putback(m_current_view.front()); auto segment = std::string_view{this->putback_buffer()}.substr(position); From b34e82224923203af8cb78a1727a3bd8a63d1f95 Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Sat, 19 Apr 2025 18:08:20 -0400 Subject: [PATCH 8/9] arg_reader: Fix errors when reading last part of contiguous segments from file The contiguous range might fail because it does not have all the characters, so we fall back and scan the range as not contiguous A basic_scan_file_buffer, when it has buffering, it can become discontiguous while moving forward when executing ranges::next(range.begin(), ranges::distance(crange.begin(), it)). E.g. the contiguous buffer might have 4.1234, but the full number is 4.12345 For that reason, we fall back and scan the range as not contiguous Also, it might not read all the digits because it does not have them all E.g. if it has 1.1234e, but it needed 1.1234e-01, it will disregard the e. For that reason, we fall back and scan the range as not contiguous --- src/scn/impl.h | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/scn/impl.h b/src/scn/impl.h index 0bdfa9ee..498a08f7 100644 --- a/src/scn/impl.h +++ b/src/scn/impl.h @@ -5911,9 +5911,21 @@ struct default_arg_reader { return impl(rd, range, value); } auto crange = get_as_contiguous(range); - SCN_TRY(it, impl(rd, crange, value)); - return ranges::next(range.begin(), - ranges::distance(crange.begin(), it)); + auto&& scn_result = impl(rd, crange, value); + if (!scn_result) { + return impl(rd, range, value); + } + auto it = *SCN_FWD(scn_result); + auto chars_read = ranges::distance(crange.begin(), it); + auto result = ranges::next(range.begin(), chars_read); + auto chars_left = ranges::distance(it, crange.end()); + if (!is_segment_contiguous(range) || + (std::is_arithmetic_v && + chars_left <= std::numeric_limits::digits && + chars_left > 0 && !is_ascii_space(*it))) { + return impl(rd, range, value); + } + return result; } else { SCN_EXPECT(false); @@ -6229,11 +6241,22 @@ struct arg_reader { specs.width != 0) { return impl(rd, range, value); } - auto crange = get_as_contiguous(range); - SCN_TRY(it, impl(rd, crange, value)); - return ranges::next(range.begin(), - ranges::distance(crange.begin(), it)); + auto&& scn_result = impl(rd, crange, value); + if (!scn_result) { + return impl(rd, range, value); + } + auto it = *SCN_FWD(scn_result); + auto chars_read = ranges::distance(crange.begin(), it); + auto result = ranges::next(range.begin(), chars_read); + auto chars_left = ranges::distance(it, crange.end()); + if (!is_segment_contiguous(range) || + (std::is_arithmetic_v && + chars_left <= std::numeric_limits::digits && + chars_left > 0 && !is_ascii_space(*it))) { + return impl(rd, range, value); + } + return result; } else { SCN_EXPECT(false); From 0aa4d058decd0140b5b671c0420043267c038a7c Mon Sep 17 00:00:00 2001 From: Spiros Tsalikis Date: Wed, 23 Apr 2025 12:30:21 -0400 Subject: [PATCH 9/9] win32/default: Enforce only 1 successfully putback guarantee https://en.cppreference.com/w/c/io/ungetc states that only 1 byte is guaranteed to be put back. Even if linux/mac support 4KB, windows and other platforms are very strict with 1 character, and they can fail if you try 2-3-4 ungetc. To that extent, no more characters than needed are ever consumed. Also, when parsing from a file, if whitespace has been consumed, it remains consumed, it's not put back, similar to fscanf. https://pubs.opengroup.org/onlinepubs/000095399/functions/fscanf.html --- include/scn/scan.h | 10 ++ src/scn/impl.cpp | 18 +- src/scn/impl.h | 169 ++++++++++-------- tests/unittests/examples_test_runner.py | 2 +- .../impl_tests/read_algorithms_test.cpp | 12 +- 5 files changed, 129 insertions(+), 82 deletions(-) diff --git a/include/scn/scan.h b/include/scn/scan.h index 9adf03de..2aa8d127 100644 --- a/include/scn/scan.h +++ b/include/scn/scan.h @@ -4418,6 +4418,15 @@ class basic_scan_buffer { SCN_NODISCARD range_type get(); SCN_NODISCARD common_range_type get_common_range(); + void set_skip_whitespace(bool skip) + { + m_skip_whitespace = skip; + } + SCN_NODISCARD bool get_skip_whitespace() const + { + return m_skip_whitespace; + } + protected: friend class forward_iterator; friend class common_forward_iterator; @@ -4444,6 +4453,7 @@ class basic_scan_buffer { std::basic_string_view m_current_view{}; std::basic_string m_putback_buffer{}; bool m_is_contiguous{false}; + bool m_skip_whitespace{false}; }; template diff --git a/src/scn/impl.cpp b/src/scn/impl.cpp index 59f107f3..d8be4338 100644 --- a/src/scn/impl.cpp +++ b/src/scn/impl.cpp @@ -1822,6 +1822,19 @@ auto scan_int_exhaustive_valid_impl(std::string_view source) -> T impl::parse_integer_value_exhaustive_valid(source, value); return value; } + +template +auto get_failed_sync_position(Source& source) -> std::ptrdiff_t +{ + const auto& buffer = source.get_segment_starting_at(0); + if (source.get_skip_whitespace() && !buffer.empty()) { + auto it = std::find_if(buffer.begin(), buffer.end(), [](auto ch) { + return !impl::is_ascii_space(ch); + }); + return std::distance(buffer.begin(), it); + } + return 0; +} } // namespace detail scan_expected vinput(std::string_view format, scan_args args) @@ -1836,7 +1849,7 @@ scan_expected vinput(std::string_view format, scan_args args) } return {}; } - if (SCN_UNLIKELY(!buffer.sync_all())) { + if (SCN_UNLIKELY(!buffer.sync(detail::get_failed_sync_position(buffer)))) { return detail::unexpected_scan_error( scan_error::invalid_source_state, "Failed to sync with underlying FILE"); @@ -1861,7 +1874,8 @@ scan_expected sync_after_vscan( } } else { - if (SCN_UNLIKELY(!source.sync_all())) { + if (SCN_UNLIKELY( + !source.sync(detail::get_failed_sync_position(source)))) { return detail::unexpected_scan_error( scan_error::invalid_source_state, "Failed to sync with underlying source"); diff --git a/src/scn/impl.h b/src/scn/impl.h index 498a08f7..9fc9ab7f 100644 --- a/src/scn/impl.h +++ b/src/scn/impl.h @@ -1197,12 +1197,7 @@ bool basic_scan_file_buffer::sync(std::ptrdiff_t position) static_cast(this->putback_buffer().size())) { putback_wrapper wrapper{m_file}; auto segment = this->get_segment_starting_at(position); - for (auto it = segment.rbegin(); it != segment.rend(); ++it) { - if (!m_file.putback(*it)) { - return false; - } - } - return true; + return segment.empty(); } m_file.unsafe_advance_n(position - static_cast( @@ -1219,12 +1214,7 @@ bool basic_scan_file_buffer::sync(std::ptrdiff_t position) m_file.putback(m_current_view.front()); auto segment = std::string_view{this->putback_buffer()}.substr(position); - for (auto it = segment.rbegin(); it != segment.rend(); ++it) { - if (!m_file.putback(*it)) { - return false; - } - } - return true; + return segment.empty(); } } // namespace detail @@ -1981,6 +1971,7 @@ auto read_exactly_n_code_units(Range range, std::ptrdiff_t count) template struct read_code_point_into_result { Iterator iterator; + std::size_t codepoint_length; std::basic_string codepoint; bool is_valid() const @@ -1990,7 +1981,7 @@ struct read_code_point_into_result { }; template -auto read_code_point_into(Range range) +auto extract_code_point_into(Range range) -> read_code_point_into_result, detail::char_t> { @@ -2001,24 +1992,72 @@ auto read_code_point_into(Range range) const auto len = detail::code_point_length_by_starting_code_unit(*it); if (SCN_UNLIKELY(len == 0)) { - ++it; - it = get_start_for_next_code_point(ranges::subrange{it, range.end()}); - return {it, {}}; + return {it, len, {}}; } - if (len == 1) { + return {it, len, string_type(1, *it)}; + } + ranges::advance(it, static_cast(len - 1), range.end()); + const auto real_len = ranges::distance(range.begin(), it) + 1;; + SCN_EXPECT(real_len <= 4); + if (SCN_LIKELY(real_len == len && it != range.end())) { + auto it2 = range.begin(); + string_type result(len, *it2); + for (std::size_t i = 1; i < len; ++i) { + result[i] = *(++it2); + } + return {it, len, std::move(result)}; + } + return {it, len, string_type(range.begin(), it)}; +} + +template +auto advance_code_point_into( + Range range, + const read_code_point_into_result, + detail::char_t>& result) + -> ranges::const_iterator_t +{ + auto it = result.iterator; + const auto& len = result.codepoint_length; + + if (SCN_UNLIKELY(len == 0)) { + ++it; + return get_start_for_next_code_point(ranges::subrange{it, range.end()}); + } + if (it != range.end()) { ++it; - return {it, string_type(1, *range.begin())}; } + return it; +} - ranges::advance(it, static_cast(len), range.end()); - return {it, string_type{range.begin(), it}}; +template +auto read_code_point_into(Range range) + -> read_code_point_into_result, + detail::char_t> +{ + auto result = extract_code_point_into(range); + result.iterator = advance_code_point_into(range, result); + return result; } template auto read_code_point(Range range) -> ranges::const_iterator_t { - return read_code_point_into(range).iterator; + SCN_EXPECT(!is_range_eof(range)); + + auto it = range.begin(); + const auto len = detail::code_point_length_by_starting_code_unit(*it); + + if (SCN_UNLIKELY(len == 0)) { + ++it; + return get_start_for_next_code_point(ranges::subrange{it, range.end()}); + } + if (len == 1) { + return ++it; + } + ranges::advance(it, static_cast(len), range.end()); + return it; } template @@ -2158,8 +2197,8 @@ auto read_until_code_point(Range range, function_ref pred) { auto it = range.begin(); while (it != range.end()) { - const auto val = - read_code_point_into(ranges::subrange{it, range.end()}); + auto subrange = ranges::subrange{it, range.end()}; + const auto val = extract_code_point_into(subrange); if (SCN_LIKELY(val.is_valid())) { const auto cp = detail::decode_code_point_exhaustive( std::basic_string_view>{val.codepoint}); @@ -2167,7 +2206,7 @@ auto read_until_code_point(Range range, function_ref pred) return it; } } - it = val.iterator; + it = advance_code_point_into(subrange, val); } return it; @@ -2242,9 +2281,8 @@ template auto read_matching_code_unit(Range range, detail::char_t ch) -> parse_expected> { - auto it = read_code_unit(range); - if (SCN_UNLIKELY(!it)) { - return unexpected(make_eof_parse_error(it.error())); + if (auto e = eof_check(range); SCN_UNLIKELY(!e)) { + return unexpected(make_eof_parse_error(e)); } if (SCN_UNLIKELY(*range.begin() != @@ -2252,14 +2290,14 @@ auto read_matching_code_unit(Range range, detail::char_t ch) return unexpected(parse_error::error); } - return *it; + return ranges::next(range.begin()); } template auto read_matching_code_point(Range range, char32_t cp) -> parse_expected> { - auto val = read_code_point_into(range); + auto val = extract_code_point_into(range); if (!val.is_valid()) { return unexpected(parse_error::error); } @@ -2267,7 +2305,7 @@ auto read_matching_code_point(Range range, char32_t cp) if (SCN_UNLIKELY(cp != decoded_cp)) { return unexpected(parse_error::error); } - return val.iterator; + return advance_code_point_into(range, val); } template @@ -2286,33 +2324,6 @@ auto read_matching_string(Range range, return it; } -template -auto read_matching_string_classic(Range range, std::string_view str) - -> parse_expected> -{ - SCN_TRY(it, read_exactly_n_code_units( - range, static_cast(str.size())) - .transform_error(make_eof_parse_error)); - - if constexpr (std::is_same_v, char>) { - auto sv = make_contiguous_buffer(ranges::subrange{range.begin(), it}); - if (SCN_UNLIKELY(sv.view() != str)) { - return unexpected(parse_error::error); - } - return it; - } - else { - auto range_it = range.begin(); - for (size_t i = 0; i < str.size(); ++i, (void)++range_it) { - if (SCN_UNLIKELY(*range_it != - static_cast>(str[i]))) { - return unexpected(parse_error::error); - } - } - return it; - } -} - // Ripped from fast_float constexpr bool fast_streq_nocase(const char* a, const char* b, size_t len) { @@ -2348,16 +2359,15 @@ auto read_matching_string_classic_nocase(Range range, std::string_view str) static_cast('a' - 'A')); }; - SCN_TRY(it, read_exactly_n_code_units( - range, static_cast(str.size())) - .transform_error(make_eof_parse_error)); - - if (SCN_UNLIKELY(!std::equal( - range.begin(), it, str.begin(), [&](auto a, auto b) { - return ascii_tolower(a) == - static_cast>(b); - }))) { - return unexpected(parse_error::error); + auto it = range.begin(); + for (std::size_t i = 0; i < str.size(); ++i, (void)++it) { + if (it == range.end()) { + return unexpected(make_eof_parse_error(eof_error::eof)); + } + if (SCN_UNLIKELY(ascii_tolower(*it) != + static_cast>(str[i]))) { + return unexpected(parse_error::error); + } } return it; @@ -2368,14 +2378,13 @@ template auto read_one_of_code_unit(Range range, std::string_view str) -> parse_expected> { - auto it = read_code_unit(range); - if (SCN_UNLIKELY(!it)) { - return unexpected(make_eof_parse_error(it.error())); + if (auto e = eof_check(range); SCN_UNLIKELY(!e)) { + return unexpected(make_eof_parse_error(e)); } for (auto ch : str) { if (*range.begin() == static_cast>(ch)) { - return *it; + return ranges::next(range.begin()); } } @@ -5426,11 +5435,11 @@ struct bool_reader_base { auto read_textual_classic(Range range, bool& value) const -> scan_expected> { - if (auto r = read_matching_string_classic(range, "true")) { + if (auto r = read_matching_string_classic_nocase(range, "true")) { value = true; return *r; } - if (auto r = read_matching_string_classic(range, "false")) { + if (auto r = read_matching_string_classic_nocase(range, "false")) { value = false; return *r; } @@ -5605,7 +5614,7 @@ class code_point_reader { auto read(const SourceRange& range, char32_t& cp) -> scan_expected> { - auto result = read_code_point_into(range); + auto result = extract_code_point_into(range); if (SCN_UNLIKELY(!result.is_valid())) { return detail::unexpected_scan_error( scan_error::invalid_scanned_value, "Invalid code point"); @@ -5613,7 +5622,7 @@ class code_point_reader { cp = detail::decode_code_point_exhaustive_valid( std::basic_string_view>{ result.codepoint}); - return result.iterator; + return advance_code_point_into(range, result); } }; @@ -5826,6 +5835,16 @@ auto skip_ws_before_if_required(bool is_required, Range range) return unexpected(e); } + if constexpr (std::is_same_v< + ranges::const_iterator_t, + typename detail::basic_scan_buffer< + detail::char_t>::forward_iterator>) { + auto beg = range.begin(); + if (beg.stores_parent()) { + beg.parent()->set_skip_whitespace(is_required); + } + } + if (!is_required) { return range.begin(); } diff --git a/tests/unittests/examples_test_runner.py b/tests/unittests/examples_test_runner.py index c72d030d..779cb10c 100755 --- a/tests/unittests/examples_test_runner.py +++ b/tests/unittests/examples_test_runner.py @@ -82,5 +82,5 @@ def check(i, input, expected_output): check(4, "", "[{1: 2, 3: 4}, {5: 6}]") check(5, "123 456", "Write two integers:\nTwo integers: 123 456\n") -check(5, "123 abc", "Write two integers:\nFirst integer: 123, rest of the line: abc") +check(5, "123 abc", "Write two integers:\nFirst integer: 123, rest of the line: abc") check(5, "abc def", "Write two integers:\nEntire line: abc def") diff --git a/tests/unittests/impl_tests/read_algorithms_test.cpp b/tests/unittests/impl_tests/read_algorithms_test.cpp index 1b56a691..954f66a9 100644 --- a/tests/unittests/impl_tests/read_algorithms_test.cpp +++ b/tests/unittests/impl_tests/read_algorithms_test.cpp @@ -132,29 +132,33 @@ TEST(ReadExactlyNCodeUnitsTest, ReadMoreNonContiguous) TEST(ReadCodePointIntoTest, SingleCodeUnitCodePointFromContiguous) { auto src = "ab"sv; - auto [it, cp] = scn::impl::read_code_point_into(src); + auto [it, len, cp] = scn::impl::read_code_point_into(src); EXPECT_EQ(it, src.begin() + 1); + EXPECT_EQ(len, 1); EXPECT_EQ(cp, "a"); } TEST(ReadCodePointIntoTest, SingleCodeUnitCodePointFromNonContiguous) { auto src = make_non_contiguous_buffer_range("ab"); - auto [it, cp] = scn::impl::read_code_point_into(src); + auto [it, len, cp] = scn::impl::read_code_point_into(src); EXPECT_EQ(it, scn::ranges::next(src.begin())); + EXPECT_EQ(len, 1); EXPECT_EQ(cp, "a"sv); } TEST(ReadCodePointIntoTest, MultipleCodeUnitCodePointFromContiguous) { auto src = "äö"sv; - auto [it, cp] = scn::impl::read_code_point_into(src); + auto [it, len, cp] = scn::impl::read_code_point_into(src); EXPECT_EQ(it, src.begin() + 2); + EXPECT_EQ(len, 2); EXPECT_EQ(cp, "ä"sv); } TEST(ReadCodePointIntoTest, MultipleCodeUnitCodePointFromNonContiguous) { auto src = make_non_contiguous_buffer_range("äö"); - auto [it, cp] = scn::impl::read_code_point_into(src); + auto [it, len, cp] = scn::impl::read_code_point_into(src); EXPECT_EQ(it, scn::ranges::next(src.begin(), 2)); + EXPECT_EQ(len, 2); EXPECT_EQ(cp, "ä"sv); }