一些基础组件
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
#include <c_StringList.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef INCLUDED_C_STRINGLIST_H
|
||||
#define INCLUDED_C_STRINGLIST_H
|
||||
|
||||
#ifndef INCLUDED_C_TYPES_H
|
||||
#include <c_Types.h>
|
||||
#endif /*INCLUDED_C_TYPES_H*/
|
||||
|
||||
#ifndef INCLUDED_C_ALLOCATOR_H
|
||||
#include <c_Allocator.h>
|
||||
#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*/
|
||||
@@ -0,0 +1,105 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,817 @@
|
||||
#include <c_utf8.h>
|
||||
|
||||
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 <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
#ifndef INCLUDED_C_UTF8_H
|
||||
#define INCLUDED_C_UTF8_H
|
||||
|
||||
#ifndef INCLUDED_C_TYPES_H
|
||||
#include <c_Types.h>
|
||||
#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*/
|
||||
@@ -0,0 +1,302 @@
|
||||
#include "c_utf8.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* --- 单元测试模块集成 --- */
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
#include <c_utf8_file.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef INCLUDED_C_UTF8_FILE_H
|
||||
#define INCLUDED_C_UTF8_FILE_H
|
||||
|
||||
#ifndef INCLUDED_C_STRINGBUFFER_H
|
||||
#include <c_StringBuffer.h>
|
||||
#endif /*INCLUDED_C_STRINGBUFFER_H*/
|
||||
|
||||
#ifndef INCLUDED_C_UTF8_H
|
||||
#include <c_utf8.h>
|
||||
#endif /*INCLUDED_C_UTF8_H*/
|
||||
|
||||
#ifndef INCLUDED_STDIO_H
|
||||
#define INCLUDED_STDIO_H
|
||||
#include <stdio.h>
|
||||
#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*/
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "c_utf8_file.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
Reference in New Issue
Block a user