From cac925fbbb2175a0427fd44e301da6e1002d3d34 Mon Sep 17 00:00:00 2001 From: Chen Peng Date: Sun, 30 Aug 2026 03:59:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=9F=BA=E7=A1=80=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Foundation/c_StringList.c | 158 +++++++ Foundation/c_StringList.h | 51 +++ Foundation/c_StringList.t.c | 105 +++++ Foundation/c_utf8.c | 817 ++++++++++++++++++++++++++++++++++++ Foundation/c_utf8.h | 242 +++++++++++ Foundation/c_utf8.t.c | 302 +++++++++++++ Foundation/c_utf8_file.c | 208 +++++++++ Foundation/c_utf8_file.h | 61 +++ Foundation/c_utf8_file.t.c | 116 +++++ 9 files changed, 2060 insertions(+) create mode 100644 Foundation/c_StringList.c create mode 100644 Foundation/c_StringList.h create mode 100644 Foundation/c_StringList.t.c create mode 100644 Foundation/c_utf8.c create mode 100644 Foundation/c_utf8.h create mode 100644 Foundation/c_utf8.t.c create mode 100644 Foundation/c_utf8_file.c create mode 100644 Foundation/c_utf8_file.h create mode 100644 Foundation/c_utf8_file.t.c diff --git a/Foundation/c_StringList.c b/Foundation/c_StringList.c new file mode 100644 index 0000000..c61ed29 --- /dev/null +++ b/Foundation/c_StringList.c @@ -0,0 +1,158 @@ +#include + +c_err_t c_StringList_Init(c_StringList* self, c_size_t capacity, c_Allocator_t* allocator) { + if (!self) return C_ERR_PARAM; + + self->allocator = (allocator != NULL) ? *allocator : c_DefaultAllocator; + self->size = 0; + self->capacity = (capacity > 0) ? capacity : 4; // 默认初始槽位预留 4 项 + + // 闭环通过内部绑定的多态分配器,开辟二级指针控制矩阵的大底座连续空间 + self->strings = (char**)c_Allocator_Alloc(&self->allocator, self->capacity * sizeof(char*)); + if (!self->strings) { + self->capacity = 0; + return C_ERR_NOMEM; + } + + // 严维抹零:确保内部所有裸指针槽位初始皆为安全的纯净 NULL 状态 + memset((void*)self->strings, 0, self->capacity * sizeof(char*)); + return C_ERR_OK; +} + +void c_StringList_Clear(c_StringList* self) { + if (!self || !self->strings) return; + + // 【核心深清算】:必须先顺着槽位将当前持有的每一个独占字符串物理火化,退还给内置分配器 + for (c_size_t i = 0; i < self->size; i++) { + if (self->strings[i]) { + c_Allocator_Free(&self->allocator, self->strings[i]); + self->strings[i] = NULL; + } + } + self->size = 0; +} + +void c_StringList_Destroy(c_StringList* self) { + if (!self) return; + + if (self->strings) { + c_StringList_Clear(self); // 先洗刷释放内部行字符串 + c_Allocator_Free(&self->allocator, self->strings); // 再一并火化指针矩阵底座 + self->strings = NULL; + } + self->capacity = 0; +} + +/** + * @brief 内部私有自动动态翻倍扩容算法 + * @note 强异常安全性:若分配器因碎片满或爆仓返回 NULL,原有数据指针及老矩阵原样完整留存,绝不发生物理跑飞 + */ +C_STATIC_FORCE_INLINE +bool c_StringList_EnsureCapacity(c_StringList* self) { + if (self->size < self->capacity) return true; + + c_size_t old_bytes = self->capacity * sizeof(char*); + c_size_t new_capacity = self->capacity * 2; + c_size_t new_bytes = new_capacity * sizeof(char*); + + // 对接你最新的 Realloc 包装,闭环支持伙伴系统在同一阶数(Order)内的 O(1) 原地续约物理搬迁 + void* new_matrix = c_Allocator_Realloc(&self->allocator, self->strings, old_bytes, new_bytes); + if (!new_matrix) return false; + + self->strings = (char**)new_matrix; + self->capacity = new_capacity; + + // 将新扩出来的后半段空白区指针槽位执行原位抹零 + c_size_t gap_slots = self->capacity - self->size; + memset((void*)(self->strings + self->size), 0, gap_slots * sizeof(char*)); + + return true; +} + +// ================================================================================================================== +// 2. 核心深度值复制增删控制操作 API +// ================================================================================================================== + +c_err_t c_StringList_InsertAt(c_StringList* self, c_size_t index, const char* str) { + if (!self || !str || index > self->size) return C_ERR_PARAM; + if (!c_StringList_EnsureCapacity(self)) return C_ERR_NOMEM; + + c_size_t str_bytes = (c_size_t)strlen(str) + 1; // 包含 \0 封底位 + // 从自身绑定的多态分配器中,为当前新加入的项单独申领专属独立生存空间 + char* owned_str = (char*)c_Allocator_Alloc(&self->allocator, str_bytes); + if (!owned_str) return C_ERR_NOMEM; + memcpy(owned_str, str, str_bytes); // 深度复制值复制,彻底与外部解耦 + + // 利用重叠安全的 memmove 将目标索引右侧的所有行指针整体右推滑移一格 + if (index < self->size) { + memmove(self->strings + index + 1, self->strings + index, (self->size - index) * sizeof(char*)); + } + + self->strings[index] = owned_str; + self->size++; + return C_ERR_OK; +} + +c_err_t c_StringList_Add(c_StringList* self, const char* str) { + return c_StringList_InsertAt(self, self->size, str); // 末尾追加 +} + +c_err_t c_StringList_RemoveAt(c_StringList* self, c_size_t index) { + if (!self || index >= self->size || !self->strings) return C_ERR_PARAM; + + // ① 定点火化销毁当前槽位持有的独占堆字符串,归还空间 + c_Allocator_Free(&self->allocator, self->strings[index]); + + // ② 自左向右推进,将右侧后续有效项指针依序前移一格,完美重组填补空缺 + if (index < self->size - 1) { + memmove(self->strings + index, self->strings + index + 1, (self->size - index - 1) * sizeof(char*)); + } + + // 将滑移后腾出来的最后一个残存指针坑位强制置空,杜绝野指针残存 + self->strings[self->size - 1] = NULL; + self->size--; + return C_ERR_OK; +} + +// ================================================================================================================== +// 3. 高级功能:链表深克隆与高速去重 +// ================================================================================================================== + +c_err_t c_StringList_Clone(c_StringList* dest, const c_StringList* src) { + if (!dest || !src || dest == src || !src->strings) return C_ERR_PARAM; + + // 清算 dest 先前绑定的生命线 + c_StringList_Clear(dest); + + // 强制将 dest 的容量与 src 完全对齐齐平 + if (dest->capacity < src->size) { + c_StringList_Destroy(dest); + c_err_t err = c_StringList_Init(dest, src->size, &dest->allocator); + if (err != C_ERR_OK) return err; + } + + // 顺着槽位依次遍历执行行级专属大连续深拷贝 + for (c_size_t i = 0; i < src->size; i++) { + c_err_t err = c_StringList_Add(dest, src->strings[i]); + if (err != C_ERR_OK) return err; + } + return C_ERR_OK; +} + +c_err_t c_StringList_Deduplicate(c_StringList* self) { + if (!self || self->size <= 1 || !self->strings) return C_ERR_OK; + + // O(N^2) 经典原位原地重排去重流 + for (c_size_t i = 0; i < self->size; i++) { + for (c_size_t j = i + 1; j < self->size; ) { + if (strcmp(self->strings[i], self->strings[j]) == 0) { + // 撞特征重复!直接调用内部定点 RemoveAt 擦除拓扑,其内部会自动左移并 size-- + c_StringList_RemoveAt(self, j); + // 此时后续槽位前移填补了坑位,j 不需要递增,原位继续二次判定 + } else { + j++; + } + } + } + return C_ERR_OK; +} diff --git a/Foundation/c_StringList.h b/Foundation/c_StringList.h new file mode 100644 index 0000000..c82a49a --- /dev/null +++ b/Foundation/c_StringList.h @@ -0,0 +1,51 @@ +#ifndef INCLUDED_C_STRINGLIST_H +#define INCLUDED_C_STRINGLIST_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_ALLOCATOR_H +#include +#endif /*INCLUDED_C_ALLOCATOR_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + char** strings; // Dynamic array of self-owned null-terminated strings + c_size_t size; // Current active string rows stored + c_size_t capacity; // Max allocated capacity bounds of the internal pointer matrix + c_Allocator_t allocator; +} c_StringList; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_StringList_Init(c_StringList* self, c_size_t capacity, c_Allocator_t* allocator); +void c_StringList_Destroy(c_StringList* self); + +// 核心操作 API (安全深拷贝值复制模式) +c_err_t c_StringList_Add(c_StringList* self, const char* str); +c_err_t c_StringList_InsertAt(c_StringList* self, c_size_t index, const char* str); +c_err_t c_StringList_RemoveAt(c_StringList* self, c_size_t index); +void c_StringList_Clear(c_StringList* self); + +// 高级功能接口 +c_err_t c_StringList_Clone(c_StringList* dest, const c_StringList* src); +c_err_t c_StringList_Deduplicate(c_StringList* self); + +// 极致高频内联只读窥探接口 +C_STATIC_FORCE_INLINE c_size_t c_StringList_Size(const c_StringList* self) { + return self ? self->size : 0; // O(1) 实时读取 +} + +C_STATIC_FORCE_INLINE const char* c_StringList_Get(const c_StringList* self, c_size_t index) { + if (!self || index >= self->size || !self->strings) return NULL; + return self->strings[index]; // 零拷贝原位返回 +} + + + +#endif /*INCLUDED_C_STRINGLIST_H*/ diff --git a/Foundation/c_StringList.t.c b/Foundation/c_StringList.t.c new file mode 100644 index 0000000..66a9c81 --- /dev/null +++ b/Foundation/c_StringList.t.c @@ -0,0 +1,105 @@ +#include +#include +#include "c_StringList.h" +#include "c_Test.h" + +static size_t g_list_pool_active_chunks = 0; // 监控多态内存池中处于活跃分配态的物理块个数 + +// 模拟伙伴系统或 Arena 内存池的分配监控桩实现 +static void* mock_list_pool_alloc(size_t size, void* ctx) { + (void)ctx; g_list_pool_active_chunks++; return malloc(size); +} +static void mock_list_pool_free(void* ptr, void* ctx) { + (void)ctx; if (ptr) g_list_pool_active_chunks--; free(ptr); +} +static void* mock_list_pool_realloc(void* ptr, size_t old_size, size_t new_size, void* ctx) { + (void)ctx; (void)old_size; return realloc(ptr, new_size); +} + +TEST_CASE(test_string_list_polymorphic_deep_copy_closure) { + g_list_pool_active_chunks = 0; + + // 1. 原地构建自定义专属内存池分配器 + c_Allocator_t my_pool = { + .alloc = mock_list_pool_alloc, + .realloc = mock_list_pool_realloc, + .free = mock_list_pool_free, + .dtor = NULL, + .ud = NULL + }; + + c_StringList list; + // 初始化容量为 2 的特化字符串链表,并注入分配器 + ASSERT_INT_EQ(C_ERR_OK, c_StringList_Init(&list, 2, &my_pool)); + + // 审计校准:此时内存池只分配了 1 个矩阵控制大底座,活跃物理块精确定格为 1 + ASSERT_INT_EQ(1, (int)g_list_pool_active_chunks); + ASSERT_INT_EQ(0, (int)c_StringList_Size(&list)); + + // 2. 验证元素填充、深度值复制、以及内部自适应动态翻倍扩容 + char stack_buffer[] = "STACK_TXT_A"; // 分配在局部栈上的临时垃圾包 + ASSERT_INT_EQ(C_ERR_OK, c_StringList_Add(&list, stack_buffer)); + ASSERT_INT_EQ(C_ERR_OK, c_StringList_Add(&list, "STACK_TXT_B")); + + // 此时触发 2 -> 4 自动扩容:内部 realloc 成功原地续约底座,并塞入第三行 + ASSERT_INT_EQ(C_ERR_OK, c_StringList_Add(&list, "STACK_TXT_A")); // 故意塞入重复项用于后续去重 + + ASSERT_INT_EQ(3, (int)c_StringList_Size(&list)); + ASSERT_INT_EQ(4, (int)list.capacity); + // 活跃物理块计算:1个指针矩阵大底座 + 3个行级独立分配的独占堆字符串 = 4 个活跃块 + ASSERT_INT_EQ(4, (int)g_list_pool_active_chunks); + + // 3. 强隔离隔离性判定:故意洗刷外部栈变量,内部持有的内容必须坚若磐石 + memset(stack_buffer, 'X', sizeof(stack_buffer)); + ASSERT_TRUE(strcmp(c_StringList_Get(&list, 0), "STACK_TXT_A") == 0); // 必须依旧是原值,证明深度值复制成立 + + // 4. 验证中段定点随机插入(InsertAt)及相对物理寻址 + ASSERT_INT_EQ(C_ERR_OK, c_StringList_InsertAt(&list, 1, "INSERTED_VAL")); + ASSERT_INT_EQ(4, (int)c_StringList_Size(&list)); + // 确认原本在索引 1 的项被 memmove 完美向右推挤到了索引 2 位置 + ASSERT_TRUE(strcmp(c_StringList_Get(&list, 1), "INSERTED_VAL") == 0); + ASSERT_TRUE(strcmp(c_StringList_Get(&list, 2), "STACK_TXT_B") == 0); + ASSERT_INT_EQ(5, (int)g_list_pool_active_chunks); + + // 5. 验证中段定点删除(RemoveAt)与后续行指针平移重组 + ASSERT_INT_EQ(C_ERR_OK, c_StringList_RemoveAt(&list, 1)); // 抹杀刚插入的项 + ASSERT_INT_EQ(3, (int)c_StringList_Size(&list)); + ASSERT_TRUE(strcmp(c_StringList_Get(&list, 1), "STACK_TXT_B") == 0); // 后面重新前滑补缺 + ASSERT_INT_EQ(4, (int)g_list_pool_active_chunks); // 行物理壳资源被精准火化扣减 1 + + // 6. 验证高级全词原位去重机制 (Deduplicate) + // 当前排列为: "STACK_TXT_A", "STACK_TXT_B", "STACK_TXT_A" + ASSERT_INT_EQ(C_ERR_OK, c_StringList_Deduplicate(&list)); + ASSERT_INT_EQ(2, (int)c_StringList_Size(&list)); // 末尾重复的 A 被定点剔除 + ASSERT_TRUE(strcmp(c_StringList_Get(&list, 0), "STACK_TXT_A") == 0); + ASSERT_TRUE(strcmp(c_StringList_Get(&list, 1), "STACK_TXT_B") == 0); + ASSERT_INT_EQ(3, (int)g_list_pool_active_chunks); // 重复行的物理资源被内置分配器在底层连根拔起 + + // 7. 验证克隆体深拷贝承袭能力 (Clone) + c_StringList clone_list; + c_StringList_Init(&clone_list, 1, &my_pool); // 给个小容量 1 + ASSERT_INT_EQ(C_ERR_OK, c_StringList_Clone(&clone_list, &list)); + + ASSERT_INT_EQ(2, (int)c_StringList_Size(&clone_list)); + ASSERT_TRUE(strcmp(c_StringList_Get(&clone_list, 0), "STACK_TXT_A") == 0); + + // 8. 终极连环解体大清算 + c_StringList_Destroy(&list); + c_StringList_Destroy(&clone_list); + + // 终极双向一致性账目审判: + // 当两个大容器彻底解体消亡后,无论是行级独立开辟的变长串,还是指针底座大矩阵,必须全数物理火化! + // 挂载的多态池活跃计数量 g_list_pool_active_chunks 必须以极致优美的姿态完美重归于 0(0 空间泄漏,0 碎片滞留) + ASSERT_INT_EQ_MSG(0, (int)g_list_pool_active_chunks, "CRITICAL: c_StringList leaked exclusive heap elements inside the polymorphic allocator pool!"); +} + +int main(void) { + printf("\n"); + TEST_START(C_StringList_Polymorphic_DeepCopy_Tests); + + // 驱动高防御型特化字符串顺序表全功能单元测试验证 + RUN_TEST(test_string_list_polymorphic_deep_copy_closure); + + TEST_REPORT(); + RETURN_TEST_STATUS; +} \ No newline at end of file diff --git a/Foundation/c_utf8.c b/Foundation/c_utf8.c new file mode 100644 index 0000000..37b7147 --- /dev/null +++ b/Foundation/c_utf8.c @@ -0,0 +1,817 @@ +#include + +c_size_t c_utf8_strlen(const char* str) { + if (!str) return 0; + + c_size_t char_count = 0; + c_size_t i = 0; + + while (str[i] != '\0') { + i += c_utf8_char_len(str[i]); // 跳过当前字符占用的全部字节 + char_count++; + } + + return char_count; +} + +const char* c_utf8_strchr(const char* str, const char* utf8_char) { + if (!str || !utf8_char || utf8_char[0] == '\0') return NULL; + + c_size_t target_bytes = c_utf8_char_len(utf8_char[0]); + c_size_t i = 0; + + while (str[i] != '\0') { + c_size_t curr_bytes = c_utf8_char_len(str[i]); + + // 当且仅当两个字符占用的字节数相同,且多字节内容完全一致时匹配成功 + if (curr_bytes == target_bytes) { + if (memcmp(&str[i], utf8_char, target_bytes) == 0) { + return &str[i]; + } + } + i += curr_bytes; // 移动到下一个 UTF-8 字符 + } + + return NULL; +} + +char* c_utf8_strncpy(char* dest, const char* src, c_size_t char_num) { + if (!dest || !src || char_num == 0) return dest; + + c_size_t src_idx = 0; + c_size_t dest_idx = 0; + c_size_t copied_chars = 0; + + while (src[src_idx] != '\0' && copied_chars < char_num) { + c_size_t char_bytes = c_utf8_char_len(src[src_idx]); + + // 批量精确拷贝当前完整字符的 N 个字节 + memcpy(&dest[dest_idx], &src[src_idx], char_bytes); + + src_idx += char_bytes; + dest_idx += char_bytes; + copied_chars++; + } + + // 兼容 strncpy 标准:如果源串长度小于 char_num,则用 '\0' 填充剩余的空隙 + // 注意:这里的剩余空隙在实际工程中通常按字节填充更安全 + dest[dest_idx] = '\0'; + + return dest; +} + +int c_utf8_strncmp(const char* str1, const char* str2, c_size_t char_num) { + if (!str1 || !str2 || char_num == 0) return 0; + + c_size_t idx1 = 0; + c_size_t idx2 = 0; + c_size_t compared_chars = 0; + + while (compared_chars < char_num) { + // 任意一端到达末尾 + if (str1[idx1] == '\0' || str2[idx2] == '\0') { + return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]); + } + + c_size_t len1 = c_utf8_char_len(str1[idx1]); + c_size_t len2 = c_utf8_char_len(str2[idx2]); + + // 如果单字长字节不相等,直接根据当前字符进行排序比较 + if (len1 != len2) { + return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]); + } + + // 长度相同时,直接比较当前单个 UTF-8 字符的内容 + int res = memcmp(&str1[idx1], &str2[idx2], len1); + if (res != 0) { + return res; + } + + idx1 += len1; + idx2 += len2; + compared_chars++; + } + + return 0; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +char* c_utf8_tolower(char* str) { + if (!str) return NULL; + + c_size_t i = 0; + while (str[i] != '\0') { + unsigned char b1 = (unsigned char)str[i]; + c_size_t len = c_utf8_char_len(str[i]); + + // Case A: Standard Single-byte ASCII Case Folding + if (len == 1) { + if (b1 >= 'A' && b1 <= 'Z') { + str[i] = (char)(b1 + 32); + } + } + // Case B: Double-byte UTF-8 Case Folding (e.g., Cyrillic / Greek scripts) + else if (len == 2) { + unsigned char b2 = (unsigned char)str[i + 1]; + + // Cyrillic script transformations (Capital letters: 0xD0 0x80 to 0xD0 0xBF) + if (b1 == 0xD0) { + if (b2 >= 0x90 && b2 <= 0xAF) { + // Shift to lowercase variant range located inside 0xD0 / 0xD1 blocks + str[i + 1] = (char)(b2 + 0x20); + } else if (b2 >= 0xB0 && b2 <= 0xBF) { + str[i] = (char)0xD1; + str[i + 1] = (char)(b2 - 0x20); + } + } + // Greek script transformations (Capital letters: 0xCE 0x91 to 0xCE 0xAB) + else if (b1 == 0xCE) { + if (b2 >= 0x91 && b2 <= 0xAB && b2 != 0xA2) { // 0xA2 is a special variant + // Shift down to lowercase variant range block inside 0xCE / 0xCF + if (b2 <= 0x9F) { + str[i + 1] = (char)(b2 + 0x20); + } else { + str[i] = (char)0xCF; + str[i + 1] = (char)(b2 - 0x20); + } + } + } + } + // Multi-byte Chinese ideographs (3 bytes) and Emojis (4 bytes) lack casing concepts; step past them + i += len; + } + + return str; +} + + +char* c_utf8_toupper(char* str) { + if (!str) return NULL; + + c_size_t i = 0; + while (str[i] != '\0') { + unsigned char b1 = (unsigned char)str[i]; + c_size_t len = c_utf8_char_len(str[i]); + + // Case A: Standard Single-byte ASCII Case Folding + if (len == 1) { + if (b1 >= 'a' && b1 <= 'z') { + str[i] = (char)(b1 - 32); + } + } + // Case B: Double-byte UTF-8 Case Folding (e.g., Cyrillic / Greek scripts) + else if (len == 2) { + unsigned char b2 = (unsigned char)str[i + 1]; + + // Cyrillic script transformations (Lowercase letters: 0xD0 0xB0 to 0xD0 0xBF, and 0xD1 0x80 to 0xD1 0x8F) + if (b1 == 0xD0) { + if (b2 >= 0xB0 && b2 <= 0xBF) { + // Shift up to uppercase variant range located inside the 0xD0 block + str[i + 1] = (char)(b2 - 0x20); + } + } else if (b1 == 0xD1) { + if (b2 >= 0x80 && b2 <= 0x8F) { + // Convert leading byte from 0xD1 back to 0xD0 and realign low byte + str[i] = (char)0xD0; + str[i + 1] = (char)(b2 + 0x20); + } + } + // Greek script transformations (Lowercase letters: 0xCE 0xB1 to 0xCE 0xBF, and 0xCF 0x80 to 0xCF 0x8B) + else if (b1 == 0xCE) { + if (b2 >= 0xB1 && b2 <= 0xBF) { + // Shift down to uppercase variant range block inside 0xCE + str[i + 1] = (char)(b2 - 0x20); + } + } else if (b1 == 0xCF) { + if (b2 >= 0x80 && b2 <= 0x8B) { + // Convert leading byte from 0xCF back to 0xCE and realign low byte + str[i] = (char)0xCE; + str[i + 1] = (char)(b2 + 0x20); + } + } + } + // 3-byte characters (Chinese Ideographs) and 4-byte characters (Emojis) lack casing concepts; jump past them safely + i += len; + } + + return str; +} + + +char* c_utf8_strcat(char* dest, const char* src) { + if (!dest || !src) return dest; + + // Locate the termination boundary point of the original destination array + c_size_t dest_idx = 0; + while (dest[dest_idx] != '\0') { + dest_idx++; + } + + // Continuously append source bytes until hitting the terminator character + c_size_t src_idx = 0; + while (src[src_idx] != '\0') { + dest[dest_idx++] = src[src_idx++]; + } + + // Force secure terminal character sealing + dest[dest_idx] = '\0'; + + return dest; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +const char* c_utf8_strstr(const char* haystack, const char* needle) { + if (!haystack || !needle) return NULL; + + // An empty needle matches the beginning of the haystack per standard strstr specification + if (needle[0] == '\0') { + return haystack; + } + + c_size_t h_idx = 0; + while (haystack[h_idx] != '\0') { + c_size_t n_idx = 0; + c_size_t current_match_idx = h_idx; + + // Perform byte-by-byte substring evaluation from the current boundary anchor + while (haystack[current_match_idx] != '\0' && needle[n_idx] != '\0' && + haystack[current_match_idx] == needle[n_idx]) { + current_match_idx++; + n_idx++; + } + + // If we successfully traversed the entire needle string, a match is found + if (needle[n_idx] == '\0') { + return &haystack[h_idx]; + } + + // Advance to the next valid UTF-8 character point in the haystack + h_idx += c_utf8_char_len(haystack[h_idx]); + } + + return NULL; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ +#include + +const char* c_utf8_strrchr(const char* str, const char* utf8_char) { + if (!str || !utf8_char || utf8_char[0] == '\0') return NULL; + + c_size_t target_bytes = c_utf8_char_len(utf8_char[0]); + c_size_t i = 0; + const char* last_match = NULL; + + while (str[i] != '\0') { + c_size_t curr_bytes = c_utf8_char_len(str[i]); + + // Continuous linear check, updating our tracker to keep the furthest matched offset + if (curr_bytes == target_bytes) { + if (memcmp(&str[i], utf8_char, target_bytes) == 0) { + last_match = &str[i]; + } + } + i += curr_bytes; // Jump forward explicitly across whole multi-byte characters + } + + return last_match; +} + +/** + * @brief Internal helper to verify if a specific UTF-8 character pointer matches any delimiter in the list. + */ +C_STATIC_FORCE_INLINE +c_bool_t c_utf8_is_delim(const char* current_char, const char* delims, c_size_t* delim_len) { + c_size_t d_idx = 0; + c_size_t c_len = c_utf8_char_len(*current_char); + + while (delims[d_idx] != '\0') { + c_size_t d_len = c_utf8_char_len(delims[d_idx]); + if (c_len == d_len && memcmp(current_char, &delims[d_idx], c_len) == 0) { + *delim_len = d_len; + return C_TRUE; + } + d_idx += d_len; + } + return C_FALSE; +} + +char* c_utf8_strtok(char* str, const char* delims, char** saveptr) { + if (!delims || !saveptr) return NULL; + + // Use our saved pointer context if str is passed as NULL + char* token_cursor = (str != NULL) ? str : *saveptr; + if (!token_cursor || *token_cursor == '\0') { + return NULL; + } + + // Step 1: Skip over any leading delimiter sequences to locate the token start + c_size_t skip_len = 0; + while (*token_cursor != '\0' && c_utf8_is_delim(token_cursor, delims, &skip_len)) { + token_cursor += skip_len; + } + + // If we hit the absolute end of the input string while skipping delims, no tokens exist + if (*token_cursor == '\0') { + *saveptr = token_cursor; + return NULL; + } + + char* token_start = token_cursor; + + // Step 2: Track forward to locate the terminal delimiter boundary of this token + while (*token_cursor != '\0') { + c_size_t next_char_len = c_utf8_char_len(*token_cursor); + c_size_t match_delim_len = 0; + + if (c_utf8_is_delim(token_cursor, delims, &match_delim_len)) { + // Found a boundary delimiter! Overwrite its leading byte with a null terminator + *token_cursor = '\0'; + // Save state context tracking pointing immediately past the clipped delimiter + *saveptr = token_cursor + match_delim_len; + return token_start; + } + token_cursor += next_char_len; + } + + // If we reached the end of the text string naturally, ensure the saveptr updates to string termination + *saveptr = token_cursor; + return token_start; +} + + +/** + * @brief Internal helper to return the uppercase variant value of a single ASCII or 2-byte UTF-8 character. + * Returns the original trailing byte structure if no case mapping applies. + */ +C_STATIC_FORCE_INLINE +void c_utf8_fold_char(const char* src, size_t len, unsigned char* out_b1, unsigned char* out_b2) { + *out_b1 = (unsigned char)src[0]; + *out_b2 = (len > 1) ? (unsigned char)src[1] : 0; + + // Single-byte ASCII case folding + if (len == 1) { + if (*out_b1 >= 'a' && *out_b1 <= 'z') { + *out_b1 -= 32; + } + } + // Double-byte UTF-8 case folding (Cyrillic & Greek scripts) + else if (len == 2) { + // Cyrillic script: 0xD0 0xB0...0xBF to 0xD0 0x90...0x9F; 0xD1 0x80...0x8F to 0xD0 0xA0...0xAF + if (*out_b1 == 0xD0) { + if (*out_b2 >= 0xB0 && *out_b2 <= 0xBF) { + *out_b2 -= 0x20; + } + } else if (*out_b1 == 0xD1) { + if (*out_b2 >= 0x80 && *out_b2 <= 0x8F) { + *out_b1 = 0xD0; + *out_b2 += 0x20; + } + } + // Greek script: 0xCE 0xB1...0xBF to 0xCE 0x91...0x9F; 0xCF 0x80...0x8B to 0xCE 0xA0...0xAB + else if (*out_b1 == 0xCE) { + if (*out_b2 >= 0xB1 && *out_b2 <= 0xBF) { + *out_b2 -= 0x20; + } + } else if (*out_b1 == 0xCF) { + if (*out_b2 >= 0x80 && *out_b2 <= 0x8B) { + *out_b1 = 0xCE; + *out_b2 += 0x20; + } + } + } +} + +int c_utf8_strncasecmp(const char* str1, const char* str2, c_size_t char_num) { + if (!str1 || !str2 || char_num == 0) return 0; + + c_size_t idx1 = 0; + c_size_t idx2 = 0; + c_size_t compared_chars = 0; + + while (compared_chars < char_num) { + // Handle termination boundaries gracefully + if (str1[idx1] == '\0' || str2[idx2] == '\0') { + return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]); + } + + c_size_t len1 = c_utf8_char_len(str1[idx1]); + c_size_t len2 = c_utf8_char_len(str2[idx2]); + + // Fold characters to uppercase form for comparison + unsigned char f1_b1, f1_b2; + unsigned char f2_b1, f2_b2; + c_utf8_fold_char(&str1[idx1], len1, &f1_b1, &f1_b2); + c_utf8_fold_char(&str2[idx2], len2, &f2_b1, &f2_b2); + + // Compare first bytes or script widths + if (f1_b1 != f2_b1) { + return (int)f1_b1 - (int)f2_b1; + } + + // Compare second bytes (relevant for 2-byte sequences) + if (f1_b2 != f2_b2) { + return (int)f1_b2 - (int)f2_b2; + } + + // For 3-byte (Chinese) or 4-byte characters, fallback to raw memory comparison if lead bytes matched + if (len1 > 2) { + if (len1 != len2) { + return (int)len1 - (int)len2; + } + int raw_res = memcmp(&str1[idx1], &str2[idx2], len1); + if (raw_res != 0) { + return raw_res; + } + } + + // Advance iteration offsets + idx1 += len1; + idx2 += len2; + compared_chars++; + } + + return 0; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_utf8_to_unicode(const char* str, c_ucs4_t* out_codepoint, c_size_t* out_bytes_consumed) { + if (!str || *str == '\0' || !out_codepoint || !out_bytes_consumed) { + return C_ERR_PARAM; + } + + unsigned char b1 = (unsigned char)str[0]; + c_size_t len = c_utf8_char_len(str[0]); + c_ucs4_t cp = 0; + + // Case 1: 1-Byte ASCII (0xxxxxxx) + if (len == 1) { + if (b1 >= 0x80) return C_ERR_PARAM; // Guard against malformed lead bytes + cp = b1; + } + // Case 2: 2-Byte Sequence (110xxxxx 10xxxxxx) + else if (len == 2) { + unsigned char b2 = (unsigned char)str[1]; + if ((b2 & 0xC0) != 0x80) return C_ERR_PARAM; // Validate continuation byte + + cp = ((b1 & 0x1F) << 6) | (b2 & 0x3F); + if (cp < 0x80) return C_ERR_PARAM; // Overlong encoding defense + } + // Case 3: 3-Byte Sequence (1110xxxx 10xxxxxx 10xxxxxx) + else if (len == 3) { + unsigned char b2 = (unsigned char)str[1]; + unsigned char b3 = (unsigned char)str[2]; + if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) return C_ERR_PARAM; + + cp = ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F); + if (cp < 0x0800) return C_ERR_PARAM; // Overlong encoding defense + if (cp >= 0xD800 && cp <= 0xDFFF) return C_ERR_PARAM; // Surrogate pairs rejection + } + // Case 4: 4-Byte Sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx) + else if (len == 4) { + unsigned char b2 = (unsigned char)str[1]; + unsigned char b3 = (unsigned char)str[2]; + unsigned char b4 = (unsigned char)str[3]; + if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 || (b4 & 0xC0) != 0x80) return C_ERR_PARAM; + + cp = ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F); + if (cp < 0x010000) return C_ERR_PARAM; // Overlong encoding defense + } + else { + return C_ERR_PARAM; + } + + // Limit validation (Unicode max standard is U+10FFFF) + if (cp > 0x10FFFF) { + return C_ERR_PARAM; + } + + *out_codepoint = cp; + *out_bytes_consumed = len; + return C_ERR_OK; +} + + +c_err_t c_utf8_from_unicode(c_ucs4_t codepoint, char* dest_buffer, c_size_t* out_bytes_written) { + if (!dest_buffer || !out_bytes_written) { + return C_ERR_PARAM; + } + + // Reject out-of-range codepoints or UTF-16 surrogate pairs (reserved for UTF-16 only) + if (codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + return C_ERR_PARAM; + } + + // Case 1: Standard ASCII range (U+0000 to U+007F) -> Requires 1 byte + if (codepoint <= 0x7F) { + dest_buffer[0] = (char)codepoint; + *out_bytes_written = 1; + } + // Case 2: U+0080 to U+07FF -> Requires 2 bytes + else if (codepoint <= 0x7FF) { + dest_buffer[0] = (char)(0xC0 | ((codepoint >> 6) & 0x1F)); + dest_buffer[1] = (char)(0x80 | (codepoint & 0x3F)); + *out_bytes_written = 2; + } + // Case 3: U+0800 to U+FFFF -> Requires 3 bytes (Handles most Chinese characters) + else if (codepoint <= 0xFFFF) { + dest_buffer[0] = (char)(0xE0 | ((codepoint >> 12) & 0x0F)); + dest_buffer[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + dest_buffer[2] = (char)(0x80 | (codepoint & 0x3F)); + *out_bytes_written = 3; + } + // Case 4: U+10000 to U+10FFFF -> Requires 4 bytes (Handles Emojis and ancient scripts) + else { + dest_buffer[0] = (char)(0xF0 | ((codepoint >> 18) & 0x07)); + dest_buffer[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F)); + dest_buffer[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + dest_buffer[3] = (char)(0x80 | (codepoint & 0x3F)); + *out_bytes_written = 4; + } + + // Securely seal the local buffer layout array with a trailing null terminator + dest_buffer[*out_bytes_written] = '\0'; + + return C_ERR_OK; +} + + +c_err_t c_utf8_to_unicode_array(const char* str, c_ucs4_t* dest_array, c_size_t array_capacity, c_size_t* out_chars_written) { + if (!str || !dest_array || !out_chars_written) { + return C_ERR_PARAM; + } + + c_size_t src_idx = 0; + c_size_t chars_count = 0; + + while (str[src_idx] != '\0') { + // Enforce array capacity threshold constraints + if (chars_count >= array_capacity) { + *out_chars_written = chars_count; + return C_ERR_PARAM; // Destination array is too small to fit the remaining string + } + + c_ucs4_t cp = 0; + c_size_t bytes_consumed = 0; + + // Decode the single character point via your core decoding function + c_err_t err = c_utf8_to_unicode(&str[src_idx], &cp, &bytes_consumed); + if (err != C_ERR_OK) { + *out_chars_written = chars_count; + return err; // Propagate the malformed stream error up + } + + dest_array[chars_count++] = cp; + src_idx += bytes_consumed; + } + + *out_chars_written = chars_count; + return C_ERR_OK; +} + +c_err_t c_utf8_from_unicode_array(const c_ucs4_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written) { + if (!src_array || !dest_buffer || !out_bytes_written) { + return C_ERR_PARAM; + } + + c_size_t dest_idx = 0; + + for (c_size_t i = 0; i < src_array_len; i++) { + char temp_char_buf[5]; // Temporary standalone slot buffer + c_size_t bytes_written = 0; + + // Encode single code point state back to byte wrappers + c_err_t err = c_utf8_from_unicode(src_array[i], temp_char_buf, &bytes_written); + if (err != C_ERR_OK) { + *out_bytes_written = dest_idx; + return err; + } + + // Verify if destination capacity bounds can hold the new character block (+1 for terminal null) + if (dest_idx + bytes_written + 1 > dest_capacity) { + *out_bytes_written = dest_idx; + dest_buffer[dest_idx] = '\0'; // Gracefully terminate the current chunk before failing + return C_ERR_PARAM; + } + + // Copy raw encoded data bytes into our tracking stream layout + memcpy(dest_buffer + dest_idx, temp_char_buf, bytes_written); + dest_idx += bytes_written; + } + + // Force strict trailing character termination closure + dest_buffer[dest_idx] = '\0'; + *out_bytes_written = dest_idx; + + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_utf8_to_utf16(const char* str, c_uint16_t* dest_array, c_size_t array_capacity, c_size_t* out_units_written) { + if (!str || !dest_array || !out_units_written) { + return C_ERR_PARAM; + } + + c_size_t src_idx = 0; + c_size_t units_count = 0; + + while (str[src_idx] != '\0') { + c_ucs4_t cp = 0; + c_size_t bytes_consumed = 0; + + // 1. Decode the UTF-8 sequence into a Unicode codepoint + c_err_t err = c_utf8_to_unicode(&str[src_idx], &cp, &bytes_consumed); + if (err != C_ERR_OK) { + *out_units_written = units_count; + return err; + } + + // 2. Encode the codepoint into UTF-16 + if (cp <= 0xFFFF) { + // BMP Range: Requires exactly one 16-bit code unit + if (units_count + 1 >= array_capacity) { + *out_units_written = units_count; + return C_ERR_PARAM; // Out of bounds + } + dest_array[units_count++] = (c_uint16_t)cp; + } else { + // Supplementary Planes (Astral): Requires a surrogate pair (two 16-bit units) + if (units_count + 2 >= array_capacity) { + *out_units_written = units_count; + return C_ERR_PARAM; // Out of bounds + } + cp -= 0x10000; + dest_array[units_count++] = (c_uint16_t)(0xD800 | ((cp >> 10) & 0x3FF)); // High Surrogate + dest_array[units_count++] = (c_uint16_t)(0xDC00 | (cp & 0x3FF)); // Low Surrogate + } + + src_idx += bytes_consumed; + } + + // Append the trailing null terminator to make it a valid UTF-16 string + if (units_count < array_capacity) { + dest_array[units_count] = 0; + } else { + *out_units_written = units_count; + return C_ERR_PARAM; + } + + *out_units_written = units_count; + return C_ERR_OK; +} + +c_err_t c_utf8_from_utf16(const c_uint16_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written) { + if (!src_array || !dest_buffer || !out_bytes_written) { + return C_ERR_PARAM; + } + + c_size_t src_idx = 0; + c_size_t dest_idx = 0; + + while (src_idx < src_array_len) { + c_ucs4_t cp = 0; + c_uint16_t u1 = src_array[src_idx++]; + + // 1. Decode UTF-16 to Unicode codepoint + if (u1 >= 0xD800 && u1 <= 0xDBFF) { + // High surrogate detected, look ahead for the matching low surrogate + if (src_idx >= src_array_len) { + *out_bytes_written = dest_idx; + return C_ERR_PARAM; // Truncated/Malformed surrogate pair + } + c_uint16_t u2 = src_array[src_idx++]; + if (u2 < 0xDC00 || u2 > 0xDFFF) { + *out_bytes_written = dest_idx; + return C_ERR_PARAM; // Missing or invalid low surrogate + } + cp = (((u1 & 0x3FF) << 10) | (u2 & 0x3FF)) + 0x10000; + } else if (u1 >= 0xDC00 && u1 <= 0xDFFF) { + // Isolated low surrogate is invalid in a lead position + *out_bytes_written = dest_idx; + return C_ERR_PARAM; + } else { + // Normal BMP character + cp = u1; + } + + // 2. Encode the Unicode codepoint back into the destination UTF-8 buffer + char temp_buf[5]; + c_size_t bytes_written = 0; + c_err_t err = c_utf8_from_unicode(cp, temp_buf, &bytes_written); + if (err != C_ERR_OK) { + *out_bytes_written = dest_idx; + return err; + } + + // Verify if destination capacity bounds can hold the new block (+1 for terminal null) + if (dest_idx + bytes_written + 1 > dest_capacity) { + *out_bytes_written = dest_idx; + dest_buffer[dest_idx] = '\0'; + return C_ERR_PARAM; // Overflow protection + } + + memcpy(dest_buffer + dest_idx, temp_buf, bytes_written); + dest_idx += bytes_written; + } + + // Force strict trailing character termination closure + dest_buffer[dest_idx] = '\0'; + *out_bytes_written = dest_idx; + + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_utf16_swap_endian(c_uint16_t* utf16_array, c_size_t length) { + if (!utf16_array) { + return C_ERR_PARAM; + } + + for (c_size_t i = 0; i < length; i++) { + c_uint16_t value = utf16_array[i]; + // Bitwise swap: (value >> 8) extracts the high byte, (value << 8) extracts the low byte + utf16_array[i] = (c_uint16_t)(((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)); + } + + return C_ERR_OK; +} + + +c_err_t c_utf16_to_unicode(const c_uint16_t* src_units, c_size_t src_capacity, c_ucs4_t* out_codepoint, c_size_t* out_units_read) { + if (!src_units || src_capacity == 0 || !out_codepoint || !out_units_read) { + return C_ERR_PARAM; + } + + c_uint16_t u1 = src_units[0]; + + // Case 1: High Surrogate Point Detection (0xD800 to 0xDBFF) + if (u1 >= 0xD800 && u1 <= 0xDBFF) { + if (src_capacity < 2) { + return C_ERR_PARAM; // Truncated sequence: expected a matching low surrogate + } + + c_uint16_t u2 = src_units[1]; + if (u2 < 0xDC00 || u2 > 0xDFFF) { + return C_ERR_PARAM; // Malformed sequence: missing a valid trailing low surrogate + } + + // Reconstruct Astral Plane Codepoint via formula: ((High - 0xD800) << 10) + (Low - 0xDC00) + 0x10000 + *out_codepoint = (c_ucs4_t)((((u1 & 0x3FF) << 10) | (u2 & 0x3FF)) + 0x10000); + *out_units_read = 2; + } + // Case 2: Isolated Low Surrogate Error Check (0xDC00 to 0xDFFF) + else if (u1 >= 0xDC00 && u1 <= 0xDFFF) { + return C_ERR_PARAM; // Isolated low surrogate is mathematically invalid in a lead position + } + // Case 3: Standard BMP Range Character + else { + *out_codepoint = (c_ucs4_t)u1; + *out_units_read = 1; + } + + return C_ERR_OK; +} + +c_err_t c_utf16_from_unicode(c_ucs4_t codepoint, c_uint16_t* dest_units, c_size_t dest_capacity, c_size_t* out_units_written) { + if (!dest_units || dest_capacity == 0 || !out_units_written) { + return C_ERR_PARAM; + } + + // Limit Validation: Reject invalid Astral values or illegal UTF-16 surrogate codepoint blocks + if (codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + return C_ERR_PARAM; + } + + // Case 1: BMP Range (U+0000 to U+FFFF) -> Requires 1 code unit + if (codepoint <= 0xFFFF) { + dest_units[0] = (c_uint16_t)codepoint; + *out_units_written = 1; + } + // Case 2: Supplementary Planes (U+10000 to U+10FFFF) -> Requires 2 code units (Surrogate Pair) + else { + if (dest_capacity < 2) { + return C_ERR_PARAM; // Insufficient buffer capacity + } + + c_ucs4_t adjusted = codepoint - 0x10000; + dest_units[0] = (c_uint16_t)(0xD800 | ((adjusted >> 10) & 0x3FF)); // High Surrogate + dest_units[1] = (c_uint16_t)(0xDC00 | (adjusted & 0x3FF)); // Low Surrogate + *out_units_written = 2; + } + + return C_ERR_OK; +} + + diff --git a/Foundation/c_utf8.h b/Foundation/c_utf8.h new file mode 100644 index 0000000..c374880 --- /dev/null +++ b/Foundation/c_utf8.h @@ -0,0 +1,242 @@ +#ifndef INCLUDED_C_UTF8_H +#define INCLUDED_C_UTF8_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#ifndef C_UNICODE_MAX +#define C_UNICODE_MAX 0x10FFFFU +#endif + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/* --- Unicode Codepoint Type Definition --- */ +typedef uint32_t c_ucs4_t; // UCS-4 / UTF-32 representation for a single Unicode Codepoint +typedef uint16_t c_uint16_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +bool c_is_valid_unicode(uint32_t code) { + // Unicode 码点不能超过 0x10FFFF,且必须排除 UTF-16 代理对范围 (0xD800 ~ 0xDFFF) + if (code > C_UNICODE_MAX) return false; + if (code >= 0xD800 && code <= 0xDFFF) return false; + return true; +} + +/** + * @brief 获取一个 UTF-8 字符在当前指针位置所占用的实际字节数 (1 ~ 4 字节) + */ +C_STATIC_FORCE_INLINE c_size_t c_utf8_char_len(char leading_byte) { + unsigned char b = (unsigned char)leading_byte; + if (b < 0x80) return 1; // 单字节 ASCII: 0xxxxxxx + if ((b & 0xE0) == 0xC0) return 2; // 双字节字符: 110xxxxx + if ((b & 0xF0) == 0xE0) return 3; // 三字节字符(大部分汉字): 1110xxxx + if ((b & 0xF8) == 0xF0) return 4; // 四字节字符(Emoji等): 11110xxx + return 1; // 非法 UTF-8 引导字节,防御性返回 1 防止死循环 +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 计算一个 UTF-8 字符串的有效字形数(字符数),而非字节数 (兼容 strlen) + */ +c_size_t c_utf8_strlen(const char* str); + +/** + * @brief 查找字符在 UTF-8 字符串中第一次出现的位置 (兼容 strchr) + * @param str 源字符串 + * @param utf8_char 待查找的 UTF-8 字符(支持多字节字符,如 "中") + * @return const char* 指向找到的第一个字节的指针,未找到返回 NULL + */ +const char* c_utf8_strchr(const char* str, const char* utf8_char); + +/** + * @brief 复制指定字形数量的 UTF-8 字符串 (兼容 strncpy) + * @note 能够完美感知 UTF-8 字符边界,绝不会切断汉字,且自动在末尾补 '\0' + * @param dest 目标缓冲区 + * @param src 源字符串 + * @param char_num 要复制的 UTF-8 字符/字形数量 + * @return char* 指向目标缓冲区 dest 的指针 + */ +char* c_utf8_strncpy(char* dest, const char* src, c_size_t char_num); + +/** + * @brief 比较两个 UTF-8 字符串的前 n 个字符 (兼容 strncmp) + * @param str1 字符串 1 + * @param str2 字符串 2 + * @param char_num 要比较的 UTF-8 字符/字形数量 + * @return int 小于 0、等于 0 或大于 0 + */ +int c_utf8_strncmp(const char* str1, const char* str2, c_size_t char_num); + + +/** + * @brief Converts a UTF-8 character string to lowercase in-place. + * Supports standard ASCII case folding and common multi-byte scripts. + * @param str Pointer to the mutable null-terminated UTF-8 string. + * @return char* Pointer to the original string. + */ +char* c_utf8_tolower(char* str); + +/** + * @brief Converts a UTF-8 character string to uppercase in-place. + * Supports standard ASCII case folding and common multi-byte scripts. + * @param str Pointer to the mutable null-terminated UTF-8 string. + * @return char* Pointer to the original string. + */ +char* c_utf8_toupper(char* str); + +/** + * @brief Appends the source UTF-8 string to the destination string buffer (Compatible with strcat). + * @param dest Pointer to the null-terminated destination buffer. + * @param src Pointer to the null-terminated source string. + * @return char* Pointer to the destination string destination pointer. + */ +char* c_utf8_strcat(char* dest, const char* src); + +/** + * @brief Finds the first occurrence of a substring in a UTF-8 string (Compatible with strstr). + * @param haystack The null-terminated UTF-8 string to scan. + * @param needle The null-terminated UTF-8 substring to search for. + * @return const char* Pointer to the first byte of the matched substring in haystack, or NULL if not found. + */ +const char* c_utf8_strstr(const char* haystack, const char* needle); + +/** + * @brief Finds the last occurrence of a specific character in a UTF-8 string (Compatible with strrchr). + * @param str The null-terminated UTF-8 string to scan. + * @param utf8_char The null-terminated UTF-8 character string to find (can be a multi-byte sequence like "中"). + * @return const char* Pointer to the last occurrence of the matched character in str, or NULL if not found. + */ +const char* c_utf8_strrchr(const char* str, const char* utf8_char); + +/** + * @brief Tokenizes a string into a series of tokens based on multiple multi-byte delimiters. + * This function is thread-safe and reentrant, operating similarly to POSIX strtok_r. + * @param str The mutable UTF-8 string to tokenize. Pass NULL on subsequent calls. + * @param delims A raw byte sequence containing multi-byte UTF-8 delimiters. + * @param saveptr A user-allocated tracking pointer to maintain state context across consecutive calls. + * @return char* Pointer to the beginning of the next valid token, or NULL when no more tokens are found. + */ +char* c_utf8_strtok(char* str, const char* delims, char** saveptr); + +/** + * @brief Compares two UTF-8 strings case-insensitively up to a specified number of characters. + * @param str1 Pointer to the first null-terminated UTF-8 string. + * @param str2 Pointer to the second null-terminated UTF-8 string. + * @param char_num Maximum number of UTF-8 characters (codepoints) to compare. + * @return int An integer less than, equal to, or greater than zero if str1 is found, + * respectively, to be less than, to match, or be greater than str2. + */ +int c_utf8_strncasecmp(const char* str1, const char* str2, c_size_t char_num); + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Converts the next UTF-8 byte sequence at a given pointer into a single Unicode Codepoint. + * @param str Pointer to the current position in a null-terminated UTF-8 string. + * @param out_codepoint Pointer to the destination where the decoded Unicode integer is saved. + * @param out_bytes_consumed Pointer to save the number of source bytes processed (1 to 4). + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM on invalid/corrupted UTF-8 byte streams. + */ +c_err_t c_utf8_to_unicode(const char* str, c_ucs4_t* out_codepoint, c_size_t* out_bytes_consumed); + + +/** + * @brief Encodes a single Unicode Codepoint (UCS-4) into a destination UTF-8 byte array. + * @param codepoint The source Unicode integer character point to encode. + * @param dest_buffer Pointer to a char array buffer (must have at least 5 bytes capacity). + * @param out_bytes_written Pointer to save the number of encoded bytes stored in dest_buffer. + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM if the codepoint is out of valid Unicode ranges or parameters are NULL. + */ +c_err_t c_utf8_from_unicode(c_ucs4_t codepoint, char* dest_buffer, c_size_t* out_bytes_written); + +/** + * @brief Decodes an entire null-terminated UTF-8 string into an array of Unicode Codepoints. + * @param str Pointer to the null-terminated source UTF-8 string. + * @param dest_array Pointer to the destination array where decoded codepoints will be stored. + * @param array_capacity Maximum number of elements that dest_array can hold. + * @param out_chars_written Pointer to save the total number of codepoints successfully stored. + * @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid/NULL parameters or if the destination array capacity is exceeded. + */ +c_err_t c_utf8_to_unicode_array(const char* str, c_ucs4_t* dest_array, c_size_t array_capacity, c_size_t* out_chars_written); + +/** + * @brief Encodes an array of Unicode Codepoints back into a null-terminated UTF-8 byte stream. + * @param src_array Pointer to the source array of Unicode codepoints. + * @param src_array_len The number of codepoint elements inside src_array to process. + * @param dest_buffer Pointer to the destination char buffer. + * @param dest_capacity Maximum byte capacity of the destination buffer (including room for '\0'). + * @param out_bytes_written Pointer to save the total number of bytes written to dest_buffer (excluding '\0'). + * @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid/NULL parameters or if dest_capacity is exceeded. + */ +c_err_t c_utf8_from_unicode_array(const c_ucs4_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written); + +/** + * @brief Converts an entire null-terminated UTF-8 string into an array of UTF-16 code units. + * @param str Pointer to the null-terminated source UTF-8 string. + * @param dest_array Pointer to the destination array where UTF-16 code units will be stored. + * @param array_capacity Maximum number of 16-bit elements that dest_array can hold. + * @param out_units_written Pointer to save the total number of UTF-16 code units successfully stored (excluding terminal '\0'). + * @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid/NULL parameters or if capacity is exceeded. + */ +c_err_t c_utf8_to_utf16(const char* str, c_uint16_t* dest_array, c_size_t array_capacity, c_size_t* out_units_written); + +/** + * @brief Converts an array of UTF-16 code units back into a null-terminated UTF-8 byte stream. + * @param src_array Pointer to the source array of UTF-16 code units. + * @param src_array_len The number of 16-bit elements inside src_array to process. + * @param dest_buffer Pointer to the destination char buffer. + * @param dest_capacity Maximum byte capacity of the destination buffer (including room for '\0'). + * @param out_bytes_written Pointer to save the total number of bytes written to dest_buffer (excluding '\0'). + * @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid/NULL parameters, malformed surrogates, or if dest_capacity is exceeded. + */ +c_err_t c_utf8_from_utf16(const c_uint16_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Swaps the byte order (endianness) of a UTF-16 string array in-place. + * @param utf16_array Pointer to the source/destination array of UTF-16 code units. + * @param length The number of 16-bit elements inside the array to process. + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM if the array pointer is NULL. + */ +c_err_t c_utf16_swap_endian(c_uint16_t* utf16_array, c_size_t length); + + +/** + * @brief Decodes a UTF-16 character stream starting at a given pointer into a single Unicode codepoint. + * @param src_units Pointer to the current code unit position in a UTF-16 array. + * @param src_capacity Remaining elements left available to read inside the source array. + * @param out_codepoint Pointer to the destination where the decoded Unicode integer is saved. + * @param out_units_read Pointer to save the number of 16-bit code units processed (1 for BMP, 2 for Surrogate Pairs). + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM on malformed surrogate sequences or missing parameters. + */ +c_err_t c_utf16_to_unicode(const c_uint16_t* src_units, c_size_t src_capacity, c_ucs4_t* out_codepoint, c_size_t* out_units_read); + +/** + * @brief Encodes a single Unicode codepoint into a target UTF-16 array buffer. + * @param codepoint The source Unicode integer character point to encode. + * @param dest_units Pointer to the destination 16-bit code unit array buffer. + * @param dest_capacity Maximum number of 16-bit elements the destination buffer can accept. + * @param out_units_written Pointer to save the number of 16-bit code units generated (1 or 2). + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM if the codepoint is invalid or destination space is insufficient. + */ +c_err_t c_utf16_from_unicode(c_ucs4_t codepoint, c_uint16_t* dest_units, c_size_t dest_capacity, c_size_t* out_units_written); + + + +#endif /*INCLUDED_C_UTF8_H*/ diff --git a/Foundation/c_utf8.t.c b/Foundation/c_utf8.t.c new file mode 100644 index 0000000..aedcaec --- /dev/null +++ b/Foundation/c_utf8.t.c @@ -0,0 +1,302 @@ +#include "c_utf8.h" +#include +#include + +/* --- 单元测试模块集成 --- */ +#define RUN_TEST(test_case, name) \ + do { \ + printf("[RUN] %s... ", name); \ + if (test_case) { \ + printf("\033[32mPASSED\033[0m\n"); \ + } else { \ + printf("\033[31mFAILED\033[0m (%s:%d)\n", __FILE__, __LINE__); \ + return C_ERR_FAIL; \ + } \ + } while(0) + +c_err_t c_Utf8String_UnitTest(void) { + printf("==================================================\n"); + printf(" STARTING C_UTF8STRING UNIT TESTING \n"); + printf("==================================================\n"); + + const char* sample = "NLP大模型_2026"; // 包含 3个大写英文、3个汉字、1个下划线、4个数字 = 11个字符 + + /* 1. c_utf8_strlen 测试 */ + // 传统的 strlen(sample) 会返回 3 + 3*3 + 1 + 4 = 17 字节 + RUN_TEST(c_utf8_strlen(sample) == 11, "c_utf8_strlen correctly counts character points"); + RUN_TEST(c_utf8_strlen("") == 0, "c_utf8_strlen handles empty strings"); + + /* 2. c_utf8_strchr 测试 */ + const char* find_eng = c_utf8_strchr(sample, "P"); + const char* find_chn = c_utf8_strchr(sample, "模"); + const char* find_none = c_utf8_strchr(sample, "国"); + + RUN_TEST(find_eng != NULL && *find_eng == 'P', "c_utf8_strchr locate ASCII element"); + // "模" 在 "模型_2026" 头部,其后紧跟 "型" + RUN_TEST(find_chn != NULL && strncmp(find_chn, "模型", 6) == 0, "c_utf8_strchr locate multi-byte Chinese word"); + RUN_TEST(find_none == NULL, "c_utf8_strchr returns NULL for non-existing chars"); + + /* 3. c_utf8_strncpy 安全截断测试 */ + char dest_buf[64]; + // 截断前 5 个字符 -> "NLP大模" (绝不会出现半个汉字或乱码断裂) + c_utf8_strncpy(dest_buf, sample, 5); + RUN_TEST(c_utf8_strlen(dest_buf) == 5, "c_utf8_strncpy slices correct character width"); + RUN_TEST(strcmp(dest_buf, "NLP大模") == 0, "c_utf8_strncpy safe boundary isolation checked"); + + /* 4. c_utf8_strncmp 字符匹配测试 */ + RUN_TEST(c_utf8_strncmp("自然语言", "自然选择", 2) == 0, "c_utf8_strncmp matches first 2 shared Chinese words"); + RUN_TEST(c_utf8_strncmp("自然语言", "自然选择", 3) != 0, "c_utf8_strncmp detects variance at 3rd word slot"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + /* 5. Lowercase Transform Verification (c_utf8_tolower) */ + char case_buf[64] = "NLP大模型_2026_Go!"; + c_utf8_tolower(case_buf); + RUN_TEST(strcmp(case_buf, "nlp大模型_2026_go!") == 0, "c_utf8_tolower translates ASCII while isolating Chinese layout characters"); + + // Cyrillic multi-byte letter test ("П" -> 0xD0 0x9F converted down to "п" -> 0xD0 0xBF) + char cyrillic_buf[8] = { (char)0xD0, (char)0x9F, '\0' }; + c_utf8_tolower(cyrillic_buf); + RUN_TEST((unsigned char)cyrillic_buf[1] == 0xBF, "c_utf8_tolower successfully transforms multi-byte Cyrillic characters"); + + /* 6. Concatenation Verification (c_utf8_strcat) */ + char cat_dest[32] = "自然"; + c_utf8_strcat(cat_dest, "语言"); + RUN_TEST(strcmp(cat_dest, "自然语言") == 0, "c_utf8_strcat appends string tokens cleanly"); + RUN_TEST(c_utf8_strlen(cat_dest) == 4, "Post-concatenation size checks out at 4 characters total"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 24. Uppercase Transform Verification (c_utf8_toupper) */ + char upper_buf[] = "nlp大模型_2026_go!"; + c_utf8_toupper(upper_buf); + RUN_TEST(strcmp(upper_buf, "NLP大模型_2026_GO!") == 0, "c_utf8_toupper translates ASCII while isolating Chinese layout characters"); + + // Cyrillic multi-byte lowercase letter test ("п" -> 0xD0 0xBF converted up to "П" -> 0xD0 0x9F) + char cyr_upper_buf[] = { (char)0xD0, (char)0xBF, '\0' }; + c_utf8_toupper(cyr_upper_buf); + RUN_TEST((unsigned char)cyr_upper_buf[1] == 0x9F, "c_utf8_toupper successfully transforms multi-byte Cyrillic lowercase characters"); + + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 7. Character Scanning Verification (c_utf8_strchr) */ + const char* scan_base = "NLP大模型_2026"; + + /* 8. Substring Scanning Verification (c_utf8_strstr) */ + const char* match_str = c_utf8_strstr(scan_base, "大模型"); + const char* miss_str = c_utf8_strstr(scan_base, "小模型"); + const char* empty_str = c_utf8_strstr(scan_base, ""); + + RUN_TEST(match_str != NULL && strncmp(match_str, "大模型_2026", 14) == 0, "c_utf8_strstr fetches multi-byte string locations"); + RUN_TEST(miss_str == NULL, "c_utf8_strstr returns NULL cleanly on substring mismatch"); + RUN_TEST(empty_str == scan_base, "c_utf8_strstr returns parent head context given empty needle input"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 16. Reverse Scanning Verification (c_utf8_strrchr) */ + const char* r_base = "自然_模型_自然_2026"; + const char* last_match = c_utf8_strrchr(r_base, "自然"); + const char* first_match = c_utf8_strchr(r_base, "自然"); + + RUN_TEST(last_match != NULL && last_match != first_match, "c_utf8_strrchr skips the first match to pull the last occurrence"); + RUN_TEST(strncmp(last_match, "自然_2026", 11) == 0, "c_utf8_strrchr locates the correct trailing block address"); + + /* 17. Reentrant Tokenization Verification (c_utf8_strtok) */ + char token_source[] = ",NLP,,大模型,2026,"; // Multi-byte Chinese comma delimiters + const char* delimiters = ","; + char* save_context = NULL; + + // First Call + char* token = c_utf8_strtok(token_source, delimiters, &save_context); + RUN_TEST(token != NULL && strcmp(token, "NLP") == 0, "c_utf8_strtok parses the first token and skips leading delims"); + + // Second Call (Pass NULL to proceed) + token = c_utf8_strtok(NULL, delimiters, &save_context); + RUN_TEST(token != NULL && strcmp(token, "大模型") == 0, "c_utf8_strtok correctly extracts multi-byte Chinese '大模型'"); + + // Third Call + token = c_utf8_strtok(NULL, delimiters, &save_context); + RUN_TEST(token != NULL && strcmp(token, "2026") == 0, "c_utf8_strtok extracts '2026'"); + + // Fourth Call - Termination + token = c_utf8_strtok(NULL, delimiters, &save_context); + RUN_TEST(token == NULL, "c_utf8_strtok returns NULL cleanly when parsing is finished"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 25. Case-Insensitive Bounded Comparison Verification (c_utf8_strncasecmp) */ + // Test Case 1: Simple matching mixed-case strings + RUN_TEST(c_utf8_strncasecmp("Nlp大模型", "NLP大模型", 6) == 0, "c_utf8_strncasecmp matches mixed cases up to 6 characters"); + + // Test Case 2: Verification of prefix character restrictions bounding + RUN_TEST(c_utf8_strncasecmp("NLP大模型_v2", "nlp大模型_v3", 6) == 0, "c_utf8_strncasecmp returns 0 if differences fall past the character count limit"); + RUN_TEST(c_utf8_strncasecmp("NLP大模型_v2", "nlp大模型_v3", 9) != 0, "c_utf8_strncasecmp registers structural variance when character limits cover differences"); + + // Test Case 3: Mixed language case sorting behavior + RUN_TEST(c_utf8_strncasecmp("自然语言NLP", "自然语言nlp", 7) == 0, "c_utf8_strncasecmp handles matching trailing ASCII case differences after multi-byte blocks"); + + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 18. UTF-8 to Unicode Codepoint Decoding Verification */ + const char* utf8_src = "中!🚀"; // "中" (3 bytes), "!" (1 byte), "🚀" Emoji (4 bytes) + c_ucs4_t cp = 0; + c_size_t bytes_step = 0; + + // Decode first character ("中" -> Expected Codepoint: U+4E2D) + c_err_t err = c_utf8_to_unicode(utf8_src, &cp, &bytes_step); + RUN_TEST(err == C_ERR_OK && bytes_step == 3, "c_utf8_to_unicode processes 3-byte Chinese characters"); + RUN_TEST(cp == 0x4E2D, "Decoded codepoint matches U+4E2D ('中') accurately"); + + // Decode next sequence ("!" -> Expected Codepoint: U+0021) + err = c_utf8_to_unicode(utf8_src + bytes_step, &cp, &bytes_step); + RUN_TEST(err == C_ERR_OK && bytes_step == 1, "c_utf8_to_unicode processes 1-byte ASCII markers"); + RUN_TEST(cp == 0x0021, "Decoded codepoint matches U+0021 ('!')"); + + // Decode next sequence (Rocket Emoji "🚀" -> Expected Codepoint: U+1F680) + err = c_utf8_to_unicode(utf8_src + 4, &cp, &bytes_step); // Skip forward 4 bytes total + RUN_TEST(err == C_ERR_OK && bytes_step == 4, "c_utf8_to_unicode decodes 4-byte astral plane emojis"); + RUN_TEST(cp == 0x1F680, "Decoded codepoint matches U+1F680 ('🚀')"); + + /* 19. Unicode Codepoint to UTF-8 Encoding Verification */ + char encode_buf[8]; + c_size_t written_len = 0; + + // Encode U+4E2D back to UTF-8 + err = c_utf8_from_unicode(0x4E2D, encode_buf, &written_len); + RUN_TEST(err == C_ERR_OK && written_len == 3, "c_utf8_from_unicode encodes U+4E2D back into 3 bytes"); + RUN_TEST(strcmp(encode_buf, "中") == 0, "Encoded string content matches '中' flawlessly"); + + // Encode U+1F680 back to UTF-8 + err = c_utf8_from_unicode(0x1F680, encode_buf, &written_len); + RUN_TEST(err == C_ERR_OK && written_len == 4, "c_utf8_from_unicode encodes U+1F680 back into 4 bytes"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 20. UTF-8 String Stream <-> Unicode Array Conversions Verification */ + const char* mixed_sentence = "NLP大模型!🚀"; // Length: 3 ASCII, 3 Chinese (9B), 1 ASCII, 1 Emoji (4B) = 8 characters total + c_ucs4_t uni_array[16]; + c_size_t total_chars = 0; + + // Test Point 1: Decode stream into codepoint container array + err = c_utf8_to_unicode_array(mixed_sentence, uni_array, 16, &total_chars); + RUN_TEST(err == C_ERR_OK && total_chars == 8, "c_utf8_to_unicode_array maps complex string streams into separate integer points"); + RUN_TEST(uni_array[0] == 'N' && uni_array[3] == 0x5927 && uni_array[7] == 0x1F680, "Decoded array positions hold proper character points ('N', '大', '🚀')"); + + // Test Point 2: Trigger array capacity guard protection + c_ucs4_t tight_array[4]; + err = c_utf8_to_unicode_array(mixed_sentence, tight_array, 4, &total_chars); + RUN_TEST(err == C_ERR_PARAM && total_chars == 4, "c_utf8_to_unicode_array safely blocks operations and returns current progress on buffer limit hits"); + + // Test Point 3: Reverse operation - Encode codepoint array back into native UTF-8 string layout + char reconstructed_str[64]; + c_size_t written_bytes = 0; + err = c_utf8_from_unicode_array(uni_array, 8, reconstructed_str, sizeof(reconstructed_str), &written_bytes); + RUN_TEST(err == C_ERR_OK && written_bytes == 17, "c_utf8_from_unicode_array successfully packs codepoints back into 17 raw bytes"); + RUN_TEST(strcmp(reconstructed_str, mixed_sentence) == 0, "Reconstructed stream data matches original expression perfectly"); + + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 21. UTF-8 <-> UTF-16 Array Conversions Verification */ + const char* utf8_sentence = "NLP大模型!🚀"; // Contains ASCII, 3-byte Chinese, and a 4-byte Astral Plane Emoji + c_uint16_t utf16_array[32]; + c_size_t units_written = 0; + + // Test Point 1: Convert UTF-8 stream to UTF-16 code units + // "NLP" (3 units) + "大模型" (3 units) + "!" (1 unit) + "🚀" (Surrogate pair = 2 units) = 9 units total + err = c_utf8_to_utf16(utf8_sentence, utf16_array, 32, &units_written); + RUN_TEST(err == C_ERR_OK && units_written == 9, "c_utf8_to_utf16 successfully packs characters including astral planes"); + RUN_TEST(utf16_array[0] == 'N' && utf16_array[3] == 0x5927, "Verify standard BMP mapping inside UTF-16 array structure"); + // Verify high and low surrogate points for the Rocket Emoji 🚀 + RUN_TEST(utf16_array[7] == 0xD83D && utf16_array[8] == 0xDE80, "Verify surrogate pair matching (0xD83D 0xDE80) for U+1F680"); + + // Test Point 2: Convert UTF-16 array back to native UTF-8 string layout + char reconstructed_utf8[64]; + c_size_t bytes_written_utf8 = 0; + err = c_utf8_from_utf16(utf16_array, units_written, reconstructed_utf8, sizeof(reconstructed_utf8), &bytes_written_utf8); + RUN_TEST(err == C_ERR_OK && bytes_written_utf8 == 17, "c_utf8_from_utf16 unpacks units back into 17 raw bytes"); + RUN_TEST(strcmp(reconstructed_utf8, utf8_sentence) == 0, "Reconstructed UTF-8 matches the original string precisely"); + + // Test Point 3: Malformed Surrogate Pair Detection + c_uint16_t malformed_utf16[] = { 0xD83D, 'A' }; // High surrogate followed by a literal letter (invalid) + char error_buf[16]; + c_size_t err_bytes = 0; + err = c_utf8_from_utf16(malformed_utf16, 2, error_buf, sizeof(error_buf), &err_bytes); + RUN_TEST(err == C_ERR_PARAM, "c_utf8_from_utf16 successfully flags and rejects malformed/orphaned surrogate code units"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 22. UTF-16 Endianness Byte-Swapping Verification (c_utf16_swap_endian) */ + c_uint16_t sample_utf16[] = { 0xD83D, 0xDE80, 0x5927 }; // 🚀 and 大 in standard host endianness + c_size_t array_len = sizeof(sample_utf16) / sizeof(sample_utf16[0]); + + // Test Point 1: Guard against NULL parameters + RUN_TEST(c_utf16_swap_endian(NULL, array_len) == C_ERR_PARAM, "c_utf16_swap_endian safely rejects NULL pointer arrays"); + + // Test Point 2: Perform initial byte-swap transformation + err = c_utf16_swap_endian(sample_utf16, array_len); + RUN_TEST(err == C_ERR_OK, "c_utf16_swap_endian executes successfully"); + // 0xD83D -> 0x3DD8, 0xDE80 -> 0x80DE, 0x5927 -> 0x2759 + RUN_TEST(sample_utf16[0] == 0x3DD8, "First code unit correctly bit-swapped (0xD83D -> 0x3DD8)"); + RUN_TEST(sample_utf16[1] == 0x80DE, "Second code unit correctly bit-swapped (0xDE80 -> 0x80DE)"); + RUN_TEST(sample_utf16[2] == 0x2759, "Third code unit correctly bit-swapped (0x5927 -> 0x2759)"); + + // Test Point 3: Swap back to restore the original host endianness values + err = c_utf16_swap_endian(sample_utf16, array_len); + RUN_TEST(err == C_ERR_OK, "c_utf16_swap_endian reverts state back on secondary execution pass"); + RUN_TEST(sample_utf16[0] == 0xD83D && sample_utf16[1] == 0xDE80 && sample_utf16[2] == 0x5927, "Original internal value contexts perfectly preserved"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 23. UTF-16 Single-Char Unicode Conversions Verification */ + c_ucs4_t decoded_cp = 0; + c_size_t units_moved = 0; + + // Test Point 1: Decode Standard BMP Character ('大' -> U+5927) + c_uint16_t bmp_sample[] = { 0x5927 }; + err = c_utf16_to_unicode(bmp_sample, 1, &decoded_cp, &units_moved); + RUN_TEST(err == C_ERR_OK && units_moved == 1, "c_utf16_to_unicode processes BMP code units"); + RUN_TEST(decoded_cp == 0x5927, "Decoded BMP matches expected U+5927 successfully"); + + // Test Point 2: Decode Surrogate Pair Character (Rocket Emoji '🚀' -> High: 0xD83D, Low: 0xDE80) + c_uint16_t astral_sample[] = { 0xD83D, 0xDE80 }; + err = c_utf16_to_unicode(astral_sample, 2, &decoded_cp, &units_moved); + RUN_TEST(err == C_ERR_OK && units_moved == 2, "c_utf16_to_unicode correctly handles surrogate pairs"); + RUN_TEST(decoded_cp == 0x1F680, "Decoded Astral match returns U+1F680 ('🚀')"); + + // Test Point 3: Error validation protection against malformed surrogate chains + c_uint16_t isolated_high[] = { 0xD83D }; // Lacks matching trailing block + RUN_TEST(c_utf16_to_unicode(isolated_high, 1, &decoded_cp, &units_moved) == C_ERR_PARAM, "c_utf16_to_unicode rejects truncated surrogate sequences"); + + // Test Point 4: Encode Astral Plane back to UTF-16 code units + c_uint16_t encode_units[2]; + units_written = 0; + err = c_utf16_from_unicode(0x1F680, encode_units, 2, &units_written); + RUN_TEST(err == C_ERR_OK && units_written == 2, "c_utf16_from_unicode builds surrogate pairs for plane codepoints"); + RUN_TEST(encode_units[0] == 0xD83D && encode_units[1] == 0xDE80, "Generated high and low values match standard encoding targets"); + + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + printf("==================================================\n"); + printf("\033[32mSUCCESS: ALL UTF-8 COMPATIBLE INTERFACES PASSED!\033[0m\n"); + printf("==================================================\n"); + return C_ERR_OK; +} + + +int main(int argc, char** argv){ + return c_Utf8String_UnitTest(); +} diff --git a/Foundation/c_utf8_file.c b/Foundation/c_utf8_file.c new file mode 100644 index 0000000..dbe6328 --- /dev/null +++ b/Foundation/c_utf8_file.c @@ -0,0 +1,208 @@ +#include + +#include +#include + +c_err_t c_utf8_file_read(const char* filepath, c_StringBuffer_t* out_sb) { + if (!filepath || !out_sb || !out_sb->buffer) { + return C_ERR_PARAM; + } + + FILE* file = fopen(filepath, "rb"); // Open in binary mode to prevent Windows crlf translations + if (!file) { + return C_ERR_FAIL; + } + + // Step 1: Detect and handle the optional 3-byte UTF-8 BOM sequence + unsigned char bom[3]; + c_size_t bom_read = fread(bom, 1, 3, file); + c_bool_t has_bom = C_FALSE; + + if (bom_read == 3 && bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF) { + has_bom = C_TRUE; // BOM sequence matched; file pointer is positioned right past it + } else { + // No BOM found; rewind file pointer back to the absolute beginning of the stream + fseek(file, 0, SEEK_SET); + } + + // Step 2: Read data sequentially via stream chunking loops + char read_chunk[1024]; + c_size_t bytes_read = 0; + c_size_t partial_offset = 0; + + while ((bytes_read = fread(read_chunk + partial_offset, 1, sizeof(read_chunk) - partial_offset, file)) > 0) { + c_size_t total_available_bytes = bytes_read + partial_offset; + c_size_t valid_process_boundary = total_available_bytes; + + // Verify that the chunk boundary does not break a multi-byte character in half. + // Look back from the absolute end of the chunk to catch multi-byte headers. + if (read_chunk[total_available_bytes - 1] & 0x80) { + c_size_t lookback = 1; + // Scan backward up to 4 bytes to find the leading byte of the fractured character + while (lookback <= 4 && lookback <= total_available_bytes) { + unsigned char b = (unsigned char)read_chunk[total_available_bytes - lookback]; + if ((b & 0xC0) == 0xC0) { // Found a multi-byte lead byte + c_size_t expected_len = c_utf8_char_len((char)b); + if (lookback < expected_len) { + // Character is indeed fractured; shrink chunk boundary to omit it + valid_process_boundary = total_available_bytes - lookback; + } + break; + } + if ((b & 0x80) == 0) { // Standard ASCII character, boundary is clean + break; + } + lookback++; + } + } + + // Pipe valid, cohesive text fragments into your dynamic string buffer tracker + if (valid_process_boundary > 0) { + c_err_t err = c_StringBuffer_Append(out_sb, read_chunk, valid_process_boundary); + if (err != C_ERR_OK) { + fclose(file); + return err; + } + } + + // Move remaining fractured bytes to the front of the next chunk buffer iteration pass + partial_offset = total_available_bytes - valid_process_boundary; + if (partial_offset > 0) { + memmove(read_chunk, read_chunk + valid_process_boundary, partial_offset); + } + } + + // Process residual bytes if the file stream terminates abruptly with an incomplete character sequence + if (partial_offset > 0) { + c_StringBuffer_Append(out_sb, read_chunk, partial_offset); + } + + fclose(file); + return C_ERR_OK; +} + +c_err_t c_utf8_file_write(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom) { + if (!filepath || !sb || !sb->buffer) { + return C_ERR_PARAM; + } + + FILE* file = fopen(filepath, "wb"); // Open in binary mode for precise byte preservation + if (!file) { + return C_ERR_FAIL; + } + + // Explicitly inject the UTF-8 BOM sequence if requested by the configuration parameter + if (write_bom) { + unsigned char bom[3] = {0xEF, 0xBB, 0xBF}; + if (fwrite(bom, 1, 3, file) != 3) { + fclose(file); + return C_ERR_FAIL; + } + } + + // Flush the string buffer's raw tracking payload into disk blocks + if (sb->size > 0) { + c_size_t written = fwrite(sb->buffer, 1, sb->size, file); + if (written != sb->size) { + fclose(file); + return C_ERR_FAIL; + } + } + + fclose(file); + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_utf8_file_append(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom) { + if (!filepath || !sb || !sb->buffer) { + return C_ERR_PARAM; + } + + // Check if the file already exists by attempting to open it in read mode + FILE* check_file = fopen(filepath, "rb"); + c_bool_t file_exists = (check_file != NULL); + if (file_exists) { + fclose(check_file); + } + + // Open the file in append-binary mode + FILE* file = fopen(filepath, "ab"); + if (!file) { + return C_ERR_FAIL; + } + + // Write the BOM only if requested AND the file is brand new + if (write_bom && !file_exists) { + unsigned char bom[3] = {0xEF, 0xBB, 0xBF}; + if (fwrite(bom, 1, 3, file) != 3) { + fclose(file); + return C_ERR_FAIL; + } + } + + // Append the string buffer's raw tracking payload + if (sb->size > 0) { + c_size_t written = fwrite(sb->buffer, 1, sb->size, file); + if (written != sb->size) { + fclose(file); + return C_ERR_FAIL; + } + } + + fclose(file); + return C_ERR_OK; +} + +c_err_t c_utf8_file_readline(FILE* file, c_StringBuffer_t* out_line) { + if (!file || !out_line || !out_line->buffer) { + return C_ERR_PARAM; + } + + // Clear previous string buffer trackers to prepare for fresh line ingestion + c_StringBuffer_Clear(out_line); + + char read_chunk[256]; + c_bool_t data_extracted = C_FALSE; + long line_start_pos = ftell(file); + + while (fgets(read_chunk, sizeof(read_chunk), file) != NULL) { + data_extracted = C_TRUE; + c_size_t chunk_len = strlen(read_chunk); + + // Check if the chunk contains a newline character + char* newline_ptr = strchr(read_chunk, '\n'); + if (newline_ptr != NULL) { + // Calculate exact copy length up to the newline boundary + c_size_t copy_len = newline_ptr - read_chunk; + + if (copy_len > 0) { + // Strip carriage returns '\r' for safe cross-platform matching + if (read_chunk[copy_len - 1] == '\r') { + copy_len--; + } + } + + if (copy_len > 0) { + c_err_t err = c_StringBuffer_Append(out_line, read_chunk, copy_len); + if (err != C_ERR_OK) return err; + } + + return C_ERR_OK; // Line read complete + } + + // If no newline is found, the line is longer than our chunk; append everything and keep reading + c_err_t err = c_StringBuffer_Append(out_line, read_chunk, chunk_len); + if (err != C_ERR_OK) return err; + } + + // Handle end-of-file (EOF) state + if (data_extracted) { + return C_ERR_OK; // Returned the final trailing line containing no newline char + } + + return C_ERR_FAIL; // Reached EOF without extracting any data bytes +} + diff --git a/Foundation/c_utf8_file.h b/Foundation/c_utf8_file.h new file mode 100644 index 0000000..6f44364 --- /dev/null +++ b/Foundation/c_utf8_file.h @@ -0,0 +1,61 @@ +#ifndef INCLUDED_C_UTF8_FILE_H +#define INCLUDED_C_UTF8_FILE_H + +#ifndef INCLUDED_C_STRINGBUFFER_H +#include +#endif /*INCLUDED_C_STRINGBUFFER_H*/ + +#ifndef INCLUDED_C_UTF8_H +#include +#endif /*INCLUDED_C_UTF8_H*/ + +#ifndef INCLUDED_STDIO_H +#define INCLUDED_STDIO_H +#include +#endif /*INCLUDED_STDIO_H*/ + + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Reads the entire contents of a UTF-8 text file into a string buffer structure. + * Automatically handles, validates, and skips the UTF-8 BOM marker if present. + * @param filepath Path to the target source file on disk. + * @param out_sb Pointer to a pre-initialized c_StringBuffer_t container to collect file data. + * @return c_err_t C_ERR_OK on complete success, C_ERR_PARAM on invalid inputs, or C_ERR_FAIL if file access throws errors. + */ +c_err_t c_utf8_file_read(const char* filepath, c_StringBuffer_t* out_sb); + +/** + * @brief Writes data from a string buffer out to a disk file using a UTF-8 text layout stream. + * @param filepath Path to the destination file on disk. + * @param sb Pointer to the source string buffer containing data. + * @param write_bom If set to C_TRUE, explicitly prefixes the file layout with the 3-byte UTF-8 BOM marker. + * @return c_err_t C_ERR_OK on complete success, C_ERR_PARAM on invalid inputs, or C_ERR_FAIL on disk write errors. + */ +c_err_t c_utf8_file_write(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom); + +/** + * @brief Appends text from a string buffer to a disk file. + * If the target file does not exist, it initializes it (with an optional BOM marker). + * @param filepath Path to the destination file on disk. + * @param sb Pointer to the source string buffer containing the append payload. + * @param write_bom If set to C_TRUE and the file is new, prefixes the stream with the 3-byte UTF-8 BOM. + * @return c_err_t C_ERR_OK on complete success, C_ERR_PARAM on invalid inputs, or C_ERR_FAIL on disk write errors. + */ +c_err_t c_utf8_file_append(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom); + +/** + * @brief Reads a single line of text from an open file stream into a string buffer (Dynamic fgets). + * Automatically handles standard '\n' and '\r\n' line endings. + * @param file An active file stream pointer opened in binary read ("rb") mode. + * @param out_line Pointer to a pre-initialized c_StringBuffer_t container to collect the line string. + * @return c_err_t C_ERR_OK on successful line read, C_ERR_FAIL when reaching EOF with no data, or parameter errors. + */ +c_err_t c_utf8_file_readline(FILE* file, c_StringBuffer_t* out_line); + + +#endif /*INCLUDED_C_UTF8_FILE_H*/ diff --git a/Foundation/c_utf8_file.t.c b/Foundation/c_utf8_file.t.c new file mode 100644 index 0000000..ea26093 --- /dev/null +++ b/Foundation/c_utf8_file.t.c @@ -0,0 +1,116 @@ +#include "c_utf8_file.h" +#include +#include + +#define RUN_TEST(test_case, name) \ + do { \ + printf("[RUN] %s... ", name); \ + if (test_case) { \ + printf("\033[32mPASSED\033[0m\n"); \ + } else { \ + printf("\033[31mFAILED\033[0m (%s:%d)\n", __FILE__, __LINE__); \ + return C_ERR_FAIL; \ + } \ + } while(0) + +static c_err_t c_utf8_file_test(void) { + printf("==================================================\n"); + printf(" STARTING C_UTF8_FILE UNIT TESTING \n"); + printf("==================================================\n"); + + /* 26. UTF-8 File I/O Operations Verification */ + c_StringBuffer_t write_sb; + c_StringBuffer_t read_sb; + const char* test_filename = "nlp_utf8_test.txt"; + const char* payload = "NLP大模型_2026_🚀"; + + c_StringBuffer_Init(&write_sb, 32, 0); + c_StringBuffer_Init(&read_sb, 32, 0); + c_StringBuffer_AppendStr(&write_sb, payload); + + // Test Point 1: Parameter checks protection + RUN_TEST(c_utf8_file_read(NULL, &read_sb) == C_ERR_PARAM, "File read handles NULL filepath strings"); + RUN_TEST(c_utf8_file_write(test_filename, NULL, C_FALSE) == C_ERR_PARAM, "File write handles NULL buffer contexts"); + + // Test Point 2: Write text payload with explicit BOM injection enabled + c_err_t err = c_utf8_file_write(test_filename, &write_sb, C_TRUE); + RUN_TEST(err == C_ERR_OK, "UTF-8 data written to disk with BOM successfully"); + + // Test Point 3: Read text payload back from disk space + err = c_utf8_file_read(test_filename, &read_sb); + RUN_TEST(err == C_ERR_OK, "UTF-8 data read from disk successfully"); + + // Verify that the BOM was skipped and data size matches exactly + RUN_TEST(read_sb.size == write_sb.size, "File reader successfully filtered out the 3 BOM byte footprints from data tracking metrics"); + RUN_TEST(strcmp(read_sb.buffer, payload) == 0, "Reconstructed disk text payload holds proper string characters perfectly"); + + // Cleanup resources and temporary test files + c_StringBuffer_Destroy(&write_sb); + c_StringBuffer_Destroy(&read_sb); + remove(test_filename); // Remove transient testing file assets from system layout + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 27. UTF-8 File Logging Append & Streaming Line Read Verification */ + c_StringBuffer_t io_sb; + c_StringBuffer_t line_sb; + const char* log_filename = "nlp_stream_test.txt"; + + c_StringBuffer_Init(&io_sb, 32, 0); + c_StringBuffer_Init(&line_sb, 32, 0); + + // Test Point 1: Consecutive appends to evaluate new file vs exist rules + c_StringBuffer_AppendStr(&io_sb, "First Line: 自然语言\n"); + err = c_utf8_file_append(log_filename, &io_sb, C_TRUE); // Creates file with BOM + RUN_TEST(err == C_ERR_OK, "Append created a new file with a BOM header successfully"); + + c_StringBuffer_Clear(&io_sb); + c_StringBuffer_AppendStr(&io_sb, "Second Line: 大模型🚀\n"); + err = c_utf8_file_append(log_filename, &io_sb, C_TRUE); // Appends to existing file (BOM skipped) + RUN_TEST(err == C_ERR_OK, "Append safely added data to the existing file without duplicate BOM injections"); + + // Test Point 2: Streaming Line Reads via c_utf8_file_readline + FILE* stream_in = fopen(log_filename, "rb"); + RUN_TEST(stream_in != NULL, "Opened log test file for stream reading"); + + // Handle initial optional BOM detection before streaming lines + unsigned char check_bom[3]; + if (fread(check_bom, 1, 3, stream_in) == 3 && check_bom[0] == 0xEF && check_bom[1] == 0xBB && check_bom[2] == 0xBF) { + // BOM detected and skipped successfully + } else { + fseek(stream_in, 0, SEEK_SET); + } + + // Read Line 1 + err = c_utf8_file_readline(stream_in, &line_sb); + RUN_TEST(err == C_ERR_OK, "Read the first text line via streaming readline API"); + RUN_TEST(strcmp(line_sb.buffer, "First Line: 自然语言") == 0, "Line 1 string matches, trailing newline stripped cleanly"); + + // Read Line 2 + err = c_utf8_file_readline(stream_in, &line_sb); + RUN_TEST(err == C_ERR_OK, "Read the second text line via streaming readline API"); + RUN_TEST(strcmp(line_sb.buffer, "Second Line: 大模型🚀") == 0, "Line 2 string matches multi-byte characters and Emojis perfectly"); + + // Read Line 3 (Expect EOF termination failure status) + err = c_utf8_file_readline(stream_in, &line_sb); + RUN_TEST(err == C_ERR_FAIL, "Readline returns C_ERR_FAIL cleanly when hitting EOF boundaries"); + + // Cleanup resources + fclose(stream_in); + c_StringBuffer_Destroy(&io_sb); + c_StringBuffer_Destroy(&line_sb); + remove(log_filename); // Purge volatile testing file asset + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + printf("==================================================\n"); + printf("\033[32mSUCCESS: ALL C_UTF8_FILE COMPATIBLE INTERFACES PASSED!\033[0m\n"); + printf("==================================================\n"); + return C_ERR_OK; +} + +int main(int argc, char** argv){ + + return c_utf8_file_test(); +}