// Native runtime - String conversion and formatting utilities // Part of the AC6 Recompilation native foundation #pragma once #include #include #include #include #include #include #include #include #include #include #include // TODO(gibbed): Clang and GCC don't have std::from_chars for floating point(!) // despite it being part of the C++17 standard. Check this in the future to see // if it's been resolved. #if REX_LACKS_FLOAT_FROM_CHARS #include #endif namespace rex::string { enum class CopySafety { IDontKnowWhatIAmDoing, IKnowWhatIAmDoing, }; inline size_t util_copy_truncating(char* dest, const std::string_view source, size_t dest_buffer_count) { if (!dest_buffer_count) { return 0; } size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1)); std::memcpy(dest, source.data(), chars_copied); dest[chars_copied] = '\0'; return chars_copied; } inline size_t util_copy_truncating(char16_t* dest, const std::u16string_view source, size_t dest_buffer_count) { if (!dest_buffer_count) { return 0; } size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1)); std::memcpy(dest, source.data(), chars_copied * sizeof(char16_t)); dest[chars_copied] = u'\0'; return chars_copied; } inline size_t util_copy_and_swap_truncating(char16_t* dest, const std::u16string_view source, size_t dest_buffer_count) { if (!dest_buffer_count) { return 0; } size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1)); memory::copy_and_swap(dest, source.data(), chars_copied); dest[chars_copied] = u'\0'; return chars_copied; } template inline size_t util_copy_maybe_truncating(char* dest, const std::string_view source, size_t dest_buffer_count) { static_assert(safety == CopySafety::IKnowWhatIAmDoing); if (!dest_buffer_count) { return 0; } size_t chars_copied = std::min(source.size(), dest_buffer_count); std::memcpy(dest, source.data(), chars_copied); return chars_copied; } template inline size_t util_copy_maybe_truncating(char16_t* dest, const std::u16string_view source, size_t dest_buffer_count) { static_assert(safety == CopySafety::IKnowWhatIAmDoing); if (!dest_buffer_count) { return 0; } size_t chars_copied = std::min(source.size(), dest_buffer_count); std::memcpy(dest, source.data(), chars_copied * sizeof(char16_t)); return chars_copied; } template inline size_t util_copy_and_swap_maybe_truncating(char16_t* dest, const std::u16string_view source, size_t dest_buffer_count) { static_assert(safety == CopySafety::IKnowWhatIAmDoing); if (!dest_buffer_count) { return 0; } size_t chars_copied = std::min(source.size(), dest_buffer_count); memory::copy_and_swap(dest, source.data(), chars_copied); return chars_copied; } inline std::string to_hex_string(uint32_t value) { return fmt::format("{:08X}", value); } inline std::string to_hex_string(uint64_t value) { return fmt::format("{:016X}", value); } inline std::string to_hex_string(float value) { static_assert(sizeof(uint32_t) == sizeof(value)); uint32_t pun; std::memcpy(&pun, &value, sizeof(value)); return to_hex_string(pun); } inline std::string to_hex_string(double value) { static_assert(sizeof(uint64_t) == sizeof(value)); uint64_t pun; std::memcpy(&pun, &value, sizeof(value)); return to_hex_string(pun); } inline std::string to_hex_string(const vec128_t& value) { return fmt::format("[{:08X} {:08X} {:08X} {:08X}]", value.u32[0], value.u32[1], value.u32[2], value.u32[3]); } template inline T from_string(const std::string_view value, bool force_hex = false) { (void)value; (void)force_hex; // Missing implementation for converting type T from string throw; } namespace detail { template > inline T make_negative(T value) { if constexpr (std::is_unsigned_v) { value = static_cast(-static_cast(value)); } else { value = -value; } return value; } // integral_from_string template inline T ifs(const std::string_view value, bool force_hex) { int base = 10; std::string_view range = value; bool is_hex = force_hex; bool is_negative = false; if (rex::string::utf8_starts_with(range, "-")) { is_negative = true; range = range.substr(1); } if (rex::string::utf8_starts_with(range, "0x")) { is_hex = true; range = range.substr(2); } if (rex::string::utf8_ends_with(range, "h")) { is_hex = true; range = range.substr(0, range.length() - 1); } T result; if (is_hex) { base = 16; } auto [p, error] = std::from_chars(range.data(), range.data() + range.size(), result, base); if (error != std::errc()) { assert_always(); return T(); } if (is_negative) { result = make_negative(result); } return result; } // floating_point_from_string template inline T fpfs(const std::string_view value, bool force_hex) { static_assert(sizeof(T) == sizeof(PUN)); std::string_view range = value; bool is_hex = force_hex; bool is_negative = false; if (rex::string::utf8_starts_with(range, "-")) { is_negative = true; range = range.substr(1); } if (rex::string::utf8_starts_with(range, "0x")) { is_hex = true; range = range.substr(2); } if (rex::string::utf8_ends_with(range, "h")) { is_hex = true; range = range.substr(0, range.length() - 1); } T result; if (is_hex) { PUN pun = from_string(range, true); if (is_negative) { pun = make_negative(pun); } std::memcpy(&result, &pun, sizeof(PUN)); } else { #if REX_LACKS_FLOAT_FROM_CHARS auto temp = std::string(range); result = std::strtod(temp.c_str(), nullptr); #else auto [p, error] = std::from_chars(range.data(), range.data() + range.size(), result, std::chars_format::general); if (error != std::errc()) { assert_always(); return T(); } #endif if (is_negative) { result = -result; } } return result; } } // namespace detail template <> inline bool from_string(const std::string_view value, bool force_hex) { (void)force_hex; return value == "true" || value == "1"; } template <> inline int8_t from_string(const std::string_view value, bool force_hex) { return detail::ifs(value, force_hex); } template <> inline uint8_t from_string(const std::string_view value, bool force_hex) { return detail::ifs(value, force_hex); } template <> inline int16_t from_string(const std::string_view value, bool force_hex) { return detail::ifs(value, force_hex); } template <> inline uint16_t from_string(const std::string_view value, bool force_hex) { return detail::ifs(value, force_hex); } template <> inline int32_t from_string(const std::string_view value, bool force_hex) { return detail::ifs(value, force_hex); } template <> inline uint32_t from_string(const std::string_view value, bool force_hex) { return detail::ifs(value, force_hex); } template <> inline int64_t from_string(const std::string_view value, bool force_hex) { return detail::ifs(value, force_hex); } template <> inline uint64_t from_string(const std::string_view value, bool force_hex) { return detail::ifs(value, force_hex); } template <> inline float from_string(const std::string_view value, bool force_hex) { return detail::fpfs(value, force_hex); } template <> inline double from_string(const std::string_view value, bool force_hex) { return detail::fpfs(value, force_hex); } template <> inline vec128_t from_string(const std::string_view value, bool force_hex) { if (!value.size()) { return vec128_t(); } vec128_t v; #if REX_LACKS_FLOAT_FROM_CHARS auto temp = std::string(value); auto p = temp.c_str(); auto end = temp.c_str() + temp.size(); #else auto p = value.data(); auto end = value.data() + value.size(); #endif bool is_hex = force_hex; if (p != end && *p == '[') { is_hex = true; ++p; } else if (p != end && *p == '(') { is_hex = false; ++p; } else { // Assume hex? is_hex = true; } if (p == end) { assert_always(); return vec128_t(); } if (is_hex) { for (size_t i = 0; i < 4; i++) { while (p != end && (*p == ' ' || *p == ',')) { ++p; } if (p == end) { assert_always(); return vec128_t(); } auto result = std::from_chars(p, end, v.u32[i], 16); if (result.ec != std::errc()) { assert_always(); return vec128_t(); } p = result.ptr; } } else { for (size_t i = 0; i < 4; i++) { while (p != end && (*p == ' ' || *p == ',')) { ++p; } if (p == end) { assert_always(); return vec128_t(); } #if REX_LACKS_FLOAT_FROM_CHARS char* next_p; v.f32[i] = std::strtof(p, &next_p); p = next_p; #else auto result = std::from_chars(p, end, v.f32[i], std::chars_format::general); if (result.ec != std::errc()) { assert_always(); return vec128_t(); } p = result.ptr; #endif } } return v; } } // namespace rex::string