From d8ee336161bfadaef6d0de0297402da6c748e071 Mon Sep 17 00:00:00 2001 From: KernelPanicNT Date: Fri, 30 Oct 2026 18:33:51 +0000 Subject: [PATCH 1/3] feat(libSceIcu): implement property and case exports from ICU tables Add ICU 78.1 (release-78.1, 049e0d6a4206) as the 3rdparty/icu submodule and build the icu4c common sources these exports reach as a static library with the compiled-in property, case, bidi, property-name and NFC tables, stub data and file I/O disabled. Implement 49 libSceIcu exports on top of it: character categories and classifiers, numeric, age, combining class, bidi and mirroring queries, property and value names, binary and enumerated property values, simple case folding, string case mapping and case-insensitive comparison. Properties whose ICU data lives in data files (emoji, NFKC, NFKC_Casefold, Indic categories, vertical orientation) and string case mapping in the default locale throw NotImplemented_nid_no_patch. --- .gitmodules | 4 + 3rdparty/icu | 1 + CMakeLists.txt | 1 + cmake/Icu.cmake | 86 ++++++ core/libs/prx/libSceIcu/CMakeLists.txt | 28 ++ core/libs/prx/libSceIcu/CaseMapping.cpp | 94 +++++++ core/libs/prx/libSceIcu/CaseMapping.hpp | 14 + core/libs/prx/libSceIcu/Export.cpp | 245 ------------------ core/libs/prx/libSceIcu/Properties.cpp | 215 +++++++++++++++ core/libs/prx/libSceIcu/Properties.hpp | 51 ++++ core/libs/prx/libSceIcu/libSceIcu.hpp | 2 + core/libs/prx/libSceIcu/tests/CaseMapping.cpp | 138 ++++++++++ core/libs/prx/libSceIcu/tests/Properties.cpp | 206 +++++++++++++++ docs/dev/debt/libSceIcu.md | 4 +- 14 files changed, 843 insertions(+), 246 deletions(-) create mode 160000 3rdparty/icu create mode 100644 cmake/Icu.cmake create mode 100644 core/libs/prx/libSceIcu/CaseMapping.cpp create mode 100644 core/libs/prx/libSceIcu/CaseMapping.hpp create mode 100644 core/libs/prx/libSceIcu/Properties.cpp create mode 100644 core/libs/prx/libSceIcu/Properties.hpp create mode 100644 core/libs/prx/libSceIcu/tests/CaseMapping.cpp create mode 100644 core/libs/prx/libSceIcu/tests/Properties.cpp diff --git a/.gitmodules b/.gitmodules index 219621ad1add..136dc4e0ef4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -67,3 +67,7 @@ [submodule "3rdparty/nghttp2"] path = 3rdparty/nghttp2 url = https://github.com/nghttp2/nghttp2.git +[submodule "3rdparty/icu"] + path = 3rdparty/icu + url = https://github.com/unicode-org/icu.git + shallow = true diff --git a/3rdparty/icu b/3rdparty/icu new file mode 160000 index 000000000000..049e0d6a4206 --- /dev/null +++ b/3rdparty/icu @@ -0,0 +1 @@ +Subproject commit 049e0d6a420629ac7db77256987d083a563287b5 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ca431244f05..c3faf51cba4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -573,6 +573,7 @@ set_target_properties(usb-1.0 PROPERTIES POSITION_INDEPENDENT_CODE ON) include(cmake/ZArchive.cmake) include(cmake/Opus.cmake) +include(cmake/Icu.cmake) if(WIN32 OR APPLE) set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE) set(MI_BUILD_STATIC OFF CACHE BOOL "" FORCE) diff --git a/cmake/Icu.cmake b/cmake/Icu.cmake new file mode 100644 index 000000000000..b558c4d34e63 --- /dev/null +++ b/cmake/Icu.cmake @@ -0,0 +1,86 @@ +set(ICU_SOURCE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/icu/icu4c/source) + +add_library(icu_common STATIC EXCLUDE_FROM_ALL + ${ICU_SOURCE_DIR}/common/appendable.cpp + ${ICU_SOURCE_DIR}/common/bmpset.cpp + ${ICU_SOURCE_DIR}/common/bytesinkutil.cpp + ${ICU_SOURCE_DIR}/common/bytestream.cpp + ${ICU_SOURCE_DIR}/common/bytestrie.cpp + ${ICU_SOURCE_DIR}/common/charstr.cpp + ${ICU_SOURCE_DIR}/common/cmemory.cpp + ${ICU_SOURCE_DIR}/common/cstring.cpp + ${ICU_SOURCE_DIR}/common/edits.cpp + ${ICU_SOURCE_DIR}/common/emojiprops.cpp + ${ICU_SOURCE_DIR}/common/loadednormalizer2impl.cpp + ${ICU_SOURCE_DIR}/common/normalizer2.cpp + ${ICU_SOURCE_DIR}/common/normalizer2impl.cpp + ${ICU_SOURCE_DIR}/common/patternprops.cpp + ${ICU_SOURCE_DIR}/common/propname.cpp + ${ICU_SOURCE_DIR}/common/putil.cpp + ${ICU_SOURCE_DIR}/common/stringpiece.cpp + ${ICU_SOURCE_DIR}/common/uarrsort.cpp + ${ICU_SOURCE_DIR}/common/ubidi_props.cpp + ${ICU_SOURCE_DIR}/common/ucase.cpp + ${ICU_SOURCE_DIR}/common/uchar.cpp + ${ICU_SOURCE_DIR}/common/ucharstrie.cpp + ${ICU_SOURCE_DIR}/common/ucharstrieiterator.cpp + ${ICU_SOURCE_DIR}/common/ucln_cmn.cpp + ${ICU_SOURCE_DIR}/common/ucmndata.cpp + ${ICU_SOURCE_DIR}/common/ucptrie.cpp + ${ICU_SOURCE_DIR}/common/udata.cpp + ${ICU_SOURCE_DIR}/common/udatamem.cpp + ${ICU_SOURCE_DIR}/common/udataswp.cpp + ${ICU_SOURCE_DIR}/common/uhash.cpp + ${ICU_SOURCE_DIR}/common/uinvchar.cpp + ${ICU_SOURCE_DIR}/common/umapfile.cpp + ${ICU_SOURCE_DIR}/common/umath.cpp + ${ICU_SOURCE_DIR}/common/umutablecptrie.cpp + ${ICU_SOURCE_DIR}/common/umutex.cpp + ${ICU_SOURCE_DIR}/common/unifilt.cpp + ${ICU_SOURCE_DIR}/common/unifunct.cpp + ${ICU_SOURCE_DIR}/common/uniset.cpp + ${ICU_SOURCE_DIR}/common/unisetspan.cpp + ${ICU_SOURCE_DIR}/common/unistr.cpp + ${ICU_SOURCE_DIR}/common/unistr_case.cpp + ${ICU_SOURCE_DIR}/common/uobject.cpp + ${ICU_SOURCE_DIR}/common/uprops.cpp + ${ICU_SOURCE_DIR}/common/ustrcase.cpp + ${ICU_SOURCE_DIR}/common/ustring.cpp + ${ICU_SOURCE_DIR}/common/ustrtrns.cpp + ${ICU_SOURCE_DIR}/common/utf_impl.cpp + ${ICU_SOURCE_DIR}/common/util.cpp + ${ICU_SOURCE_DIR}/common/utrace.cpp + ${ICU_SOURCE_DIR}/common/utrie2.cpp + ${ICU_SOURCE_DIR}/common/utrie_swap.cpp + ${ICU_SOURCE_DIR}/common/uvector.cpp + ${ICU_SOURCE_DIR}/common/uvectr32.cpp + ${ICU_SOURCE_DIR}/stubdata/stubdata.cpp) +if(WIN32) + target_sources(icu_common PRIVATE + ${ICU_SOURCE_DIR}/common/fixedstring.cpp + ${ICU_SOURCE_DIR}/common/localebuilder.cpp + ${ICU_SOURCE_DIR}/common/locid.cpp + ${ICU_SOURCE_DIR}/common/loclikely.cpp + ${ICU_SOURCE_DIR}/common/loclikelysubtags.cpp + ${ICU_SOURCE_DIR}/common/locmap.cpp + ${ICU_SOURCE_DIR}/common/lsr.cpp + ${ICU_SOURCE_DIR}/common/resource.cpp + ${ICU_SOURCE_DIR}/common/ucol_swp.cpp + ${ICU_SOURCE_DIR}/common/uenum.cpp + ${ICU_SOURCE_DIR}/common/uloc.cpp + ${ICU_SOURCE_DIR}/common/uloc_keytype.cpp + ${ICU_SOURCE_DIR}/common/uloc_tag.cpp + ${ICU_SOURCE_DIR}/common/uresbund.cpp + ${ICU_SOURCE_DIR}/common/uresdata.cpp + ${ICU_SOURCE_DIR}/common/uscript_props.cpp + ${ICU_SOURCE_DIR}/common/ustrenum.cpp + ${ICU_SOURCE_DIR}/common/utypes.cpp + ${ICU_SOURCE_DIR}/common/wintz.cpp) +endif() +target_include_directories(icu_common SYSTEM PUBLIC ${ICU_SOURCE_DIR}/common) +target_compile_definitions(icu_common PUBLIC U_STATIC_IMPLEMENTATION PRIVATE U_COMMON_IMPLEMENTATION UCONFIG_NO_FILE_IO=1) +target_compile_features(icu_common PUBLIC cxx_std_17) +set_target_properties(icu_common PROPERTIES POSITION_INDEPENDENT_CODE ON CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON) +if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(icu_common INTERFACE LINKER:--exclude-libs,libicu_common.a) +endif() diff --git a/core/libs/prx/libSceIcu/CMakeLists.txt b/core/libs/prx/libSceIcu/CMakeLists.txt index 4458d7708f25..e1e29e8609c3 100644 --- a/core/libs/prx/libSceIcu/CMakeLists.txt +++ b/core/libs/prx/libSceIcu/CMakeLists.txt @@ -5,6 +5,8 @@ target_sources(libSceIcu PRIVATE ConverterCallbacks.cpp) target_sources(libSceIcu PRIVATE CString.cpp) target_sources(libSceIcu PRIVATE Space.cpp) target_sources(libSceIcu PRIVATE Utf8.cpp) +target_sources(libSceIcu PRIVATE Properties.cpp CaseMapping.cpp) +target_link_libraries(libSceIcu PRIVATE icu_common) if(BUILD_TESTING) add_test_executable(guest_icu_utf8_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuUtf8.cpp) @@ -83,3 +85,29 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_properties_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Properties.cpp) + target_include_directories(guest_icu_properties_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_properties_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_properties_tests) + add_test(NAME guest_icu_properties COMMAND guest_icu_properties_tests) + set_tests_properties(guest_icu_properties PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_properties PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_case_mapping_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/CaseMapping.cpp) + target_include_directories(guest_icu_case_mapping_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_case_mapping_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_case_mapping_tests) + add_test(NAME guest_icu_case_mapping COMMAND guest_icu_case_mapping_tests) + set_tests_properties(guest_icu_case_mapping PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_case_mapping PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceIcu/CaseMapping.cpp b/core/libs/prx/libSceIcu/CaseMapping.cpp new file mode 100644 index 000000000000..8b6d952cfe2f --- /dev/null +++ b/core/libs/prx/libSceIcu/CaseMapping.cpp @@ -0,0 +1,94 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/General.hpp" +#include "ucase.h" +#include "ucasemap_imp.h" +#include "unicode/ustring.h" +#include + +namespace { + +void CheckString(const char16_t* text, std::int32_t length) { + if (length < -1 || (!text && length != 0)) APS5_INVALID_ARG_EX; +} + +UErrorCode& Status(std::int32_t* error) { + if (!error) APS5_INVALID_ARG_EX; + return *reinterpret_cast(error); +} + +bool InvalidMapping(const char16_t* destination, std::int32_t capacity, const char16_t* source, std::int32_t length) { + return capacity < 0 || (!destination && capacity > 0) || !source || length < -1; +} + +std::int32_t MapInLocale(const char* name, const char* locale, char16_t* destination, std::int32_t capacity, + const char16_t* source, std::int32_t length, UStringCaseMapper* mapper, std::int32_t* error) { + auto& status = Status(error); + std::int32_t caseLocale = UCASE_LOC_ROOT; + if (locale) { + if (*locale) caseLocale = ucase_getCaseLocale(locale); + } else if (U_SUCCESS(status) && !InvalidMapping(destination, capacity, source, length)) { + NotImplemented_nid_no_patch(name); + } + return ustrcase_mapWithOverlap(caseLocale, 0, UCASEMAP_BREAK_ITERATOR_NULL destination, capacity, source, length, mapper, + status); +} + +} + +extern "C" { + +std::int32_t APS5_VABI u_strFoldCase_nid_postfix(char16_t* destination, std::int32_t capacity, const char16_t* source, + std::int32_t length, std::uint32_t options, std::int32_t* error) { + return u_strFoldCase(destination, capacity, source, length, options, &Status(error)); +} + +std::int32_t APS5_VABI u_strToLower_nid_postfix(char16_t* destination, std::int32_t capacity, const char16_t* source, + std::int32_t length, const char* locale, std::int32_t* error) { + return MapInLocale("u_strToLower: default locale", locale, destination, capacity, source, length, + ustrcase_internalToLower, error); +} + +std::int32_t APS5_VABI u_strToUpper_nid_postfix(char16_t* destination, std::int32_t capacity, const char16_t* source, + std::int32_t length, const char* locale, std::int32_t* error) { + return MapInLocale("u_strToUpper: default locale", locale, destination, capacity, source, length, + ustrcase_internalToUpper, error); +} + +std::int32_t APS5_VABI u_strCaseCompare_nid_postfix(const char16_t* first, std::int32_t firstLength, const char16_t* second, + std::int32_t secondLength, std::uint32_t options, std::int32_t* error) { + return u_strCaseCompare(first, firstLength, second, secondLength, options, reinterpret_cast(error)); +} + +std::int32_t APS5_VABI u_strcasecmp_nid_postfix(const char16_t* first, const char16_t* second, std::uint32_t options) { + CheckString(first, -1); + CheckString(second, -1); + return u_strcasecmp(first, second, options); +} + +std::int32_t APS5_VABI u_strncasecmp_nid_postfix(const char16_t* first, const char16_t* second, std::int32_t count, + std::uint32_t options) { + CheckString(first, count); + CheckString(second, count); + return u_strncasecmp(first, second, count, options); +} + +std::int32_t APS5_VABI u_memcasecmp_nid_postfix(const char16_t* first, const char16_t* second, std::int32_t length, + std::uint32_t options) { + CheckString(first, length); + CheckString(second, length); + return u_memcasecmp(first, second, length, options); +} + +void APS5_VABI u_caseInsensitivePrefixMatch_nid_postfix(const char16_t* first, std::int32_t firstLength, const char16_t* second, + std::int32_t secondLength, std::uint32_t options, + std::int32_t* firstMatch, std::int32_t* secondMatch, + std::int32_t* error) { + if (U_FAILURE(Status(error))) return; + if (firstMatch && !secondMatch) APS5_INVALID_ARG_EX; + CheckString(first, firstLength); + CheckString(second, secondLength); + u_caseInsensitivePrefixMatch(first, firstLength, second, secondLength, options, firstMatch, secondMatch, + reinterpret_cast(error)); +} + +} diff --git a/core/libs/prx/libSceIcu/CaseMapping.hpp b/core/libs/prx/libSceIcu/CaseMapping.hpp new file mode 100644 index 000000000000..f84293472f50 --- /dev/null +++ b/core/libs/prx/libSceIcu/CaseMapping.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +std::int32_t APS5_VABI u_strFoldCase_nid_postfix(char16_t*, std::int32_t, const char16_t*, std::int32_t, std::uint32_t, std::int32_t*); +std::int32_t APS5_VABI u_strToLower_nid_postfix(char16_t*, std::int32_t, const char16_t*, std::int32_t, const char*, std::int32_t*); +std::int32_t APS5_VABI u_strToUpper_nid_postfix(char16_t*, std::int32_t, const char16_t*, std::int32_t, const char*, std::int32_t*); +std::int32_t APS5_VABI u_strCaseCompare_nid_postfix(const char16_t*, std::int32_t, const char16_t*, std::int32_t, std::uint32_t, std::int32_t*); +std::int32_t APS5_VABI u_strcasecmp_nid_postfix(const char16_t*, const char16_t*, std::uint32_t); +std::int32_t APS5_VABI u_strncasecmp_nid_postfix(const char16_t*, const char16_t*, std::int32_t, std::uint32_t); +std::int32_t APS5_VABI u_memcasecmp_nid_postfix(const char16_t*, const char16_t*, std::int32_t, std::uint32_t); +void APS5_VABI u_caseInsensitivePrefixMatch_nid_postfix(const char16_t*, std::int32_t, const char16_t*, std::int32_t, std::uint32_t, std::int32_t*, std::int32_t*, std::int32_t*); +} diff --git a/core/libs/prx/libSceIcu/Export.cpp b/core/libs/prx/libSceIcu/Export.cpp index 4e8e30c45e91..a6db43658b4e 100644 --- a/core/libs/prx/libSceIcu/Export.cpp +++ b/core/libs/prx/libSceIcu/Export.cpp @@ -86,96 +86,41 @@ int APS5_VABI sceIcuUnknown_RlZwfEuRDx8_nid_no_patch() { } APS5_EXPORT("RlZwfEuRDx8", sceIcuUnknown_RlZwfEuRDx8_nid_no_patch); -int APS5_VABI sceIcuUnknown_vXu991uXNUo_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("vXu991uXNUo", sceIcuUnknown_vXu991uXNUo_nid_no_patch); - -int APS5_VABI sceIcuUnknown_aF_oEFPzEsY_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("aF+oEFPzEsY", sceIcuUnknown_aF_oEFPzEsY_nid_no_patch); - -int APS5_VABI sceIcuUnknown_lNPpoUVElSA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("lNPpoUVElSA", sceIcuUnknown_lNPpoUVElSA_nid_no_patch); - -int APS5_VABI sceIcuUnknown_BTFjoSWI5_Y_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("BTFjoSWI5-Y", sceIcuUnknown_BTFjoSWI5_Y_nid_no_patch); - int APS5_VABI sceIcuUnknown_Q8P3U9F8p_Y_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("Q8P3U9F8p-Y", sceIcuUnknown_Q8P3U9F8p_Y_nid_no_patch); -int APS5_VABI sceIcuUnknown_4Kf0Hy8xJrI_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("4Kf0Hy8xJrI", sceIcuUnknown_4Kf0Hy8xJrI_nid_no_patch); - int APS5_VABI sceIcuUnknown_JGkExZI3fFI_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("JGkExZI3fFI", sceIcuUnknown_JGkExZI3fFI_nid_no_patch); -int APS5_VABI sceIcuUnknown_0cYEtBUJ5zQ_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("0cYEtBUJ5zQ", sceIcuUnknown_0cYEtBUJ5zQ_nid_no_patch); - int APS5_VABI sceIcuUnknown_1SsridDRBYQ_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("1SsridDRBYQ", sceIcuUnknown_1SsridDRBYQ_nid_no_patch); -int APS5_VABI sceIcuUnknown_a25spvWYEOA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("a25spvWYEOA", sceIcuUnknown_a25spvWYEOA_nid_no_patch); - int APS5_VABI sceIcuUnknown_EuFvqF73SwU_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("EuFvqF73SwU", sceIcuUnknown_EuFvqF73SwU_nid_no_patch); -int APS5_VABI sceIcuUnknown_wybPkXuWnoE_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("wybPkXuWnoE", sceIcuUnknown_wybPkXuWnoE_nid_no_patch); - int APS5_VABI sceIcuUnknown_2qAOcgRJZ3M_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("2qAOcgRJZ3M", sceIcuUnknown_2qAOcgRJZ3M_nid_no_patch); -int APS5_VABI sceIcuUnknown_n4yiKNo1yCs_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("n4yiKNo1yCs", sceIcuUnknown_n4yiKNo1yCs_nid_no_patch); - int APS5_VABI sceIcuUnknown_hI8JVrcGWhc_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("hI8JVrcGWhc", sceIcuUnknown_hI8JVrcGWhc_nid_no_patch); -int APS5_VABI sceIcuUnknown_h16q7HjVyTE_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("h16q7HjVyTE", sceIcuUnknown_h16q7HjVyTE_nid_no_patch); - int APS5_VABI sceIcuUnknown_Q8Y0ntVp_GY_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("Q8Y0ntVp-GY", sceIcuUnknown_Q8Y0ntVp_GY_nid_no_patch); -int APS5_VABI sceIcuUnknown_TXEHKpCJ_aE_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("TXEHKpCJ-aE", sceIcuUnknown_TXEHKpCJ_aE_nid_no_patch); - int APS5_VABI sceIcuUnknown_UmNS0P0_ovs_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -201,46 +146,6 @@ int APS5_VABI sceIcuUnknown__aK6d418l4c_nid_no_patch() { } APS5_EXPORT("-aK6d418l4c", sceIcuUnknown__aK6d418l4c_nid_no_patch); -int APS5_VABI sceIcuUnknown_5V2WIPOZcaA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("5V2WIPOZcaA", sceIcuUnknown_5V2WIPOZcaA_nid_no_patch); - -int APS5_VABI sceIcuUnknown_FHSNfxQFEdA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("FHSNfxQFEdA", sceIcuUnknown_FHSNfxQFEdA_nid_no_patch); - -int APS5_VABI sceIcuUnknown_eD76mHY99fM_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("eD76mHY99fM", sceIcuUnknown_eD76mHY99fM_nid_no_patch); - -int APS5_VABI sceIcuUnknown_F_2mXPmh_7s_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("F+2mXPmh-7s", sceIcuUnknown_F_2mXPmh_7s_nid_no_patch); - -int APS5_VABI sceIcuUnknown_BQtUj7CVrC0_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("BQtUj7CVrC0", sceIcuUnknown_BQtUj7CVrC0_nid_no_patch); - -int APS5_VABI sceIcuUnknown_pCh4kIKMRhY_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("pCh4kIKMRhY", sceIcuUnknown_pCh4kIKMRhY_nid_no_patch); - -int APS5_VABI sceIcuUnknown_3Z22ZB8mHq8_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("3Z22ZB8mHq8", sceIcuUnknown_3Z22ZB8mHq8_nid_no_patch); - -int APS5_VABI sceIcuUnknown_Hka9gz6FuDs_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Hka9gz6FuDs", sceIcuUnknown_Hka9gz6FuDs_nid_no_patch); - int APS5_VABI sceIcuUnknown_5__ZFSRQ0_k_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -251,131 +156,11 @@ int APS5_VABI sceIcuUnknown_zGuhKwXM9I0_nid_no_patch() { } APS5_EXPORT("zGuhKwXM9I0", sceIcuUnknown_zGuhKwXM9I0_nid_no_patch); -int APS5_VABI sceIcuUnknown_6y_NITWLmmE_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("6y+NITWLmmE", sceIcuUnknown_6y_NITWLmmE_nid_no_patch); - -int APS5_VABI sceIcuUnknown_qXSNbAnlsKg_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("qXSNbAnlsKg", sceIcuUnknown_qXSNbAnlsKg_nid_no_patch); - -int APS5_VABI sceIcuUnknown_u8WyPl4r3Oc_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("u8WyPl4r3Oc", sceIcuUnknown_u8WyPl4r3Oc_nid_no_patch); - -int APS5_VABI sceIcuUnknown_7E_h3yiZjKw_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("7E+h3yiZjKw", sceIcuUnknown_7E_h3yiZjKw_nid_no_patch); - int APS5_VABI sceIcuUnknown_dzbKmf3Ycog_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("dzbKmf3Ycog", sceIcuUnknown_dzbKmf3Ycog_nid_no_patch); -int APS5_VABI sceIcuUnknown_AvrA9K2lvC4_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("AvrA9K2lvC4", sceIcuUnknown_AvrA9K2lvC4_nid_no_patch); - -int APS5_VABI sceIcuUnknown_58CIUaViW_k_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("58CIUaViW+k", sceIcuUnknown_58CIUaViW_k_nid_no_patch); - -int APS5_VABI sceIcuUnknown_1GkIKiuqgBg_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("1GkIKiuqgBg", sceIcuUnknown_1GkIKiuqgBg_nid_no_patch); - -int APS5_VABI sceIcuUnknown_qGIX2VTcNJo_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("qGIX2VTcNJo", sceIcuUnknown_qGIX2VTcNJo_nid_no_patch); - -int APS5_VABI sceIcuUnknown_OJ_13SCOxgI_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("OJ+13SCOxgI", sceIcuUnknown_OJ_13SCOxgI_nid_no_patch); - -int APS5_VABI sceIcuUnknown_wxirhbbuv84_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("wxirhbbuv84", sceIcuUnknown_wxirhbbuv84_nid_no_patch); - -int APS5_VABI sceIcuUnknown__L1K53Ku3mE_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("+L1K53Ku3mE", sceIcuUnknown__L1K53Ku3mE_nid_no_patch); - -int APS5_VABI sceIcuUnknown_LtBBDJEZXhY_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("LtBBDJEZXhY", sceIcuUnknown_LtBBDJEZXhY_nid_no_patch); - -int APS5_VABI sceIcuUnknown_Sv7TbVAEqeQ_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Sv7TbVAEqeQ", sceIcuUnknown_Sv7TbVAEqeQ_nid_no_patch); - -int APS5_VABI sceIcuUnknown_n4o8mgXtF5s_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("n4o8mgXtF5s", sceIcuUnknown_n4o8mgXtF5s_nid_no_patch); - -int APS5_VABI sceIcuUnknown__P5TDFU2_fc_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("+P5TDFU2-fc", sceIcuUnknown__P5TDFU2_fc_nid_no_patch); - -int APS5_VABI sceIcuUnknown_XVQ3cf0BdQ0_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("XVQ3cf0BdQ0", sceIcuUnknown_XVQ3cf0BdQ0_nid_no_patch); - -int APS5_VABI sceIcuUnknown_HneyXNQpPhQ_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("HneyXNQpPhQ", sceIcuUnknown_HneyXNQpPhQ_nid_no_patch); - -int APS5_VABI sceIcuUnknown_bgnHFMqxYag_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("bgnHFMqxYag", sceIcuUnknown_bgnHFMqxYag_nid_no_patch); - -int APS5_VABI sceIcuUnknown_LXxEsOsIa5g_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("LXxEsOsIa5g", sceIcuUnknown_LXxEsOsIa5g_nid_no_patch); - -int APS5_VABI sceIcuUnknown_Z5_W18jAIH4_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Z5+W18jAIH4", sceIcuUnknown_Z5_W18jAIH4_nid_no_patch); - -int APS5_VABI sceIcuUnknown_BhCcIlyHMrk_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("BhCcIlyHMrk", sceIcuUnknown_BhCcIlyHMrk_nid_no_patch); - -int APS5_VABI sceIcuUnknown_S0WDjcZLdqI_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("S0WDjcZLdqI", sceIcuUnknown_S0WDjcZLdqI_nid_no_patch); - -int APS5_VABI sceIcuUnknown_udAPjod3tpU_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("udAPjod3tpU", sceIcuUnknown_udAPjod3tpU_nid_no_patch); - -int APS5_VABI sceIcuUnknown_CfP4Q2Yjm1U_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("CfP4Q2Yjm1U", sceIcuUnknown_CfP4Q2Yjm1U_nid_no_patch); - int APS5_VABI sceIcuUnknown_sQ0avkeBLo4_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -406,11 +191,6 @@ int APS5_VABI sceIcuUnknown_xAo5lvA_W3o_nid_no_patch() { } APS5_EXPORT("xAo5lvA+W3o", sceIcuUnknown_xAo5lvA_W3o_nid_no_patch); -int APS5_VABI sceIcuUnknown_KzcrFzKpYE8_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("KzcrFzKpYE8", sceIcuUnknown_KzcrFzKpYE8_nid_no_patch); - int APS5_VABI sceIcuUnknown_pgvyZZ_d1ck_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -421,11 +201,6 @@ int APS5_VABI sceIcuUnknown_TNZrG2iXikw_nid_no_patch() { } APS5_EXPORT("TNZrG2iXikw", sceIcuUnknown_TNZrG2iXikw_nid_no_patch); -int APS5_VABI sceIcuUnknown_Z5ljxAg3thw_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Z5ljxAg3thw", sceIcuUnknown_Z5ljxAg3thw_nid_no_patch); - int APS5_VABI sceIcuUnknown_OAFz3KoG_gA_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -446,11 +221,6 @@ int APS5_VABI sceIcuUnknown_RIcNJWDYKhM_nid_no_patch() { } APS5_EXPORT("RIcNJWDYKhM", sceIcuUnknown_RIcNJWDYKhM_nid_no_patch); -int APS5_VABI sceIcuUnknown_4FrZfvvYHCI_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("4FrZfvvYHCI", sceIcuUnknown_4FrZfvvYHCI_nid_no_patch); - int APS5_VABI sceIcuUnknown_ZxnyBy8reV4_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -461,21 +231,6 @@ int APS5_VABI sceIcuUnknown_X7gbPHVoNEw_nid_no_patch() { } APS5_EXPORT("X7gbPHVoNEw", sceIcuUnknown_X7gbPHVoNEw_nid_no_patch); -int APS5_VABI sceIcuUnknown_aXUj3BT58vY_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("aXUj3BT58vY", sceIcuUnknown_aXUj3BT58vY_nid_no_patch); - -int APS5_VABI sceIcuUnknown_FCx2DacLDWs_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("FCx2DacLDWs", sceIcuUnknown_FCx2DacLDWs_nid_no_patch); - -int APS5_VABI sceIcuUnknown_ekxUEjhuk3Q_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("ekxUEjhuk3Q", sceIcuUnknown_ekxUEjhuk3Q_nid_no_patch); - int APS5_VABI sceIcuUnknown_BsjbIVU27jQ_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceIcu/Properties.cpp b/core/libs/prx/libSceIcu/Properties.cpp new file mode 100644 index 000000000000..8873d7fe6fd7 --- /dev/null +++ b/core/libs/prx/libSceIcu/Properties.cpp @@ -0,0 +1,215 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/General.hpp" +#include "unicode/uchar.h" +#include "uprops.h" +#include + +namespace { + +struct CharTypeEnumeration { + Icu::CharTypeRange range; + const void* context; +}; + +UBool U_CALLCONV ForwardCharTypeRange(const void* context, UChar32 start, UChar32 limit, UCharCategory type) { + const auto& enumeration = *static_cast(context); + return enumeration.range(enumeration.context, start, limit, type) != 0; +} + +bool FromDataFile(UPropertySource source) { + switch (source) { + case UPROPS_SRC_NFKC: + case UPROPS_SRC_NFKC_CF: + case UPROPS_SRC_INPC: + case UPROPS_SRC_INSC: + case UPROPS_SRC_VO: + case UPROPS_SRC_EMOJI: return true; + default: return false; + } +} + +bool LayoutProperty(UPropertySource source) { + return source == UPROPS_SRC_INPC || source == UPROPS_SRC_INSC || source == UPROPS_SRC_VO; +} + +UProperty Property(std::int32_t which) { + return static_cast(which); +} + +} + +extern "C" { + +std::int8_t APS5_VABI u_charType_nid_postfix(std::int32_t character) { + return u_charType(character); +} + +void APS5_VABI u_enumCharTypes_nid_postfix(Icu::CharTypeRange range, const void* context) { + if (!range) return; + const CharTypeEnumeration enumeration{range, context}; + u_enumCharTypes(ForwardCharTypeRange, &enumeration); +} + +std::int8_t APS5_VABI u_islower_nid_postfix(std::int32_t character) { + return u_islower(character); +} + +std::int8_t APS5_VABI u_isupper_nid_postfix(std::int32_t character) { + return u_isupper(character); +} + +std::int8_t APS5_VABI u_istitle_nid_postfix(std::int32_t character) { + return u_istitle(character); +} + +std::int8_t APS5_VABI u_isdigit_nid_postfix(std::int32_t character) { + return u_isdigit(character); +} + +std::int8_t APS5_VABI u_isxdigit_nid_postfix(std::int32_t character) { + return u_isxdigit(character); +} + +std::int8_t APS5_VABI u_isalpha_nid_postfix(std::int32_t character) { + return u_isalpha(character); +} + +std::int8_t APS5_VABI u_isalnum_nid_postfix(std::int32_t character) { + return u_isalnum(character); +} + +std::int8_t APS5_VABI u_isdefined_nid_postfix(std::int32_t character) { + return u_isdefined(character); +} + +std::int8_t APS5_VABI u_isbase_nid_postfix(std::int32_t character) { + return u_isbase(character); +} + +std::int8_t APS5_VABI u_iscntrl_nid_postfix(std::int32_t character) { + return u_iscntrl(character); +} + +std::int8_t APS5_VABI u_ispunct_nid_postfix(std::int32_t character) { + return u_ispunct(character); +} + +std::int8_t APS5_VABI u_isgraph_nid_postfix(std::int32_t character) { + return u_isgraph(character); +} + +std::int8_t APS5_VABI u_isprint_nid_postfix(std::int32_t character) { + return u_isprint(character); +} + +std::int8_t APS5_VABI u_isIDStart_nid_postfix(std::int32_t character) { + return u_isIDStart(character); +} + +std::int8_t APS5_VABI u_isIDPart_nid_postfix(std::int32_t character) { + return u_isIDPart(character); +} + +std::int8_t APS5_VABI u_isIDIgnorable_nid_postfix(std::int32_t character) { + return u_isIDIgnorable(character); +} + +std::int8_t APS5_VABI u_isJavaIDStart_nid_postfix(std::int32_t character) { + return u_isJavaIDStart(character); +} + +std::int8_t APS5_VABI u_isJavaIDPart_nid_postfix(std::int32_t character) { + return u_isJavaIDPart(character); +} + +std::int8_t APS5_VABI u_isUAlphabetic_nid_postfix(std::int32_t character) { + return u_isUAlphabetic(character); +} + +std::int8_t APS5_VABI u_isULowercase_nid_postfix(std::int32_t character) { + return u_isULowercase(character); +} + +std::int8_t APS5_VABI u_isUUppercase_nid_postfix(std::int32_t character) { + return u_isUUppercase(character); +} + +std::int32_t APS5_VABI u_charDigitValue_nid_postfix(std::int32_t character) { + return u_charDigitValue(character); +} + +std::int32_t APS5_VABI u_digit_nid_postfix(std::int32_t character, std::int8_t radix) { + return u_digit(character, radix); +} + +double APS5_VABI u_getNumericValue_nid_postfix(std::int32_t character) { + return u_getNumericValue(character); +} + +void APS5_VABI u_charAge_nid_postfix(std::int32_t character, std::uint8_t* version) { + u_charAge(character, version); +} + +std::uint8_t APS5_VABI u_getCombiningClass_nid_postfix(std::int32_t character) { + return u_getCombiningClass(character); +} + +std::int32_t APS5_VABI u_charDirection_nid_postfix(std::int32_t character) { + return u_charDirection(character); +} + +std::int8_t APS5_VABI u_isMirrored_nid_postfix(std::int32_t character) { + return u_isMirrored(character); +} + +std::int32_t APS5_VABI u_charMirror_nid_postfix(std::int32_t character) { + return u_charMirror(character); +} + +std::int32_t APS5_VABI u_getBidiPairedBracket_nid_postfix(std::int32_t character) { + return u_getBidiPairedBracket(character); +} + +std::int32_t APS5_VABI u_foldCase_nid_postfix(std::int32_t character, std::uint32_t options) { + return u_foldCase(character, options); +} + +std::int32_t APS5_VABI u_getPropertyEnum_nid_postfix(const char* alias) { + return u_getPropertyEnum(alias); +} + +const char* APS5_VABI u_getPropertyName_nid_postfix(std::int32_t property, std::int32_t choice) { + return u_getPropertyName(Property(property), static_cast(choice)); +} + +std::int32_t APS5_VABI u_getPropertyValueEnum_nid_postfix(std::int32_t property, const char* alias) { + return u_getPropertyValueEnum(Property(property), alias); +} + +const char* APS5_VABI u_getPropertyValueName_nid_postfix(std::int32_t property, std::int32_t value, std::int32_t choice) { + return u_getPropertyValueName(Property(property), value, static_cast(choice)); +} + +std::int32_t APS5_VABI u_getIntPropertyMinValue_nid_postfix(std::int32_t property) { + return u_getIntPropertyMinValue(Property(property)); +} + +std::int32_t APS5_VABI u_getIntPropertyMaxValue_nid_postfix(std::int32_t property) { + if (property >= UCHAR_INT_START && property < UCHAR_INT_LIMIT && LayoutProperty(uprops_getSource(Property(property)))) + NotImplemented_nid_no_patch("u_getIntPropertyMaxValue: property from a data file"); + return u_getIntPropertyMaxValue(Property(property)); +} + +std::int8_t APS5_VABI u_hasBinaryProperty_nid_postfix(std::int32_t character, std::int32_t property) { + if (property >= UCHAR_BINARY_START && property < UCHAR_BINARY_LIMIT && FromDataFile(uprops_getSource(Property(property)))) + NotImplemented_nid_no_patch("u_hasBinaryProperty: property from a data file"); + return u_hasBinaryProperty(character, Property(property)); +} + +std::int32_t APS5_VABI u_getIntPropertyValue_nid_postfix(std::int32_t character, std::int32_t property) { + if (property >= UCHAR_BINARY_START && property < UCHAR_INT_LIMIT && FromDataFile(uprops_getSource(Property(property)))) + NotImplemented_nid_no_patch("u_getIntPropertyValue: property from a data file"); + return u_getIntPropertyValue(character, Property(property)); +} + +} diff --git a/core/libs/prx/libSceIcu/Properties.hpp b/core/libs/prx/libSceIcu/Properties.hpp new file mode 100644 index 000000000000..b965873b0e88 --- /dev/null +++ b/core/libs/prx/libSceIcu/Properties.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +namespace Icu { +using CharTypeRange = std::int8_t (APS5_VABI*)(const void*, std::int32_t, std::int32_t, std::int32_t); +} // namespace Icu + +extern "C" { +std::int8_t APS5_VABI u_charType_nid_postfix(std::int32_t); +void APS5_VABI u_enumCharTypes_nid_postfix(Icu::CharTypeRange, const void*); +std::int8_t APS5_VABI u_islower_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isupper_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_istitle_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isdigit_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isxdigit_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isalpha_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isalnum_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isdefined_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isbase_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_iscntrl_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_ispunct_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isgraph_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isprint_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isIDStart_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isIDPart_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isIDIgnorable_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isJavaIDStart_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isJavaIDPart_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isUAlphabetic_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isULowercase_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isUUppercase_nid_postfix(std::int32_t); +std::int32_t APS5_VABI u_charDigitValue_nid_postfix(std::int32_t); +std::int32_t APS5_VABI u_digit_nid_postfix(std::int32_t, std::int8_t); +double APS5_VABI u_getNumericValue_nid_postfix(std::int32_t); +void APS5_VABI u_charAge_nid_postfix(std::int32_t, std::uint8_t*); +std::uint8_t APS5_VABI u_getCombiningClass_nid_postfix(std::int32_t); +std::int32_t APS5_VABI u_charDirection_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_isMirrored_nid_postfix(std::int32_t); +std::int32_t APS5_VABI u_charMirror_nid_postfix(std::int32_t); +std::int32_t APS5_VABI u_getBidiPairedBracket_nid_postfix(std::int32_t); +std::int32_t APS5_VABI u_foldCase_nid_postfix(std::int32_t, std::uint32_t); +std::int32_t APS5_VABI u_getPropertyEnum_nid_postfix(const char*); +const char* APS5_VABI u_getPropertyName_nid_postfix(std::int32_t, std::int32_t); +std::int32_t APS5_VABI u_getPropertyValueEnum_nid_postfix(std::int32_t, const char*); +const char* APS5_VABI u_getPropertyValueName_nid_postfix(std::int32_t, std::int32_t, std::int32_t); +std::int32_t APS5_VABI u_getIntPropertyMinValue_nid_postfix(std::int32_t); +std::int32_t APS5_VABI u_getIntPropertyMaxValue_nid_postfix(std::int32_t); +std::int8_t APS5_VABI u_hasBinaryProperty_nid_postfix(std::int32_t, std::int32_t); +std::int32_t APS5_VABI u_getIntPropertyValue_nid_postfix(std::int32_t, std::int32_t); +} diff --git a/core/libs/prx/libSceIcu/libSceIcu.hpp b/core/libs/prx/libSceIcu/libSceIcu.hpp index bcbcf0130ad7..809db3fb9327 100644 --- a/core/libs/prx/libSceIcu/libSceIcu.hpp +++ b/core/libs/prx/libSceIcu/libSceIcu.hpp @@ -6,3 +6,5 @@ #include "CString.hpp" #include "Space.hpp" #include "Unescape.hpp" +#include "Properties.hpp" +#include "CaseMapping.hpp" diff --git a/core/libs/prx/libSceIcu/tests/CaseMapping.cpp b/core/libs/prx/libSceIcu/tests/CaseMapping.cpp new file mode 100644 index 000000000000..1c1ca0e7b487 --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/CaseMapping.cpp @@ -0,0 +1,138 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include + +namespace { + +constexpr std::int32_t Success = 0; +constexpr std::int32_t IllegalArgument = 1; +constexpr std::int32_t BufferOverflow = 15; +constexpr std::int32_t StringNotTerminated = -124; + +void Expect(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "failed: %s\n", what); + std::abort(); +} + +void ExpectInt(std::int64_t actual, std::int64_t expected, const char* what) { + if (actual == expected) return; + std::fprintf(stderr, "%s returned %lld, expected %lld\n", what, static_cast(actual), static_cast(expected)); + std::abort(); +} + +template +void ExpectThrow(Function function, const char* what) { + try { + function(); + } catch (const Exception&) { + return; + } + Expect(false, what); +} + +using Mapper = std::int32_t (APS5_VABI*)(char16_t*, std::int32_t, const char16_t*, std::int32_t, const char*, std::int32_t*); + +void ExpectMapping(Mapper map, const char* locale, const std::u16string& source, const std::u16string& expected, const char* what) { + char16_t buffer[32] = {}; + std::int32_t error = Success; + const auto length = map(buffer, 32, source.c_str(), -1, locale, &error); + Expect(error == Success, what); + ExpectInt(length, static_cast(expected.size()), what); + Expect(std::u16string(buffer, length) == expected && buffer[length] == 0, what); +} + +void TestMapping() { + ExpectMapping(u_strToUpper_nid_postfix, "", u"straße", u"STRASSE", "u_strToUpper(straße)"); + ExpectMapping(u_strToUpper_nid_postfix, "tr", u"i", u"İ", "u_strToUpper(i, tr)"); + ExpectMapping(u_strToUpper_nid_postfix, "en", u"i", u"I", "u_strToUpper(i, en)"); + ExpectMapping(u_strToUpper_nid_postfix, "el", u"ά", u"Α", "u_strToUpper(U+03AC, el)"); + ExpectMapping(u_strToUpper_nid_postfix, "", u"ά", u"Ά", "u_strToUpper(U+03AC, root)"); + ExpectMapping(u_strToLower_nid_postfix, "az", u"I", u"ı", "u_strToLower(I, az)"); + ExpectMapping(u_strToLower_nid_postfix, "", u"İ", u"i̇", "u_strToLower(U+0130)"); + ExpectMapping(u_strToLower_nid_postfix, "lt", u"Ì", u"i̇̀", "u_strToLower(U+00CC, lt)"); + ExpectMapping(u_strToLower_nid_postfix, "", u"ΣAΣ", u"σaς", "u_strToLower(final sigma)"); + + std::int32_t error = Success; + ExpectInt(u_strToUpper_nid_postfix(nullptr, 0, u"straße", -1, "", &error), 7, "u_strToUpper preflight"); + ExpectInt(error, BufferOverflow, "u_strToUpper preflight error"); + char16_t exact[7]; + error = Success; + ExpectInt(u_strToUpper_nid_postfix(exact, 7, u"straße", -1, "", &error), 7, "u_strToUpper exact capacity"); + ExpectInt(error, StringNotTerminated, "u_strToUpper exact capacity error"); + char16_t inPlace[8] = u"abc"; + error = Success; + ExpectInt(u_strToUpper_nid_postfix(inPlace, 8, inPlace, -1, "", &error), 3, "u_strToUpper in place"); + Expect(error == Success && std::u16string(inPlace) == u"ABC", "u_strToUpper in place result"); + error = Success; + ExpectInt(u_strToLower_nid_postfix(nullptr, 4, u"A", -1, "", &error), 0, "u_strToLower null destination"); + ExpectInt(error, IllegalArgument, "u_strToLower null destination error"); + error = BufferOverflow; + ExpectInt(u_strToLower_nid_postfix(exact, 7, u"A", -1, nullptr, &error), 0, "u_strToLower failed input"); + ExpectInt(error, BufferOverflow, "u_strToLower keeps the input error"); + error = Success; + ExpectInt(u_strToLower_nid_postfix(exact, 7, nullptr, -1, nullptr, &error), 0, "u_strToLower null source"); + ExpectInt(error, IllegalArgument, "u_strToLower null source error"); + error = Success; + ExpectThrow([&] { u_strToLower_nid_postfix(exact, 7, u"A", -1, nullptr, &error); }, "u_strToLower default locale"); + ExpectThrow([&] { u_strToUpper_nid_postfix(exact, 7, u"a", -1, nullptr, &error); }, "u_strToUpper default locale"); + ExpectThrow([&] { u_strToUpper_nid_postfix(exact, 7, u"a", -1, "", nullptr); }, "u_strToUpper null error"); + + char16_t folded[8] = {}; + error = Success; + ExpectInt(u_strFoldCase_nid_postfix(folded, 8, u"ßI", -1, 0, &error), 3, "u_strFoldCase"); + Expect(error == Success && std::u16string(folded) == u"ssi", "u_strFoldCase result"); + error = Success; + ExpectInt(u_strFoldCase_nid_postfix(folded, 8, u"I", 1, 1, &error), 1, "u_strFoldCase exclude special I"); + Expect(folded[0] == 0x131, "u_strFoldCase exclude special I result"); + ExpectThrow([&] { u_strFoldCase_nid_postfix(folded, 8, u"I", 1, 0, nullptr); }, "u_strFoldCase null error"); +} + +void TestComparison() { + ExpectInt(u_strcasecmp_nid_postfix(u"HELLO", u"hello", 0), 0, "u_strcasecmp equal"); + Expect(u_strcasecmp_nid_postfix(u"a", u"B", 0) < 0, "u_strcasecmp less"); + ExpectInt(u_strcasecmp_nid_postfix(u"straße", u"STRASSE", 0), 0, "u_strcasecmp full folding"); + Expect(u_strcasecmp_nid_postfix(u"ı", u"I", 0) != 0, "u_strcasecmp dotless i"); + ExpectInt(u_strcasecmp_nid_postfix(u"ı", u"I", 1), 0, "u_strcasecmp exclude special I"); + ExpectInt(u_strncasecmp_nid_postfix(u"abcX", u"ABCY", 3, 0), 0, "u_strncasecmp prefix"); + Expect(u_strncasecmp_nid_postfix(u"abcX", u"ABCY", 4, 0) < 0, "u_strncasecmp differs"); + ExpectInt(u_strncasecmp_nid_postfix(u"ab", u"AB\0c", 4, 0), 0, "u_strncasecmp stops at NUL"); + ExpectInt(u_memcasecmp_nid_postfix(u"a\0B", u"A\0b", 3, 0), 0, "u_memcasecmp crosses NUL"); + Expect(u_memcasecmp_nid_postfix(u"a\0B", u"A\0c", 3, 0) < 0, "u_memcasecmp differs after NUL"); + ExpectInt(u_memcasecmp_nid_postfix(nullptr, nullptr, 0, 0), 0, "u_memcasecmp empty null"); + std::int32_t error = Success; + ExpectInt(u_strCaseCompare_nid_postfix(u"Straße", -1, u"STRASSE", 7, 0, &error), 0, "u_strCaseCompare"); + ExpectInt(error, Success, "u_strCaseCompare error"); + ExpectInt(u_strCaseCompare_nid_postfix(nullptr, 1, u"a", 1, 0, &error), 0, "u_strCaseCompare null"); + ExpectInt(error, IllegalArgument, "u_strCaseCompare null error"); + ExpectInt(u_strCaseCompare_nid_postfix(u"a", 1, u"a", 1, 0, nullptr), 0, "u_strCaseCompare null error pointer"); + ExpectThrow([] { u_strcasecmp_nid_postfix(nullptr, u"a", 0); }, "u_strcasecmp null"); + ExpectThrow([] { u_memcasecmp_nid_postfix(u"a", u"a", -2, 0); }, "u_memcasecmp negative length"); + + std::int32_t first = -1; + std::int32_t second = -1; + error = Success; + u_caseInsensitivePrefixMatch_nid_postfix(u"Fust", -1, u"Fußball", -1, 0, &first, &second, &error); + Expect(error == Success && first == 2 && second == 2, "u_caseInsensitivePrefixMatch partial fold"); + u_caseInsensitivePrefixMatch_nid_postfix(u"ABCd", 4, u"abcD", 4, 0, &first, &second, &error); + Expect(first == 4 && second == 4, "u_caseInsensitivePrefixMatch full match"); + first = second = -1; + error = BufferOverflow; + u_caseInsensitivePrefixMatch_nid_postfix(u"a", 1, u"a", 1, 0, &first, nullptr, &error); + Expect(first == -1 && error == BufferOverflow, "u_caseInsensitivePrefixMatch failed input"); + error = Success; + u_caseInsensitivePrefixMatch_nid_postfix(u"a", 1, u"a", 1, 0, nullptr, nullptr, &error); + ExpectThrow([&] { u_caseInsensitivePrefixMatch_nid_postfix(u"a", 1, u"a", 1, 0, &first, nullptr, &error); }, "u_caseInsensitivePrefixMatch missing second length"); + ExpectThrow([&] { u_caseInsensitivePrefixMatch_nid_postfix(u"a", 1, u"a", 1, 0, &first, &second, nullptr); }, "u_caseInsensitivePrefixMatch null error"); +} + +} + +int main() { + TestMapping(); + TestComparison(); + std::puts("ICU case mapping tests passed"); +} diff --git a/core/libs/prx/libSceIcu/tests/Properties.cpp b/core/libs/prx/libSceIcu/tests/Properties.cpp new file mode 100644 index 000000000000..7bd94ec3998d --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/Properties.cpp @@ -0,0 +1,206 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Expect(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "failed: %s\n", what); + std::abort(); +} + +void ExpectInt(std::int64_t actual, std::int64_t expected, const char* what) { + if (actual == expected) return; + std::fprintf(stderr, "%s returned %lld, expected %lld\n", what, static_cast(actual), static_cast(expected)); + std::abort(); +} + +void ExpectName(const char* actual, const char* expected, const char* what) { + if (expected ? actual && std::strcmp(actual, expected) == 0 : !actual) return; + std::fprintf(stderr, "%s returned %s, expected %s\n", what, actual ? actual : "null", expected ? expected : "null"); + std::abort(); +} + +template +void ExpectNotImplemented(Function function, const char* what) { + try { + function(); + } catch (const std::runtime_error&) { + return; + } + Expect(false, what); +} + +struct Range { + std::int32_t start; + std::int32_t limit; + std::int32_t type; +}; + +struct Ranges { + std::vector seen; + std::size_t stopAfter; +}; + +std::int8_t APS5_VABI Collect(const void* context, std::int32_t start, std::int32_t limit, std::int32_t type) { + auto& ranges = *static_cast(const_cast(context)); + ranges.seen.push_back({start, limit, type}); + return ranges.seen.size() < ranges.stopAfter; +} + +void TestCategories() { + ExpectInt(u_charType_nid_postfix(0x41), 1, "u_charType(A)"); + ExpectInt(u_charType_nid_postfix(0x61), 2, "u_charType(a)"); + ExpectInt(u_charType_nid_postfix(0x1c5), 3, "u_charType(U+01C5)"); + ExpectInt(u_charType_nid_postfix(0x4e00), 5, "u_charType(U+4E00)"); + ExpectInt(u_charType_nid_postfix(0x301), 6, "u_charType(U+0301)"); + ExpectInt(u_charType_nid_postfix(0x30), 9, "u_charType(0)"); + ExpectInt(u_charType_nid_postfix(0x20), 12, "u_charType(space)"); + ExpectInt(u_charType_nid_postfix(0xe000), 17, "u_charType(U+E000)"); + ExpectInt(u_charType_nid_postfix(0xd800), 18, "u_charType(U+D800)"); + ExpectInt(u_charType_nid_postfix(0x1f600), 27, "u_charType(U+1F600)"); + ExpectInt(u_charType_nid_postfix(0x20c1), 25, "u_charType(U+20C1)"); + ExpectInt(u_charType_nid_postfix(0x378), 0, "u_charType(U+0378)"); + ExpectInt(u_charType_nid_postfix(0x110000), 0, "u_charType(U+110000)"); + ExpectInt(u_charType_nid_postfix(-1), 0, "u_charType(-1)"); + + Ranges all{{}, static_cast(-1)}; + u_enumCharTypes_nid_postfix(Collect, &all); + Expect(!all.seen.empty(), "u_enumCharTypes reports ranges"); + Expect(all.seen.front().start == 0 && all.seen.front().limit == 0x20 && all.seen.front().type == 15, "first range is Cc"); + Expect(all.seen.back().limit == 0x110000, "last range ends at U+110000"); + for (std::size_t index = 1; index < all.seen.size(); ++index) + Expect(all.seen[index].start == all.seen[index - 1].limit, "ranges are contiguous"); + Ranges two{{}, 2}; + u_enumCharTypes_nid_postfix(Collect, &two); + ExpectInt(static_cast(two.seen.size()), 2, "u_enumCharTypes stops when the callback returns false"); + u_enumCharTypes_nid_postfix(nullptr, nullptr); +} + +void TestClassifiers() { + Expect(u_islower_nid_postfix(0x61) && !u_islower_nid_postfix(0x41), "u_islower"); + Expect(u_isupper_nid_postfix(0x41) && !u_isupper_nid_postfix(0x61), "u_isupper"); + Expect(u_istitle_nid_postfix(0x1c5) && !u_istitle_nid_postfix(0x41), "u_istitle"); + Expect(u_isdigit_nid_postfix(0x660) && !u_isdigit_nid_postfix(0x41), "u_isdigit"); + Expect(u_isxdigit_nid_postfix(0xff21) && !u_isxdigit_nid_postfix(0x67), "u_isxdigit"); + Expect(u_isalpha_nid_postfix(0x4e00) && !u_isalpha_nid_postfix(0x345), "u_isalpha"); + Expect(u_isalnum_nid_postfix(0x35) && !u_isalnum_nid_postfix(0x21), "u_isalnum"); + Expect(u_isdefined_nid_postfix(0x41) && !u_isdefined_nid_postfix(0x378), "u_isdefined"); + Expect(u_isbase_nid_postfix(0x20dd) && !u_isbase_nid_postfix(0x301), "u_isbase"); + Expect(u_iscntrl_nid_postfix(0x7) && u_iscntrl_nid_postfix(0x2028) && !u_iscntrl_nid_postfix(0x41), "u_iscntrl"); + Expect(u_ispunct_nid_postfix(0x21) && !u_ispunct_nid_postfix(0x24), "u_ispunct"); + Expect(u_isgraph_nid_postfix(0x41) && !u_isgraph_nid_postfix(0x20), "u_isgraph"); + Expect(u_isprint_nid_postfix(0x20) && !u_isprint_nid_postfix(0x7), "u_isprint"); + Expect(u_isIDStart_nid_postfix(0x61) && !u_isIDStart_nid_postfix(0x31), "u_isIDStart"); + Expect(u_isIDPart_nid_postfix(0x31) && !u_isIDPart_nid_postfix(0x2d), "u_isIDPart"); + Expect(u_isIDIgnorable_nid_postfix(0x0) && u_isIDIgnorable_nid_postfix(0x200b) && !u_isIDIgnorable_nid_postfix(0x9), "u_isIDIgnorable"); + Expect(u_isJavaIDStart_nid_postfix(0x24) && !u_isJavaIDStart_nid_postfix(0x31), "u_isJavaIDStart"); + Expect(u_isJavaIDPart_nid_postfix(0x31) && !u_isJavaIDPart_nid_postfix(0x2d), "u_isJavaIDPart"); + Expect(u_isUAlphabetic_nid_postfix(0x345) && !u_isUAlphabetic_nid_postfix(0x31), "u_isUAlphabetic"); + Expect(u_isULowercase_nid_postfix(0x61) && !u_isULowercase_nid_postfix(0x1c5), "u_isULowercase"); + Expect(u_isUUppercase_nid_postfix(0x41) && !u_isUUppercase_nid_postfix(0x1c5), "u_isUUppercase"); +} + +void TestNumbers() { + ExpectInt(u_charDigitValue_nid_postfix(0x663), 3, "u_charDigitValue(U+0663)"); + ExpectInt(u_charDigitValue_nid_postfix(0x41), -1, "u_charDigitValue(A)"); + ExpectInt(u_digit_nid_postfix(0x7a, 36), 35, "u_digit(z, 36)"); + ExpectInt(u_digit_nid_postfix(0xff21, 16), 10, "u_digit(U+FF21, 16)"); + ExpectInt(u_digit_nid_postfix(0x38, 8), -1, "u_digit(8, 8)"); + ExpectInt(u_digit_nid_postfix(0x30, 1), -1, "u_digit(0, 1)"); + Expect(u_getNumericValue_nid_postfix(0xbd) == 0.5, "u_getNumericValue(U+00BD)"); + Expect(u_getNumericValue_nid_postfix(0x216b) == 12.0, "u_getNumericValue(U+216B)"); + Expect(u_getNumericValue_nid_postfix(0x41) == -123456789.0, "u_getNumericValue(A)"); + std::uint8_t version[4] = {9, 9, 9, 9}; + u_charAge_nid_postfix(0x41, version); + Expect(version[0] == 1 && version[1] == 1 && version[2] == 0 && version[3] == 0, "u_charAge(A)"); + u_charAge_nid_postfix(0x20ac, version); + Expect(version[0] == 2 && version[1] == 1, "u_charAge(U+20AC)"); + u_charAge_nid_postfix(0x20c1, version); + Expect(version[0] == 17 && version[1] == 0, "u_charAge(U+20C1)"); + u_charAge_nid_postfix(0x41, nullptr); + ExpectInt(u_getCombiningClass_nid_postfix(0x301), 230, "u_getCombiningClass(U+0301)"); + ExpectInt(u_getCombiningClass_nid_postfix(0x316), 220, "u_getCombiningClass(U+0316)"); + ExpectInt(u_getCombiningClass_nid_postfix(0x41), 0, "u_getCombiningClass(A)"); +} + +void TestBidiAndCase() { + ExpectInt(u_charDirection_nid_postfix(0x41), 0, "u_charDirection(A)"); + ExpectInt(u_charDirection_nid_postfix(0x5d0), 1, "u_charDirection(U+05D0)"); + ExpectInt(u_charDirection_nid_postfix(0x31), 2, "u_charDirection(1)"); + ExpectInt(u_charDirection_nid_postfix(0x627), 13, "u_charDirection(U+0627)"); + Expect(u_isMirrored_nid_postfix(0x28) && !u_isMirrored_nid_postfix(0x41), "u_isMirrored"); + ExpectInt(u_charMirror_nid_postfix(0x28), 0x29, "u_charMirror(()"); + ExpectInt(u_charMirror_nid_postfix(0x226e), 0x226f, "u_charMirror(U+226E)"); + ExpectInt(u_charMirror_nid_postfix(0x41), 0x41, "u_charMirror(A)"); + ExpectInt(u_getBidiPairedBracket_nid_postfix(0x5b), 0x5d, "u_getBidiPairedBracket([)"); + ExpectInt(u_getBidiPairedBracket_nid_postfix(0x226e), 0x226e, "u_getBidiPairedBracket(U+226E)"); + ExpectInt(u_foldCase_nid_postfix(0x41, 0), 0x61, "u_foldCase(A)"); + ExpectInt(u_foldCase_nid_postfix(0x49, 1), 0x131, "u_foldCase(I, exclude special I)"); + ExpectInt(u_foldCase_nid_postfix(0x130, 0), 0x130, "u_foldCase(U+0130)"); +} + +void TestPropertyNames() { + ExpectInt(u_getPropertyEnum_nid_postfix("Alphabetic"), 0, "u_getPropertyEnum(Alphabetic)"); + ExpectInt(u_getPropertyEnum_nid_postfix("gc"), 0x1005, "u_getPropertyEnum(gc)"); + ExpectInt(u_getPropertyEnum_nid_postfix("general category"), 0x1005, "u_getPropertyEnum(general category)"); + ExpectInt(u_getPropertyEnum_nid_postfix("nonsense"), -1, "u_getPropertyEnum(nonsense)"); + ExpectInt(u_getPropertyEnum_nid_postfix(nullptr), -1, "u_getPropertyEnum(null)"); + ExpectName(u_getPropertyName_nid_postfix(0x1005, 0), "gc", "u_getPropertyName(gc, short)"); + ExpectName(u_getPropertyName_nid_postfix(0x1005, 1), "General_Category", "u_getPropertyName(gc, long)"); + ExpectName(u_getPropertyName_nid_postfix(0x1005, 9), nullptr, "u_getPropertyName(gc, 9)"); + ExpectName(u_getPropertyName_nid_postfix(0x6000, 0), nullptr, "u_getPropertyName(0x6000)"); + ExpectInt(u_getPropertyValueEnum_nid_postfix(0x1005, "Lu"), 1, "u_getPropertyValueEnum(gc, Lu)"); + ExpectInt(u_getPropertyValueEnum_nid_postfix(0x100a, "Latn"), 25, "u_getPropertyValueEnum(sc, Latn)"); + ExpectInt(u_getPropertyValueEnum_nid_postfix(0x1005, nullptr), -1, "u_getPropertyValueEnum(gc, null)"); + ExpectInt(u_getPropertyValueEnum_nid_postfix(0x6000, "Lu"), -1, "u_getPropertyValueEnum(0x6000, Lu)"); + ExpectName(u_getPropertyValueName_nid_postfix(0x1005, 1, 1), "Uppercase_Letter", "u_getPropertyValueName(gc, Lu, long)"); + ExpectName(u_getPropertyValueName_nid_postfix(0x100a, 25, 0), "Latn", "u_getPropertyValueName(sc, Latin, short)"); + ExpectName(u_getPropertyValueName_nid_postfix(0x1005, 99, 0), nullptr, "u_getPropertyValueName(gc, 99)"); +} + +void TestPropertyValues() { + ExpectInt(u_getIntPropertyMinValue_nid_postfix(0x1005), 0, "u_getIntPropertyMinValue(gc)"); + ExpectInt(u_getIntPropertyMinValue_nid_postfix(-5), 0, "u_getIntPropertyMinValue(-5)"); + ExpectInt(u_getIntPropertyMaxValue_nid_postfix(0), 1, "u_getIntPropertyMaxValue(Alphabetic)"); + ExpectInt(u_getIntPropertyMaxValue_nid_postfix(57), 1, "u_getIntPropertyMaxValue(Emoji)"); + ExpectInt(u_getIntPropertyMaxValue_nid_postfix(0x1005), 29, "u_getIntPropertyMaxValue(gc)"); + ExpectInt(u_getIntPropertyMaxValue_nid_postfix(0x100f), 2, "u_getIntPropertyMaxValue(NFKC_QC)"); + ExpectInt(u_getIntPropertyMaxValue_nid_postfix(0x6000), -1, "u_getIntPropertyMaxValue(0x6000)"); + ExpectNotImplemented([] { u_getIntPropertyMaxValue_nid_postfix(0x1016); }, "u_getIntPropertyMaxValue(InPC)"); + + Expect(u_hasBinaryProperty_nid_postfix(0x41, 0) && !u_hasBinaryProperty_nid_postfix(0x31, 0), "u_hasBinaryProperty(Alphabetic)"); + Expect(u_hasBinaryProperty_nid_postfix(0x2ffe, 72), "u_hasBinaryProperty(IDS_Unary_Operator)"); + Expect(u_hasBinaryProperty_nid_postfix(0xc5, 9) == 0 && u_hasBinaryProperty_nid_postfix(0x340, 9), "u_hasBinaryProperty(Full_Composition_Exclusion)"); + Expect(u_hasBinaryProperty_nid_postfix(0x41, 54) && !u_hasBinaryProperty_nid_postfix(0x61, 54), "u_hasBinaryProperty(Changes_When_Casefolded)"); + Expect(!u_hasBinaryProperty_nid_postfix(0x41, 0x100f) && !u_hasBinaryProperty_nid_postfix(0x41, -1), "u_hasBinaryProperty outside binary properties"); + ExpectNotImplemented([] { u_hasBinaryProperty_nid_postfix(0x1f600, 57); }, "u_hasBinaryProperty(Emoji)"); + ExpectNotImplemented([] { u_hasBinaryProperty_nid_postfix(0x41, 40); }, "u_hasBinaryProperty(NFKC_Inert)"); + + ExpectInt(u_getIntPropertyValue_nid_postfix(0x41, 0x1005), 1, "u_getIntPropertyValue(A, gc)"); + ExpectInt(u_getIntPropertyValue_nid_postfix(0x41, 0x100a), 25, "u_getIntPropertyValue(A, sc)"); + ExpectInt(u_getIntPropertyValue_nid_postfix(0x41, 0x2000), 2, "u_getIntPropertyValue(A, gcm)"); + ExpectInt(u_getIntPropertyValue_nid_postfix(0x301, 0x1002), 230, "u_getIntPropertyValue(U+0301, ccc)"); + ExpectInt(u_getIntPropertyValue_nid_postfix(0x301, 0x100e), 2, "u_getIntPropertyValue(U+0301, NFC_QC)"); + ExpectInt(u_getIntPropertyValue_nid_postfix(0x41, 0), 1, "u_getIntPropertyValue(A, Alphabetic)"); + ExpectInt(u_getIntPropertyValue_nid_postfix(0x41, 0x6000), 0, "u_getIntPropertyValue(A, 0x6000)"); + ExpectNotImplemented([] { u_getIntPropertyValue_nid_postfix(0x41, 0x1018); }, "u_getIntPropertyValue(vo)"); + ExpectNotImplemented([] { u_getIntPropertyValue_nid_postfix(0x41, 0x100f); }, "u_getIntPropertyValue(NFKC_QC)"); +} + +} + +int main() { + TestCategories(); + TestClassifiers(); + TestNumbers(); + TestBidiAndCase(); + TestPropertyNames(); + TestPropertyValues(); + std::puts("ICU property tests passed"); +} diff --git a/docs/dev/debt/libSceIcu.md b/docs/dev/debt/libSceIcu.md index be476d498e2c..377e05fbd3da 100644 --- a/docs/dev/debt/libSceIcu.md +++ b/docs/dev/debt/libSceIcu.md @@ -20,6 +20,8 @@ - [Default-converter string copy helpers](../../../core/libs/prx/libSceIcu/Invariant.cpp) `u_austrcpy` and `u_austrncpy` are unverified on a console and throw `NotImplemented_nid_no_patch` on code units above `0x7f`. Their names and NIDs come from the PS5 3.20 inventory; signatures and ASCII (`0x01`–`0x7f`) behavior follow ICU 78.1's [ustr_cnv.cpp](https://github.com/unicode-org/icu/blob/release-78.1/icu4c/source/common/ustr_cnv.cpp), unchanged since 65.1, without assuming the console's default converter for non-ASCII units. `u_austrncpy` writes a single NUL when the source ends before the count without zero-padding the remainder. Null buffers with `u_austrcpy` or a positive count throw an invalid-argument exception, whereas ICU dereferences in `u_austrcpy` and returns the destination unchanged in `u_austrncpy`. Closes when: compared with the console, including the default converter for non-ASCII units. +- [Character property](../../../core/libs/prx/libSceIcu/Properties.cpp) and [case mapping](../../../core/libs/prx/libSceIcu/CaseMapping.cpp) exports throw `NotImplemented_nid_no_patch` where ICU 78.1 needs a data file this build does not have: `u_hasBinaryProperty` and `u_getIntPropertyValue` for emoji, NFKC, NFKC_Casefold, Indic positional and syllabic category and vertical orientation properties, `u_getIntPropertyMaxValue` for the last three, and `u_strToLower` and `u_strToUpper` with a null (default) locale. Prototypes follow the ICU 78.1 headers; other inputs follow its compiled-in tables from the [3rdparty/icu](https://github.com/unicode-org/icu/tree/049e0d6a4206/icu4c/source/common) submodule; null pointers ICU dereferences and lengths below -1 throw `std::invalid_argument`. Closes when: compared with the console and the data files are available. + ### Known export names; ABI prototypes unverified -- The 96 `sceIcuUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceIcu/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- The 47 `sceIcuUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceIcu/Export.cpp) are present in the older export inventory cited under ICU export availability. Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. From 491163f9da8ad2c48459990c3141d616f608e492 Mon Sep 17 00:00:00 2001 From: KernelPanicNT Date: Wed, 4 Nov 2026 14:16:10 +0000 Subject: [PATCH 2/3] fix(libSceIcu): include only the export headers in ICU-backed sources Main's Iterator.hpp declares the guest-ABI UCharIterator and UCharIteratorOrigin in the global namespace, and the umbrella libSceIcu.hpp pulls it in. CaseMapping.cpp also includes ICU's ustring.h, which defines ICU's own UCharIterator, so the merged tree failed to compile. Properties.cpp and CaseMapping.cpp now include only their own export headers. --- core/libs/prx/libSceIcu/CaseMapping.cpp | 2 +- core/libs/prx/libSceIcu/Properties.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceIcu/CaseMapping.cpp b/core/libs/prx/libSceIcu/CaseMapping.cpp index 8b6d952cfe2f..29297640af1f 100644 --- a/core/libs/prx/libSceIcu/CaseMapping.cpp +++ b/core/libs/prx/libSceIcu/CaseMapping.cpp @@ -1,4 +1,4 @@ -#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libSceIcu/CaseMapping.hpp" #include "prx/libc/include/General.hpp" #include "ucase.h" #include "ucasemap_imp.h" diff --git a/core/libs/prx/libSceIcu/Properties.cpp b/core/libs/prx/libSceIcu/Properties.cpp index 8873d7fe6fd7..a315e94bf3c9 100644 --- a/core/libs/prx/libSceIcu/Properties.cpp +++ b/core/libs/prx/libSceIcu/Properties.cpp @@ -1,4 +1,4 @@ -#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libSceIcu/Properties.hpp" #include "prx/libc/include/General.hpp" #include "unicode/uchar.h" #include "uprops.h" From 004279dd53f1489267120f26a15961d9b386b6c0 Mon Sep 17 00:00:00 2001 From: KernelPanicNT Date: Wed, 4 Nov 2026 14:16:11 +0000 Subject: [PATCH 3/3] test(libSceIcu): check property exports against UnicodeData.txt guest_icu_unicode_data reads UnicodeData.txt from the pinned ICU submodule at test time (path passed by CMake) and fails if it is missing or malformed. For every code point 0..10FFFF it compares u_charType, u_getCombiningClass, u_charDigitValue and u_charDirection with the file, expanding First/Last ranges and treating unlisted code points as Cn, class 0 and no digit. u_charDirection is skipped for unlisted code points because the submodule has no DerivedBidiClass.txt. u_enumCharTypes ranges are checked against the same table, and INT32_MIN, -1, 0x110000 and INT32_MAX against ICU's trie error values. --- core/libs/prx/libSceIcu/CMakeLists.txt | 14 ++ core/libs/prx/libSceIcu/tests/UnicodeData.cpp | 165 ++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 core/libs/prx/libSceIcu/tests/UnicodeData.cpp diff --git a/core/libs/prx/libSceIcu/CMakeLists.txt b/core/libs/prx/libSceIcu/CMakeLists.txt index a3256e871015..320caf35e823 100644 --- a/core/libs/prx/libSceIcu/CMakeLists.txt +++ b/core/libs/prx/libSceIcu/CMakeLists.txt @@ -190,3 +190,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_unicode_data_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/UnicodeData.cpp) + target_include_directories(guest_icu_unicode_data_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_compile_definitions(guest_icu_unicode_data_tests PRIVATE ANYPS5_ICU_UNICODE_DATA="${ICU_SOURCE_DIR}/data/unidata/UnicodeData.txt") + target_link_libraries(guest_icu_unicode_data_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_unicode_data_tests) + add_test(NAME guest_icu_unicode_data COMMAND guest_icu_unicode_data_tests) + set_tests_properties(guest_icu_unicode_data PROPERTIES TIMEOUT 60) + if(WIN32) + set_tests_properties(guest_icu_unicode_data PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceIcu/tests/UnicodeData.cpp b/core/libs/prx/libSceIcu/tests/UnicodeData.cpp new file mode 100644 index 000000000000..e9eae15cd364 --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/UnicodeData.cpp @@ -0,0 +1,165 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::int32_t kCodePoints = 0x110000; + +struct Properties { + std::int32_t category = 0; + std::int32_t direction = -1; + std::int32_t combining = 0; + std::int32_t digit = -1; +}; + +[[noreturn]] void Fail(const std::string& what) { + std::fprintf(stderr, "failed: %s\n", what.c_str()); + std::abort(); +} + +std::int32_t Index(const char* const* names, std::size_t count, const std::string& name, const std::string& line) { + for (std::size_t index = 0; index < count; ++index) + if (name == names[index]) return static_cast(index); + Fail("unknown value " + name + " in " + line); +} + +std::int32_t Category(const std::string& name, const std::string& line) { + static const char* const names[] = {"Cn", "Lu", "Ll", "Lt", "Lm", "Lo", "Mn", "Me", "Mc", "Nd", "Nl", "No", "Zs", "Zl", "Zp", + "Cc", "Cf", "Co", "Cs", "Pd", "Ps", "Pe", "Pc", "Po", "Sm", "Sc", "Sk", "So", "Pi", "Pf"}; + return Index(names, sizeof(names) / sizeof(names[0]), name, line); +} + +std::int32_t Direction(const std::string& name, const std::string& line) { + static const char* const names[] = {"L", "R", "EN", "ES", "ET", "AN", "CS", "B", "S", "WS", "ON", "LRE", + "LRO", "AL", "RLE", "RLO", "PDF", "NSM", "BN", "FSI", "LRI", "RLI", "PDI"}; + return Index(names, sizeof(names) / sizeof(names[0]), name, line); +} + +std::int32_t Number(const std::string& text, int base, const std::string& line) { + if (text.empty()) Fail("empty number in " + line); + std::size_t used = 0; + long value = 0; + try { + value = std::stol(text, &used, base); + } catch (...) { + Fail("bad number " + text + " in " + line); + } + if (used != text.size()) Fail("bad number " + text + " in " + line); + return static_cast(value); +} + +bool EndsWith(const std::string& text, const std::string& suffix) { + return text.size() >= suffix.size() && text.compare(text.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +std::vector Load(std::int32_t& listed) { + std::ifstream file(ANYPS5_ICU_UNICODE_DATA); + if (!file) Fail(std::string("cannot open ") + ANYPS5_ICU_UNICODE_DATA); + std::vector table(kCodePoints); + std::vector seen(kCodePoints); + std::int32_t first = -1; + std::int32_t previous = -1; + std::string line; + listed = 0; + while (std::getline(file, line)) { + if (!line.empty() && line.back() == '\r') line.pop_back(); + if (line.empty()) continue; + std::vector fields; + std::size_t start = 0; + for (std::size_t end; (end = line.find(';', start)) != std::string::npos; start = end + 1) fields.push_back(line.substr(start, end - start)); + fields.push_back(line.substr(start)); + if (fields.size() != 15) Fail("expected 15 fields in " + line); + const std::int32_t code = Number(fields[0], 16, line); + if (code <= previous || code >= kCodePoints) Fail("code point out of order in " + line); + previous = code; + Properties properties; + properties.category = Category(fields[2], line); + properties.combining = Number(fields[3], 10, line); + properties.direction = Direction(fields[4], line); + properties.digit = fields[6].empty() ? -1 : Number(fields[6], 10, line); + if (EndsWith(fields[1], ", First>")) { + if (first >= 0) Fail("nested range in " + line); + first = code; + continue; + } + std::int32_t low = code; + if (EndsWith(fields[1], ", Last>")) { + if (first < 0) Fail("range end without start in " + line); + low = first; + first = -1; + } else if (first >= 0) { + Fail("range start without end before " + line); + } + for (std::int32_t character = low; character <= code; ++character) { + table[character] = properties; + seen[character] = true; + ++listed; + } + } + if (first >= 0) Fail("unterminated range at end of file"); + if (!seen[0x41] || !seen[0x10fffd] || !seen[0xd800] || !seen[0x4e00]) Fail("UnicodeData.txt is incomplete"); + return table; +} + +struct Mismatches { + std::int32_t count = 0; + + void Check(const char* what, std::int32_t character, std::int64_t actual, std::int64_t expected) { + if (actual == expected) return; + if (++count <= 20) + std::fprintf(stderr, "%s(0x%x) returned %lld, expected %lld\n", what, static_cast(character), + static_cast(actual), static_cast(expected)); + } +}; + +struct Enumerated { + const std::vector* table; + Mismatches* mismatches; + std::int32_t next; +}; + +std::int8_t APS5_VABI CheckRange(const void* context, std::int32_t start, std::int32_t limit, std::int32_t type) { + auto& enumerated = *static_cast(const_cast(context)); + enumerated.mismatches->Check("u_enumCharTypes start", start, start, enumerated.next); + for (std::int32_t character = start; character < limit && character < kCodePoints; ++character) + enumerated.mismatches->Check("u_enumCharTypes", character, type, (*enumerated.table)[character].category); + enumerated.next = limit; + return 1; +} + +} + +int main() { + std::int32_t listed = 0; + const std::vector table = Load(listed); + Mismatches mismatches; + for (std::int32_t character = 0; character < kCodePoints; ++character) { + const Properties& expected = table[character]; + mismatches.Check("u_charType", character, u_charType_nid_postfix(character), expected.category); + mismatches.Check("u_getCombiningClass", character, u_getCombiningClass_nid_postfix(character), expected.combining); + mismatches.Check("u_charDigitValue", character, u_charDigitValue_nid_postfix(character), expected.digit); + if (expected.direction >= 0) + mismatches.Check("u_charDirection", character, u_charDirection_nid_postfix(character), expected.direction); + } + Enumerated enumerated{&table, &mismatches, 0}; + u_enumCharTypes_nid_postfix(CheckRange, &enumerated); + mismatches.Check("u_enumCharTypes end", 0, enumerated.next, kCodePoints); + for (const std::int32_t character : {std::numeric_limits::min(), -1, kCodePoints, std::numeric_limits::max()}) { + mismatches.Check("u_charType", character, u_charType_nid_postfix(character), 0); + mismatches.Check("u_getCombiningClass", character, u_getCombiningClass_nid_postfix(character), 0); + mismatches.Check("u_charDigitValue", character, u_charDigitValue_nid_postfix(character), -1); + mismatches.Check("u_charDirection", character, u_charDirection_nid_postfix(character), 0); + } + if (mismatches.count) { + std::fprintf(stderr, "%d mismatches against UnicodeData.txt\n", mismatches.count); + return 1; + } + std::printf("ICU properties match UnicodeData.txt for all %d code points (%d listed)\n", kCodePoints, listed); +}