一些基础组件

This commit is contained in:
2026-08-30 03:59:45 +08:00
parent 37fddc0bdd
commit cac925fbbb
9 changed files with 2060 additions and 0 deletions
+158
View File
@@ -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;
}
+51
View File
@@ -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*/
+105
View File
@@ -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;
}
+817
View File
@@ -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;
}
+242
View File
@@ -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*/
+302
View File
@@ -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();
}
+208
View File
@@ -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
}
+61
View File
@@ -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*/
+116
View File
@@ -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();
}