Skip to content

Commit fd89a88

Browse files
committed
Improve: Case-insensitive search for Ry, Vi, El, Hy (Am)
1 parent c1c0305 commit fd89a88

3 files changed

Lines changed: 1504 additions & 802 deletions

File tree

include/stringzilla/types.h

Lines changed: 166 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -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. */
16071607
SZ_INTERNAL sz_ptr_t sz_memory_allocate_fixed_(sz_size_t length, void *handle) {
16081608

0 commit comments

Comments
 (0)