/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2020 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #ifndef XENIA_BASE_STRING_UTIL_H_ #define XENIA_BASE_STRING_UTIL_H_ #include #include #include #include #include #include #include #include "third_party/fmt/include/fmt/format.h" #include "xenia/base/assert.h" #include "xenia/base/memory.h" #include "xenia/base/platform.h" #include "xenia/base/string.h" #include "xenia/base/vec128.h" // 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 XE_COMPILER_CLANG || XE_COMPILER_GNUC #include #endif namespace xe { namespace string_util { enum class Safety { IDontKnowWhatIAmDoing, IKnowWhatIAmDoing, }; inline size_t 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 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 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)); xe::copy_and_swap(dest, source.data(), chars_copied); dest[chars_copied] = u'\0'; return chars_copied; } template inline size_t copy_maybe_truncating(char* dest, const std::string_view source, size_t dest_buffer_count) { static_assert(safety == Safety::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 copy_maybe_truncating(char16_t* dest, const std::u16string_view source, size_t dest_buffer_count) { static_assert(safety == Safety::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 copy_and_swap_maybe_truncating(char16_t* dest, const std::u16string_view source, size_t dest_buffer_count) { static_assert(safety == Safety::IKnowWhatIAmDoing); if (!dest_buffer_count) { return 0; } size_t chars_copied = std::min(source.size(), dest_buffer_count); xe::copy_and_swap(dest, source.data(), chars_copied); return chars_copied; } inline bool hex_string_to_array(std::vector& output_array, std::string_view value) { if (value.rfind("0x", 0) == 0) { value.remove_prefix(2); } output_array.reserve((value.size() + 1) / 2); size_t remaining_length = value.size(); while (remaining_length > 0) { uint8_t chars_to_read = remaining_length > 1 ? 2 : 1; const char* substring_pointer = value.data() + value.size() - remaining_length; uint8_t string_value = 0; std::from_chars_result result = std::from_chars( substring_pointer, substring_pointer + chars_to_read, string_value, 16); if (result.ec != std::errc() || result.ptr != substring_pointer + chars_to_read) { output_array.clear(); return false; } output_array.push_back(string_value); remaining_length -= chars_to_read; } return true; } inline std::string BoolToString(bool value) { return value ? "true" : "false"; } inline std::string ltrim(const std::string& value) { return std::regex_replace(value, std::regex("^\\s+"), std::string("")); } inline std::string rtrim(const std::string& value) { return std::regex_replace(value, std::regex("\\s+$"), std::string("")); } inline std::string trim(const std::string& value) { return ltrim(rtrim(value)); } inline std::string remove_eol(const std::string& value) { std::string result = value; result.erase(std::remove(result.begin(), result.end(), '\n'), result.cend()); return result; } 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) { // Missing implementation for converting type T from string throw; } namespace internal { 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 (utf8::starts_with(range, "-")) { is_negative = true; range = range.substr(1); } if (utf8::starts_with(range, "0x")) { is_hex = true; range = range.substr(2); } if (utf8::ends_with(range, "h")) { is_hex = true; range = range.substr(0, range.length() - 1); } T result; if (is_hex) { base = 16; } // TODO(gibbed): do something more with errors? 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 (utf8::starts_with(range, "-")) { is_negative = true; range = range.substr(1); } if (utf8::starts_with(range, "0x")) { is_hex = true; range = range.substr(2); } if (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 { auto temp = std::string(range); result = static_cast(std::strtod(temp.c_str(), nullptr)); if (is_negative) { result = -result; } } return result; } } // namespace internal template <> inline bool from_string(const std::string_view value, bool force_hex) { return value == "true" || value == "1"; } template <> inline int8_t from_string(const std::string_view value, bool force_hex) { return internal::ifs(value, force_hex); } template <> inline uint8_t from_string(const std::string_view value, bool force_hex) { return internal::ifs(value, force_hex); } template <> inline int16_t from_string(const std::string_view value, bool force_hex) { return internal::ifs(value, force_hex); } template <> inline uint16_t from_string(const std::string_view value, bool force_hex) { return internal::ifs(value, force_hex); } template <> inline int32_t from_string(const std::string_view value, bool force_hex) { return internal::ifs(value, force_hex); } template <> inline uint32_t from_string(const std::string_view value, bool force_hex) { return internal::ifs(value, force_hex); } template <> inline int64_t from_string(const std::string_view value, bool force_hex) { return internal::ifs(value, force_hex); } template <> inline uint64_t from_string(const std::string_view value, bool force_hex) { return internal::ifs(value, force_hex); } template <> inline float from_string(const std::string_view value, bool force_hex) { return internal::fpfs(value, force_hex); } template <> inline double from_string(const std::string_view value, bool force_hex) { return internal::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 XE_COMPILER_CLANG || XE_COMPILER_GNUC 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 XE_COMPILER_CLANG || XE_COMPILER_GNUC 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; } inline size_t size_in_bytes(std::variant string, bool include_terminator = true) { if (std::holds_alternative(string)) { return std::get(string).size() + include_terminator; } else if (std::holds_alternative(string)) { return (std::get(string).size() + include_terminator) * sizeof(char16_t); } else { assert_always(); } return 0; } inline std::u16string read_u16string_and_swap(const char16_t* string_ptr) { std::u16string input_str = std::u16string(string_ptr); std::u16string output_str = {}; output_str.resize(input_str.size() + 1); copy_and_swap_truncating(output_str.data(), input_str, size_in_bytes(input_str, false)); output_str.pop_back(); // Remove nullptr added by copy_and_swap. return output_str; } } // namespace string_util } // namespace xe #endif // XENIA_BASE_STRING_UTIL_H_