diff --git a/include/boost/uuid/detail/from_chars.hpp b/include/boost/uuid/detail/from_chars.hpp new file mode 100644 index 0000000..c24e851 --- /dev/null +++ b/include/boost/uuid/detail/from_chars.hpp @@ -0,0 +1,150 @@ +#ifndef BOOST_UUID_DETAIL_FROM_CHARS_HPP_INCLUDED +#define BOOST_UUID_DETAIL_FROM_CHARS_HPP_INCLUDED + +// Copyright 2025 Peter Dimov +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +namespace boost { +namespace uuids { +namespace detail { + +constexpr char const* from_chars_digits( char const* ) noexcept +{ + return "0123456789ABCDEFabcdef-"; +} + +constexpr wchar_t const* from_chars_digits( wchar_t const* ) noexcept +{ + return L"0123456789ABCDEFabcdef-"; +} + +constexpr char16_t const* from_chars_digits( char16_t const* ) noexcept +{ + return u"0123456789ABCDEFabcdef-"; +} + +constexpr char32_t const* from_chars_digits( char32_t const* ) noexcept +{ + return U"0123456789ABCDEFabcdef-"; +} + +#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L + +constexpr char8_t const* from_chars_digits( char8_t const* ) noexcept +{ + return u8"0123456789ABCDEFabcdef-"; +} + +#endif + +template +BOOST_CXX14_CONSTEXPR inline +unsigned char from_chars_digit_value( Ch ch ) +{ + constexpr Ch const* digits = detail::from_chars_digits( static_cast( nullptr ) ); + + std::size_t i = 0; + + for( ; i < 16; ++i ) + { + if( digits[ i ] == ch ) return static_cast< unsigned char >( i ); + } + + for( ; i < 22; ++i ) + { + if( digits[ i ] == ch ) return static_cast< unsigned char >( i - 6 ); + } + + return 255; +} + +template +BOOST_CXX14_CONSTEXPR inline +unsigned char from_chars_is_dash( Ch ch ) +{ + constexpr Ch const* digits = detail::from_chars_digits( static_cast( nullptr ) ); + + return ch == digits[ 22 ]; +} + +} // namespace detail + +enum class from_chars_error +{ + none = 0, + + unexpected_end_of_input, + hex_digit_expected, + dash_expected +}; + +template struct from_chars_result +{ + Ch const* ptr; + from_chars_error ec; +}; + +template +BOOST_CXX14_CONSTEXPR inline +from_chars_result from_chars( Ch const* first, Ch const* last, uuid& u ) noexcept +{ + u = {}; + + for( std::size_t i = 0; i < 16; ++i ) + { + if( first == last ) + { + return { first, from_chars_error::unexpected_end_of_input }; + } + + unsigned char v1 = detail::from_chars_digit_value( *first ); + + if( v1 == 255 ) + { + return { first, from_chars_error::hex_digit_expected }; + } + + ++first; + + if( first == last ) + { + return { first, from_chars_error::unexpected_end_of_input }; + } + + unsigned char v2 = detail::from_chars_digit_value( *first ); + + if( v2 == 255 ) + { + return { first, from_chars_error::hex_digit_expected }; + } + + ++first; + + u.data()[ i ] = static_cast( ( v1 << 4 ) + v2 ); + + if( i == 3 || i == 5 || i == 7 || i == 9 ) + { + if( first == last ) + { + return { first, from_chars_error::unexpected_end_of_input }; + } + + if( !detail::from_chars_is_dash( *first ) ) + { + return { first, from_chars_error::dash_expected }; + } + + ++first; + } + } + + return { first, from_chars_error::none }; +} + +}} //namespace boost::uuids + +#endif // BOOST_UUID_DETAIL_TO_CHARS_HPP_INCLUDED diff --git a/include/boost/uuid/uuid_io.hpp b/include/boost/uuid/uuid_io.hpp index bf1ba84..f70c355 100644 --- a/include/boost/uuid/uuid_io.hpp +++ b/include/boost/uuid/uuid_io.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 0e8f553..821d1ba 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -91,9 +91,13 @@ run test_comparison.cpp : : : BOOST_UUID_NO_SIMD BOOST_UUID_REPO run test_io.cpp : : : /boost/lexical_cast//boost_lexical_cast /boost/predef//boost_predef -$(WERROR) ; run test_io_2.cpp ; + run test_to_chars.cpp ; run test_to_chars_2.cpp ; +run test_from_chars.cpp ; +run test_from_chars_2.cpp ; + # test uuid_clock run test_uuid_clock.cpp ; diff --git a/test/test_from_chars.cpp b/test/test_from_chars.cpp new file mode 100644 index 0000000..b7b2812 --- /dev/null +++ b/test/test_from_chars.cpp @@ -0,0 +1,86 @@ +// Copyright 2025 Peter Dimov +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include + +using namespace boost::uuids; + +template void test( uuid const& expected, Ch const* str ) +{ + Ch const* first = str; + Ch const* last = first + std::char_traits::length( first ); + + uuid u; + + from_chars_result r = from_chars( first, last, u ); + + BOOST_TEST_EQ( r.ptr, first + 36 ); + + BOOST_TEST( r.ec == from_chars_error::none ); + BOOST_TEST_EQ( static_cast( r.ec ), 0 ); + + BOOST_TEST_EQ( u, expected ); +} + +int main() +{ + uuid const u1 = {{ 0 }}; + + test( u1, "00000000-0000-0000-0000-000000000000" ); + test( u1, L"00000000-0000-0000-0000-000000000000" ); + test( u1, u"00000000-0000-0000-0000-000000000000" ); + test( u1, U"00000000-0000-0000-0000-000000000000" ); + test( u1, u8"00000000-0000-0000-0000-000000000000" ); + + test( u1, "00000000-0000-0000-0000-000000000000-0000" ); + test( u1, L"00000000-0000-0000-0000-000000000000-0000" ); + test( u1, u"00000000-0000-0000-0000-000000000000-0000" ); + test( u1, U"00000000-0000-0000-0000-000000000000-0000" ); + test( u1, u8"00000000-0000-0000-0000-000000000000-0000" ); + + uuid const u2 = {{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }}; + + test( u2, "00010203-0405-0607-0809-0a0b0c0d0e0f" ); + test( u2, L"00010203-0405-0607-0809-0a0b0c0d0e0f" ); + test( u2, u"00010203-0405-0607-0809-0a0b0c0d0e0f" ); + test( u2, U"00010203-0405-0607-0809-0a0b0c0d0e0f" ); + test( u2, u8"00010203-0405-0607-0809-0a0b0c0d0e0f" ); + + test( u2, "00010203-0405-0607-0809-0a0b0c0d0e0f1011" ); + test( u2, L"00010203-0405-0607-0809-0a0b0c0d0e0f1011" ); + test( u2, u"00010203-0405-0607-0809-0a0b0c0d0e0f1011" ); + test( u2, U"00010203-0405-0607-0809-0a0b0c0d0e0f1011" ); + test( u2, u8"00010203-0405-0607-0809-0a0b0c0d0e0f1011" ); + + test( u2, "00010203-0405-0607-0809-0A0B0C0D0E0F" ); + test( u2, L"00010203-0405-0607-0809-0A0B0C0D0E0F" ); + test( u2, u"00010203-0405-0607-0809-0A0B0C0D0E0F" ); + test( u2, U"00010203-0405-0607-0809-0A0B0C0D0E0F" ); + test( u2, u8"00010203-0405-0607-0809-0A0B0C0D0E0F" ); + + uuid const u3 = {{ 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef }}; + + test( u3, "12345678-90ab-cdef-1234-567890abcdef" ); + test( u3, L"12345678-90ab-cdef-1234-567890abcdef" ); + test( u3, u"12345678-90ab-cdef-1234-567890abcdef" ); + test( u3, U"12345678-90ab-cdef-1234-567890abcdef" ); + test( u3, u8"12345678-90ab-cdef-1234-567890abcdef" ); + + test( u3, "12345678-90AB-CDEF-1234-567890abcdef" ); + test( u3, L"12345678-90AB-CDEF-1234-567890abcdef" ); + test( u3, u"12345678-90AB-CDEF-1234-567890abcdef" ); + test( u3, U"12345678-90AB-CDEF-1234-567890abcdef" ); + test( u3, u8"12345678-90AB-CDEF-1234-567890abcdef" ); + + test( u3, "12345678-90AB-CDEF-1234-567890abcdefghij" ); + test( u3, L"12345678-90AB-CDEF-1234-567890abcdefghij" ); + test( u3, u"12345678-90AB-CDEF-1234-567890abcdefghij" ); + test( u3, U"12345678-90AB-CDEF-1234-567890abcdefghij" ); + test( u3, u8"12345678-90AB-CDEF-1234-567890abcdefghij" ); + + return boost::report_errors(); +} diff --git a/test/test_from_chars_2.cpp b/test/test_from_chars_2.cpp new file mode 100644 index 0000000..29a745a --- /dev/null +++ b/test/test_from_chars_2.cpp @@ -0,0 +1,96 @@ +// Copyright 2025 Peter Dimov +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include + +using namespace boost::uuids; + +template void test( Ch const* str, int pos, from_chars_error err ) +{ + Ch const* first = str; + Ch const* last = first + std::char_traits::length( first ); + + uuid u; + + from_chars_result r = from_chars( first, last, u ); + + BOOST_TEST_EQ( r.ptr, first + pos ); + + BOOST_TEST( r.ec == err ); + BOOST_TEST_EQ( static_cast( r.ec ), static_cast( err ) ); +} + +int main() +{ + test( "", 0, from_chars_error::unexpected_end_of_input ); + test( L"", 0, from_chars_error::unexpected_end_of_input ); + test( u"", 0, from_chars_error::unexpected_end_of_input ); + test( U"", 0, from_chars_error::unexpected_end_of_input ); + test( u8"", 0, from_chars_error::unexpected_end_of_input ); + + test( "@", 0, from_chars_error::hex_digit_expected ); + test( L"@", 0, from_chars_error::hex_digit_expected ); + test( u"@", 0, from_chars_error::hex_digit_expected ); + test( U"@", 0, from_chars_error::hex_digit_expected ); + test( u8"@", 0, from_chars_error::hex_digit_expected ); + + test( "G", 0, from_chars_error::hex_digit_expected ); + test( L"G", 0, from_chars_error::hex_digit_expected ); + test( u"G", 0, from_chars_error::hex_digit_expected ); + test( U"G", 0, from_chars_error::hex_digit_expected ); + test( u8"G", 0, from_chars_error::hex_digit_expected ); + + test( "g", 0, from_chars_error::hex_digit_expected ); + test( L"g", 0, from_chars_error::hex_digit_expected ); + test( u"g", 0, from_chars_error::hex_digit_expected ); + test( U"g", 0, from_chars_error::hex_digit_expected ); + test( u8"g", 0, from_chars_error::hex_digit_expected ); + + test( "-", 0, from_chars_error::hex_digit_expected ); + test( L"-", 0, from_chars_error::hex_digit_expected ); + test( u"-", 0, from_chars_error::hex_digit_expected ); + test( U"-", 0, from_chars_error::hex_digit_expected ); + test( u8"-", 0, from_chars_error::hex_digit_expected ); + + test( "abcdefgh-0000-0000-0000-000000000000", 6, from_chars_error::hex_digit_expected ); + test( L"abcdefgh-0000-0000-0000-000000000000", 6, from_chars_error::hex_digit_expected ); + test( u"abcdefgh-0000-0000-0000-000000000000", 6, from_chars_error::hex_digit_expected ); + test( U"abcdefgh-0000-0000-0000-000000000000", 6, from_chars_error::hex_digit_expected ); + test( u8"abcdefgh-0000-0000-0000-000000000000", 6, from_chars_error::hex_digit_expected ); + + test( "Have a nice day", 0, from_chars_error::hex_digit_expected ); + test( L"Have a nice day", 0, from_chars_error::hex_digit_expected ); + test( u"Have a nice day", 0, from_chars_error::hex_digit_expected ); + test( U"Have a nice day", 0, from_chars_error::hex_digit_expected ); + test( u8"Have a nice day", 0, from_chars_error::hex_digit_expected ); + + test( "0000000000000-0000-0000-000000000000", 8, from_chars_error::dash_expected ); + test( L"0000000000000-0000-0000-000000000000", 8, from_chars_error::dash_expected ); + test( u"0000000000000-0000-0000-000000000000", 8, from_chars_error::dash_expected ); + test( U"0000000000000-0000-0000-000000000000", 8, from_chars_error::dash_expected ); + test( u8"0000000000000-0000-0000-000000000000", 8, from_chars_error::dash_expected ); + + test( "00000000-000000000-0000-000000000000", 13, from_chars_error::dash_expected ); + test( L"00000000-000000000-0000-000000000000", 13, from_chars_error::dash_expected ); + test( u"00000000-000000000-0000-000000000000", 13, from_chars_error::dash_expected ); + test( U"00000000-000000000-0000-000000000000", 13, from_chars_error::dash_expected ); + test( u8"00000000-000000000-0000-000000000000", 13, from_chars_error::dash_expected ); + + test( "00000000-0000-000000000-000000000000", 18, from_chars_error::dash_expected ); + test( L"00000000-0000-000000000-000000000000", 18, from_chars_error::dash_expected ); + test( u"00000000-0000-000000000-000000000000", 18, from_chars_error::dash_expected ); + test( U"00000000-0000-000000000-000000000000", 18, from_chars_error::dash_expected ); + test( u8"00000000-0000-000000000-000000000000", 18, from_chars_error::dash_expected ); + + test( "00000000-0000-0000-00000000000000000", 23, from_chars_error::dash_expected ); + test( L"00000000-0000-0000-00000000000000000", 23, from_chars_error::dash_expected ); + test( u"00000000-0000-0000-00000000000000000", 23, from_chars_error::dash_expected ); + test( U"00000000-0000-0000-00000000000000000", 23, from_chars_error::dash_expected ); + test( u8"00000000-0000-0000-00000000000000000", 23, from_chars_error::dash_expected ); + + return boost::report_errors(); +}