diff --git a/oxenmq/bt_producer.h b/oxenmq/bt_producer.h index 91f2ec3..5222cde 100644 --- a/oxenmq/bt_producer.h +++ b/oxenmq/bt_producer.h @@ -14,6 +14,39 @@ using namespace std::literals; class bt_dict_producer; +#if defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define OXENMQ_APPLE_TO_CHARS_WORKAROUND +/// Really simplistic version of std::to_chars on Apple, because Apple doesn't allow `std::to_chars` +/// to be used if targetting anything before macOS 10.15. The buffer must have at least 20 chars of +/// space (for int types up to 64-bit); we return a pointer one past the last char written. +template +char* apple_to_chars10(char* buf, IntType val) { + static_assert(std::is_integral_v && sizeof(IntType) <= 64); + if constexpr (std::is_signed_v) { + if (val < 0) { + buf[0] = '-'; + return apple_to_chars10(buf+1, static_cast>(-val)); + } + } + + // write it to the buffer in reverse (because we don't know how many chars we'll need yet, but + // writing in reverse will figure that out). + char* pos = buf; + do { + *pos++ = '0' + static_cast(val % 10); + val /= 10; + } while (val > 0); + + // Reverse the digits into the right order + int swaps = (pos - buf) / 2; + for (int i = 0; i < swaps; i++) + std::swap(buf[i], pos[-1 - i]); + + return pos; +} +#endif + + /// Class that allows you to build a bt-encoded list manually, without copying or allocating memory. /// This is essentially the reverse of bt_list_consumer: where it lets you stream-parse a buffer, /// this class lets you build directly into a buffer that you own. @@ -62,9 +95,15 @@ class bt_list_producer { template char* write_integer(IntType val, char* buf) { static_assert(sizeof(IntType) <= 64); + +#ifndef OXENMQ_APPLE_TO_CHARS_WORKAROUND auto [ptr, ec] = std::to_chars(buf, buf+20, val); assert(ec == std::errc()); return ptr; +#else + // Hate apple. + return apple_to_chars10(buf, val); +#endif } // Serializes an integer value and appends it to the output buffer. Does not call diff --git a/tests/test_bt.cpp b/tests/test_bt.cpp index 0977f17..d9dd019 100644 --- a/tests/test_bt.cpp +++ b/tests/test_bt.cpp @@ -289,3 +289,34 @@ TEST_CASE("bt allocation-free producer", "[bt][dict][list][producer]") { CHECK( lp.view() == "l3:abci42ei1ei17ei-999eli0e0:elll3:omgeeed3:foo3:bar1:gi42e1:hld1:ad1:Ali999eeeeeee" ); } } + +#ifdef OXENMQ_APPLE_TO_CHARS_WORKAROUND +TEST_CASE("apple to_chars workaround test", "[bt][apple][sucks]") { + char buf[20]; + auto buf_view = [&](char* end) { return std::string_view{buf, static_cast(end - buf)}; }; + CHECK( buf_view(oxenmq::apple_to_chars10(buf, 0)) == "0" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, 1)) == "1" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, 2)) == "2" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, 10)) == "10" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, 42)) == "42" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, 99)) == "99" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, 1234567890)) == "1234567890" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, -1)) == "-1" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, -2)) == "-2" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, -10)) == "-10" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, -99)) == "-99" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, -1234567890)) == "-1234567890" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, char{42})) == "42" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, (unsigned char){42})) == "42" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, short{42})) == "42" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, std::numeric_limits::min())) == "-128" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, std::numeric_limits::max())) == "127" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, (unsigned char){42})) == "42" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, std::numeric_limits::max())) == "18446744073709551615" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, int64_t{-1})) == "-1" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, std::numeric_limits::min())) == "-9223372036854775808" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, int64_t{-9223372036854775807})) == "-9223372036854775807" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, int64_t{9223372036854775807})) == "9223372036854775807" ); + CHECK( buf_view(oxenmq::apple_to_chars10(buf, int64_t{9223372036854775806})) == "9223372036854775806" ); +} +#endif