@@ -992,172 +992,6 @@ typedef union sz_u512_vec_t {
992992
993993#pragma endregion
994994
995- #pragma region UTF8
996-
997- /* *
998- * @brief Extracts one UTF-8 codepoint from a UTF-8 string into a 32-bit unsigned integer.
999- * @param[in] utf8 Pointer to the beginning of a valid UTF-8 encoded string.
1000- * @param[out] runes Output parameter to store the extracted UTF-32 codepoint.
1001- * @param[out] runes_lengths Output parameter to store the length of the UTF-8 codepoint in bytes (1-4).
1002- * @warning Assumes valid UTF-8 input. Use `sz_utf8_valid()` first if validation is needed.
1003- * @note This function does not perform any bounds checking on the input string.
1004- */
1005- SZ_PUBLIC void sz_rune_parse (sz_cptr_t utf8, sz_rune_t *runes, sz_rune_length_t *runes_lengths) {
1006- sz_u8_t const *u8s = (sz_u8_t const *)utf8;
1007- sz_u8_t lead = *u8s++;
1008-
1009- // Branchless UTF-8 length detection using arithmetic.
1010- // The 3 comparisons are independent and can execute in parallel on superscalar CPUs.
1011- // CLZ-based approach was also considered but has complications with ASCII handling.
1012- sz_rune_length_t len = (sz_rune_length_t )(1 + (lead >= 0xC0U ) + (lead >= 0xE0U ) + (lead >= 0xF0U ));
1013-
1014- // Extract rune bits - switch compiles to efficient jump table.
1015- // Assumes valid UTF-8 input; use sz_utf8_valid() first if validation needed.
1016- sz_rune_t rune;
1017- switch (len) {
1018- // Single-byte rune (0xxxxxxx)
1019- case 1 : rune = lead; break ;
1020- // Two-byte rune (110xxxxx 10xxxxxx)
1021- case 2 : rune = (lead & 0x1FU ) << 6 | (u8s[0 ] & 0x3FU ); break ;
1022- // Three-byte rune (1110xxxx 10xxxxxx 10xxxxxx)
1023- case 3 : rune = (lead & 0x0FU ) << 12 | (u8s[0 ] & 0x3FU ) << 6 | (u8s[1 ] & 0x3FU ); break ;
1024- // Four-byte rune (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
1025- default : rune = (lead & 0x07U ) << 18 | (u8s[0 ] & 0x3FU ) << 12 | (u8s[1 ] & 0x3FU ) << 6 | (u8s[2 ] & 0x3FU ); break ;
1026- }
1027-
1028- *runes = rune;
1029- *runes_lengths = len;
1030- }
1031-
1032- /* *
1033- * @brief Extracts a UTF-8 codepoint from a string, scanning backwards from the given position.
1034- * @param[in] utf8_end Pointer to one past the last byte of the UTF-8 sequence to parse.
1035- * @param[out] rune Output parameter to store the extracted UTF-32 codepoint.
1036- * @param[out] rune_length Output parameter to store the length of the UTF-8 codepoint in bytes (1-4).
1037- * @warning Assumes valid UTF-8 input. Use `sz_utf8_valid()` first if validation is needed.
1038- * @note This function does not perform any bounds checking on the input string.
1039- */
1040- SZ_PUBLIC void sz_rune_rparse (sz_cptr_t utf8_end, sz_rune_t *rune, sz_rune_length_t *rune_length) {
1041- sz_u8_t const *u8s = (sz_u8_t const *)utf8_end;
1042-
1043- // Scan backwards to find the lead byte (not a continuation byte 10xxxxxx)
1044- sz_rune_length_t len = 1 ;
1045- for (--u8s; (*u8s & 0xC0 ) == 0x80 && len < 4 ; --u8s, ++len) {}
1046-
1047- // Now u8s points to the lead byte, len is the sequence length
1048- sz_rune_parse ((sz_cptr_t )u8s, rune, rune_length);
1049- sz_assert_ (*rune_length == len && " Inconsistent rune length detected in sz_rune_rparse." );
1050- }
1051-
1052- /* *
1053- * @brief Validates if a UTF-8 string contains only valid UTF-8 sequences.
1054- * @param text Pointer to the UTF-8 string to validate.
1055- * @param length Length of the string in bytes.
1056- * @return sz_true_k if the string contains only valid UTF-8, sz_false_k otherwise.
1057- */
1058- SZ_PUBLIC sz_bool_t sz_utf8_valid (sz_cptr_t text, sz_size_t length) {
1059- sz_u8_t const *text_u8 = (sz_u8_t const *)text;
1060- sz_u8_t const *end_u8 = text_u8 + length;
1061-
1062- while (text_u8 < end_u8) {
1063- sz_u8_t byte1 = *text_u8;
1064-
1065- // 1-byte sequence (0x00-0x7F)
1066- if (byte1 <= 0x7F ) { text_u8 += 1 ; }
1067-
1068- // 2-byte sequence (0xC2-0xDF)
1069- else if (byte1 >= 0xC2 && byte1 <= 0xDF ) {
1070- if (text_u8 + 1 >= end_u8) return sz_false_k;
1071- sz_u8_t byte2 = text_u8[1 ];
1072- if ((byte2 & 0xC0 ) != 0x80 ) return sz_false_k; // Invalid continuation
1073- text_u8 += 2 ;
1074- }
1075-
1076- // 3-byte sequence (0xE0-0xEF)
1077- else if (byte1 >= 0xE0 && byte1 <= 0xEF ) {
1078- if (text_u8 + 2 >= end_u8) return sz_false_k;
1079- sz_u8_t byte2 = text_u8[1 ];
1080- sz_u8_t byte3 = text_u8[2 ];
1081- if ((byte2 & 0xC0 ) != 0x80 || (byte3 & 0xC0 ) != 0x80 ) return sz_false_k;
1082-
1083- // Check for overlong encodings and surrogates
1084- if (byte1 == 0xE0 && byte2 < 0xA0 ) return sz_false_k; // Overlong
1085- if (byte1 == 0xED && byte2 >= 0xA0 ) return sz_false_k; // Surrogate (U+D800-U+DFFF)
1086-
1087- text_u8 += 3 ;
1088- }
1089-
1090- // 4-byte sequence (0xF0-0xF4)
1091- else if (byte1 >= 0xF0 && byte1 <= 0xF4 ) {
1092- if (text_u8 + 3 >= end_u8) return sz_false_k;
1093- sz_u8_t byte2 = text_u8[1 ];
1094- sz_u8_t byte3 = text_u8[2 ];
1095- sz_u8_t byte4 = text_u8[3 ];
1096- if ((byte2 & 0xC0 ) != 0x80 || (byte3 & 0xC0 ) != 0x80 || (byte4 & 0xC0 ) != 0x80 ) return sz_false_k;
1097-
1098- // Check for overlong and out-of-range
1099- if (byte1 == 0xF0 && byte2 < 0x90 ) return sz_false_k; // Overlong
1100- if (byte1 == 0xF4 && byte2 >= 0x90 ) return sz_false_k; // > U+10FFFF
1101-
1102- text_u8 += 4 ;
1103- }
1104-
1105- // Invalid lead byte
1106- else { return sz_false_k; }
1107- }
1108-
1109- return sz_true_k;
1110- }
1111-
1112- /* *
1113- * @brief Encode a UTF-32 codepoint to UTF-8, outputting 1-4 bytes.
1114- * @param[in] rune The UTF-32 codepoint to encode.
1115- * @param[out] utf8s Output buffer (must have space for at least 4 bytes).
1116- * @return Number of bytes written (1-4), or 0 if the codepoint is invalid.
1117- */
1118- SZ_PUBLIC sz_rune_length_t sz_rune_export (sz_rune_t rune, sz_u8_t *utf8s) {
1119- if (rune <= 0x7F ) {
1120- utf8s[0 ] = (sz_u8_t )rune;
1121- return sz_utf8_rune_1byte_k;
1122- }
1123- else if (rune <= 0x7FF ) {
1124- utf8s[0 ] = (sz_u8_t )(0xC0 | (rune >> 6 ));
1125- utf8s[1 ] = (sz_u8_t )(0x80 | (rune & 0x3F ));
1126- return sz_utf8_rune_2bytes_k;
1127- }
1128- else if (rune <= 0xFFFF ) {
1129- // Reject surrogate codepoints
1130- if (rune >= 0xD800 && rune <= 0xDFFF ) return sz_utf8_invalid_k;
1131- utf8s[0 ] = (sz_u8_t )(0xE0 | (rune >> 12 ));
1132- utf8s[1 ] = (sz_u8_t )(0x80 | ((rune >> 6 ) & 0x3F ));
1133- utf8s[2 ] = (sz_u8_t )(0x80 | (rune & 0x3F ));
1134- return sz_utf8_rune_3bytes_k;
1135- }
1136- else if (rune <= 0x10FFFF ) {
1137- utf8s[0 ] = (sz_u8_t )(0xF0 | (rune >> 18 ));
1138- utf8s[1 ] = (sz_u8_t )(0x80 | ((rune >> 12 ) & 0x3F ));
1139- utf8s[2 ] = (sz_u8_t )(0x80 | ((rune >> 6 ) & 0x3F ));
1140- utf8s[3 ] = (sz_u8_t )(0x80 | (rune & 0x3F ));
1141- return sz_utf8_rune_4bytes_k;
1142- }
1143- return sz_utf8_invalid_k;
1144- }
1145-
1146- /* *
1147- * @brief Exports a UTF8 string into a UTF32 buffer.
1148- * @warning The result is undefined id the UTF8 string is corrupted.
1149- * @return The length in the number of codepoints.
1150- */
1151- SZ_PUBLIC sz_size_t sz_runes_parse (sz_cptr_t utf8, sz_size_t utf8_length, sz_rune_t *utf32) {
1152- sz_cptr_t const end = utf8 + utf8_length;
1153- sz_size_t count = 0 ;
1154- sz_rune_length_t rune_length;
1155- for (; utf8 != end; utf8 += rune_length, utf32++, count++) sz_rune_parse (utf8, utf32, &rune_length);
1156- return count;
1157- }
1158-
1159- #pragma endregion
1160-
1161995#pragma region String Sequences API
1162996
1163997/* * @brief Signature of `sz_sequence_t::get_start` used to get the start of a member string at a given index. */
@@ -1603,6 +1437,172 @@ SZ_INTERNAL sz_u64_vec_t sz_u64_load(sz_cptr_t ptr) {
16031437#endif
16041438}
16051439
1440+ #pragma region UTF8
1441+
1442+ /* *
1443+ * @brief Validates if a UTF-8 string contains only valid UTF-8 sequences.
1444+ * @param[in] text Pointer to the UTF-8 string to validate.
1445+ * @param[in] length Length of the string in bytes.
1446+ * @return sz_true_k if the string contains only valid UTF-8, sz_false_k otherwise.
1447+ */
1448+ SZ_PUBLIC sz_bool_t sz_utf8_valid (sz_cptr_t text, sz_size_t length) {
1449+ sz_u8_t const *text_u8 = (sz_u8_t const *)text;
1450+ sz_u8_t const *end_u8 = text_u8 + length;
1451+
1452+ while (text_u8 < end_u8) {
1453+ sz_u8_t byte1 = *text_u8;
1454+
1455+ // 1-byte sequence (0x00-0x7F)
1456+ if (byte1 <= 0x7F ) { text_u8 += 1 ; }
1457+
1458+ // 2-byte sequence (0xC2-0xDF)
1459+ else if (byte1 >= 0xC2 && byte1 <= 0xDF ) {
1460+ if (text_u8 + 1 >= end_u8) return sz_false_k;
1461+ sz_u8_t byte2 = text_u8[1 ];
1462+ if ((byte2 & 0xC0 ) != 0x80 ) return sz_false_k; // Invalid continuation
1463+ text_u8 += 2 ;
1464+ }
1465+
1466+ // 3-byte sequence (0xE0-0xEF)
1467+ else if (byte1 >= 0xE0 && byte1 <= 0xEF ) {
1468+ if (text_u8 + 2 >= end_u8) return sz_false_k;
1469+ sz_u8_t byte2 = text_u8[1 ];
1470+ sz_u8_t byte3 = text_u8[2 ];
1471+ if ((byte2 & 0xC0 ) != 0x80 || (byte3 & 0xC0 ) != 0x80 ) return sz_false_k;
1472+
1473+ // Check for overlong encodings and surrogates
1474+ if (byte1 == 0xE0 && byte2 < 0xA0 ) return sz_false_k; // Overlong
1475+ if (byte1 == 0xED && byte2 >= 0xA0 ) return sz_false_k; // Surrogate (U+D800-U+DFFF)
1476+
1477+ text_u8 += 3 ;
1478+ }
1479+
1480+ // 4-byte sequence (0xF0-0xF4)
1481+ else if (byte1 >= 0xF0 && byte1 <= 0xF4 ) {
1482+ if (text_u8 + 3 >= end_u8) return sz_false_k;
1483+ sz_u8_t byte2 = text_u8[1 ];
1484+ sz_u8_t byte3 = text_u8[2 ];
1485+ sz_u8_t byte4 = text_u8[3 ];
1486+ if ((byte2 & 0xC0 ) != 0x80 || (byte3 & 0xC0 ) != 0x80 || (byte4 & 0xC0 ) != 0x80 ) return sz_false_k;
1487+
1488+ // Check for overlong and out-of-range
1489+ if (byte1 == 0xF0 && byte2 < 0x90 ) return sz_false_k; // Overlong
1490+ if (byte1 == 0xF4 && byte2 >= 0x90 ) return sz_false_k; // > U+10FFFF
1491+
1492+ text_u8 += 4 ;
1493+ }
1494+
1495+ // Invalid lead byte
1496+ else { return sz_false_k; }
1497+ }
1498+
1499+ return sz_true_k;
1500+ }
1501+
1502+ /* *
1503+ * @brief Extracts one UTF-8 codepoint from a UTF-8 string into a 32-bit unsigned integer.
1504+ * @param[in] utf8 Pointer to the beginning of a valid UTF-8 encoded string.
1505+ * @param[out] runes Output parameter to store the extracted UTF-32 codepoint.
1506+ * @param[out] runes_lengths Output parameter to store the length of the UTF-8 codepoint in bytes (1-4).
1507+ * @warning Assumes valid UTF-8 input. Use `sz_utf8_valid()` first if validation is needed.
1508+ * @note This function does not perform any bounds checking on the input string.
1509+ */
1510+ SZ_INTERNAL void sz_rune_parse (sz_cptr_t utf8, sz_rune_t *runes, sz_rune_length_t *runes_lengths) {
1511+ sz_u8_t const *u8s = (sz_u8_t const *)utf8;
1512+ sz_u8_t lead = *u8s++;
1513+
1514+ // Branchless UTF-8 length detection using arithmetic.
1515+ // The 3 comparisons are independent and can execute in parallel on superscalar CPUs.
1516+ // CLZ-based approach was also considered but has complications with ASCII handling.
1517+ sz_rune_length_t len = (sz_rune_length_t )(1 + (lead >= 0xC0U ) + (lead >= 0xE0U ) + (lead >= 0xF0U ));
1518+
1519+ // Extract rune bits - switch compiles to efficient jump table.
1520+ // Assumes valid UTF-8 input; use sz_utf8_valid() first if validation needed.
1521+ sz_rune_t rune;
1522+ switch (len) {
1523+ // Single-byte rune (0xxxxxxx)
1524+ case 1 : rune = lead; break ;
1525+ // Two-byte rune (110xxxxx 10xxxxxx)
1526+ case 2 : rune = (lead & 0x1FU ) << 6 | (u8s[0 ] & 0x3FU ); break ;
1527+ // Three-byte rune (1110xxxx 10xxxxxx 10xxxxxx)
1528+ case 3 : rune = (lead & 0x0FU ) << 12 | (u8s[0 ] & 0x3FU ) << 6 | (u8s[1 ] & 0x3FU ); break ;
1529+ // Four-byte rune (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
1530+ default : rune = (lead & 0x07U ) << 18 | (u8s[0 ] & 0x3FU ) << 12 | (u8s[1 ] & 0x3FU ) << 6 | (u8s[2 ] & 0x3FU ); break ;
1531+ }
1532+
1533+ *runes = rune;
1534+ *runes_lengths = len;
1535+ }
1536+
1537+ /* *
1538+ * @brief Extracts a UTF-8 codepoint from a string, scanning backwards from the given position.
1539+ * @param[in] utf8_end Pointer to one past the last byte of the UTF-8 sequence to parse.
1540+ * @param[out] rune Output parameter to store the extracted UTF-32 codepoint.
1541+ * @param[out] rune_length Output parameter to store the length of the UTF-8 codepoint in bytes (1-4).
1542+ * @warning Assumes valid UTF-8 input. Use `sz_utf8_valid()` first if validation is needed.
1543+ * @note This function does not perform any bounds checking on the input string.
1544+ */
1545+ SZ_INTERNAL void sz_rune_rparse (sz_cptr_t utf8_end, sz_rune_t *rune, sz_rune_length_t *rune_length) {
1546+ sz_u8_t const *u8s = (sz_u8_t const *)utf8_end;
1547+
1548+ // Scan backwards to find the lead byte (not a continuation byte 10xxxxxx)
1549+ int len = 1 ;
1550+ for (--u8s; (*u8s & 0xC0 ) == 0x80 && len < 4 ; --u8s, ++len) {}
1551+
1552+ // Now u8s points to the lead byte, len is the sequence length
1553+ sz_rune_parse ((sz_cptr_t )u8s, rune, rune_length);
1554+ sz_assert_ (*rune_length == (sz_rune_length_t )len && " Inconsistent rune length detected in sz_rune_rparse." );
1555+ }
1556+
1557+ /* *
1558+ * @brief Encode a UTF-32 codepoint to UTF-8, outputting 1-4 bytes.
1559+ * @param[in] rune The UTF-32 codepoint to encode.
1560+ * @param[out] utf8s Output buffer (must have space for at least 4 bytes).
1561+ * @return Number of bytes written (1-4), or 0 if the codepoint is invalid.
1562+ */
1563+ SZ_INTERNAL sz_rune_length_t sz_rune_export (sz_rune_t rune, sz_u8_t *utf8s) {
1564+ if (rune <= 0x7F ) {
1565+ utf8s[0 ] = (sz_u8_t )rune;
1566+ return sz_utf8_rune_1byte_k;
1567+ }
1568+ else if (rune <= 0x7FF ) {
1569+ utf8s[0 ] = (sz_u8_t )(0xC0 | (rune >> 6 ));
1570+ utf8s[1 ] = (sz_u8_t )(0x80 | (rune & 0x3F ));
1571+ return sz_utf8_rune_2bytes_k;
1572+ }
1573+ else if (rune <= 0xFFFF ) {
1574+ // Reject surrogate codepoints
1575+ if (rune >= 0xD800 && rune <= 0xDFFF ) return sz_utf8_invalid_k;
1576+ utf8s[0 ] = (sz_u8_t )(0xE0 | (rune >> 12 ));
1577+ utf8s[1 ] = (sz_u8_t )(0x80 | ((rune >> 6 ) & 0x3F ));
1578+ utf8s[2 ] = (sz_u8_t )(0x80 | (rune & 0x3F ));
1579+ return sz_utf8_rune_3bytes_k;
1580+ }
1581+ else if (rune <= 0x10FFFF ) {
1582+ utf8s[0 ] = (sz_u8_t )(0xF0 | (rune >> 18 ));
1583+ utf8s[1 ] = (sz_u8_t )(0x80 | ((rune >> 12 ) & 0x3F ));
1584+ utf8s[2 ] = (sz_u8_t )(0x80 | ((rune >> 6 ) & 0x3F ));
1585+ utf8s[3 ] = (sz_u8_t )(0x80 | (rune & 0x3F ));
1586+ return sz_utf8_rune_4bytes_k;
1587+ }
1588+ return sz_utf8_invalid_k;
1589+ }
1590+
1591+ /* *
1592+ * @brief Exports a UTF8 string into a UTF32 buffer.
1593+ * @warning The result is undefined id the UTF8 string is corrupted.
1594+ * @return The length in the number of codepoints.
1595+ */
1596+ SZ_PUBLIC sz_size_t sz_runes_parse (sz_cptr_t utf8, sz_size_t utf8_length, sz_rune_t *utf32) {
1597+ sz_cptr_t const end = utf8 + utf8_length;
1598+ sz_size_t count = 0 ;
1599+ sz_rune_length_t rune_length;
1600+ for (; utf8 != end; utf8 += rune_length, utf32++, count++) sz_rune_parse (utf8, utf32, &rune_length);
1601+ return count;
1602+ }
1603+
1604+ #pragma endregion
1605+
16061606/* * @brief Helper function, using the supplied fixed-capacity buffer to allocate memory. */
16071607SZ_INTERNAL sz_ptr_t sz_memory_allocate_fixed_ (sz_size_t length, void *handle) {
16081608
0 commit comments