#include "prx/libSceIcu/libSceIcu.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include #include #include namespace { void Require(bool condition, const char* message) { if (condition) return; std::fprintf(stderr, "ICU comparisons: %s\n", message); std::abort(); } int Sign(std::int32_t value) { return (value > 0) - (value < 0); } struct Points { std::array values{}; std::size_t size = 0; }; Points Decode(const char16_t* units, std::size_t count) { Points points; for (std::size_t index = 0; index < count; ++index) { const auto current = static_cast(units[index]); if (current >= 0xd800 && current <= 0xdbff && index + 1 < count && units[index + 1] >= 0xdc00 && units[index + 1] <= 0xdfff) { points.values[points.size++] = 0x10000 + (current - 0xd800) * 0x400 + units[++index] - 0xdc00; } else points.values[points.size++] = current; } return points; } int ComparePoints(const char16_t* first, std::size_t firstCount, const char16_t* second, std::size_t secondCount) { const auto left = Decode(first, firstCount); const auto right = Decode(second, secondCount); const auto mismatch = std::mismatch(left.values.begin(), left.values.begin() + left.size, right.values.begin(), right.values.begin() + right.size); if (mismatch.first == left.values.begin() + left.size) return mismatch.second == right.values.begin() + right.size ? 0 : -1; if (mismatch.second == right.values.begin() + right.size) return 1; return *mismatch.first < *mismatch.second ? -1 : 1; } std::int32_t CompareUnits(const char16_t* first, const char16_t* second, std::size_t count) { const auto different = std::mismatch(first, first + count, second); return different.first == first + count ? 0 : static_cast(*different.first) - *different.second; } void Exhaustive() { constexpr std::array alphabet{0, u'a', 0xd7ff, 0xd800, 0xdbff, 0xdc00, 0xdfff, 0xe000, 0xffff}; std::vector> strings; for (auto first : alphabet) for (auto second : alphabet) for (auto third : alphabet) strings.push_back({first, second, third, 0}); const auto saved = strings; for (const auto& first : strings) { const auto length = std::char_traits::length(first.data()); Require(u_strlen_nid_postfix(first.data()) == static_cast(length), "length counts code units through first NUL"); for (const auto& second : strings) { const auto secondLength = std::char_traits::length(second.data()); const auto terminatedCount = std::min(length, secondLength) + 1; const auto terminatedDifference = CompareUnits(first.data(), second.data(), terminatedCount); Require(u_strcmp_nid_postfix(first.data(), second.data()) == terminatedDifference, "exact terminated code-unit difference"); for (std::int32_t count = 0; count <= 3; ++count) { const auto expected = CompareUnits(first.data(), second.data(), count); Require(u_memcmp_nid_postfix(first.data(), second.data(), count) == expected, "exact binary difference including NUL"); Require(u_strncmp_nid_postfix(first.data(), second.data(), count) == CompareUnits(first.data(), second.data(), std::min(count, terminatedCount)), "bounded string stops at NUL or bound"); Require(Sign(u_memcmpCodePointOrder_nid_postfix(first.data(), second.data(), count)) == ComparePoints(first.data(), count, second.data(), count), "scalar-value ordering with paired and unpaired surrogates"); } Require(Sign(u_memcmpCodePointOrder_nid_postfix(first.data(), second.data(), -1)) == ComparePoints(first.data(), length, second.data(), secondLength), "negative code-point count uses string lengths"); } } Require(strings == saved, "all inputs remain unchanged"); } void Boundaries() { const std::array supplementary{0xd800, 0xdc00, 0}; const std::array bmp{0xe000, 0, 0}; Require(u_strlen_nid_postfix(supplementary.data()) == 2, "supplementary character occupies two code units"); Require(u_memcmpCodePointOrder_nid_postfix(supplementary.data(), bmp.data(), 1) == -0x800, "boundary excludes trailing surrogate"); Require(u_memcmpCodePointOrder_nid_postfix(supplementary.data(), bmp.data(), 2) == 0x2000, "paired lead sorts after upper BMP with exact difference"); const std::array longer{u'a', u'b', 0}; const std::array shorter{u'a', 0, 0}; Require(u_memcmpCodePointOrder_nid_postfix(shorter.data(), longer.data(), -1) == -98, "negative length compares the first terminator exactly"); Require(u_memcmpCodePointOrder_nid_postfix(longer.data(), shorter.data(), std::numeric_limits::min()) == 98, "all negative counts select terminated lengths"); Require(u_strncmp_nid_postfix(u"a", u"b", std::numeric_limits::max()) == -1, "large bound stops at mismatch"); Require(u_strncmp_nid_postfix(u"", u"", std::numeric_limits::max()) == 0, "large bound stops at terminator"); for (const auto count : {0, -1, std::numeric_limits::min()}) { Require(u_memcmp_nid_postfix(nullptr, nullptr, count) == 0, "nonpositive binary count reads nothing"); Require(u_strncmp_nid_postfix(nullptr, nullptr, count) == 0, "nonpositive string bound reads nothing"); } for (const auto count : {0, -1, 1, std::numeric_limits::min(), std::numeric_limits::max()}) Require(u_memcmpCodePointOrder_nid_postfix(nullptr, nullptr, count) == 0, "identical code-point buffers read nothing for every count"); int rejected = 0; try { u_strlen_nid_postfix(nullptr); } catch (const std::invalid_argument&) { ++rejected; } try { u_strcmp_nid_postfix(nullptr, u""); } catch (const std::invalid_argument&) { ++rejected; } try { u_strncmp_nid_postfix(u"", nullptr, 1); } catch (const std::invalid_argument&) { ++rejected; } try { u_memcmp_nid_postfix(nullptr, u"", 1); } catch (const std::invalid_argument&) { ++rejected; } try { u_memcmpCodePointOrder_nid_postfix(u"", nullptr, -1); } catch (const std::invalid_argument&) { ++rejected; } Require(rejected == 5, "invalid required buffers throw"); } } // namespace int main() { Exhaustive(); Boundaries(); }