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/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); 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/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..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; } @@ -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..2aa8d127 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 @@ -3341,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()); @@ -3778,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; } } @@ -3817,7 +3821,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 @@ -4156,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; @@ -4214,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; @@ -4410,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; @@ -4436,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 @@ -4884,7 +4902,7 @@ struct stdio_file_interface_impl void lock() {} void unlock() {} - bool has_buffering() const + static constexpr bool has_buffering() { return false; } @@ -4933,7 +4951,7 @@ struct posix_stdio_file_interface : stdio_file_interface_base { funlockfile(this->file); } - static bool has_buffering() + static constexpr bool has_buffering() { return true; } @@ -5043,7 +5061,7 @@ struct stdio_file_interface_impl _unlock_file(this->file); } - static bool has_buffering() + static constexpr bool has_buffering() { return false; } @@ -5086,7 +5104,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 +5127,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 { @@ -5318,7 +5342,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{}; @@ -5556,7 +5581,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)} @@ -5768,7 +5793,6 @@ constexpr size_t encode_types() (encode_types_impl() << packed_arg_bits); } else { - SCN_EXPECT(false); SCN_UNREACHABLE; } } @@ -6992,7 +7016,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 @@ -7242,7 +7271,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; @@ -8777,7 +8811,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 +9016,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 +9185,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 +9371,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 +9536,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..d8be4338 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 @@ -721,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; } }; @@ -1815,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) @@ -1829,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"); @@ -1854,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 8a9acd94..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( @@ -1211,21 +1206,15 @@ 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); - for (auto it = segment.rbegin(); it != segment.rend(); ++it) { - if (!m_file.putback(*it)) { - return false; - } - } - return true; + return segment.empty(); } } // namespace detail @@ -1923,13 +1912,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 @@ -1982,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 @@ -1991,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> { @@ -2002,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 @@ -2159,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}); @@ -2168,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; @@ -2243,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() != @@ -2253,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); } @@ -2268,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 @@ -2287,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) { @@ -2349,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; @@ -2369,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()); } } @@ -3453,21 +3461,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 +3688,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: @@ -5425,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; } @@ -5604,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"); @@ -5612,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); } }; @@ -5825,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(); } @@ -5910,9 +5930,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); @@ -6228,11 +6260,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); 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/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) { 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); }