diff --git a/Base/c_Platform.h b/Base/c_Platform.h index 4f7604e..9d35609 100644 --- a/Base/c_Platform.h +++ b/Base/c_Platform.h @@ -1,6 +1,11 @@ #ifndef INCLUDED_C_PLATFORM_H #define INCLUDED_C_PLATFORM_H +#ifndef INCLUDED_C_CONFIG_H +#include +#endif /*INCLUDED_C_CONFIG_H*/ + + /* ============================================================================== * 🎯 1. 编译器类型常量(严格以 C_ 开头) * ============================================================================== */ @@ -127,5 +132,14 @@ #define C_ENVIRONMENT_IS_MINGW 0 #endif +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#if (C_SIZEOF_VOID_P==8) +#define C_PLATFORM_IS_64BIT +#elif (C_SIZEOF_VOID_P==4) +#define C_PLATFORM_IS_32BIT +#endif + #endif /*INCLUDED_C_PLATFORM_H*/ diff --git a/Base/c_Types.h b/Base/c_Types.h index 8ea8253..7d891a9 100644 --- a/Base/c_Types.h +++ b/Base/c_Types.h @@ -108,6 +108,7 @@ typedef int c_err_t; #define C_ERR_OUTOFBOUND (-6) #define C_ERR_EMPTY (-7) #define C_ERR_FULL (-8) +#define C_ERR_ALREADY_EXISTS (-9) #define C_SUCCESS C_ERR_OK @@ -118,5 +119,9 @@ typedef int c_err_t; #define C_TRUE true #define C_FALSE false +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef c_int_t c_index_t; #endif /*INCLUDED_C_TYPES_H*/ diff --git a/Foundation/c_Array.c b/Foundation/c_Array.c new file mode 100644 index 0000000..85ae74b --- /dev/null +++ b/Foundation/c_Array.c @@ -0,0 +1,114 @@ +#include +#include + +c_err_t c_Array_Init(c_Array_t* array, c_size_t length, c_size_t size) { + if (!array) return C_ERR_PARAM; + + array->length = length; + array->size = size; + if (length>0) { + array->array = C_CALLOC(length, size); + if (!array->array) { + return C_ERR_NOMEM; + } + }else { + array->array = NULL; + } + + return C_ERR_OK; +} + +void c_Array_Destroy(c_Array_t* array) { + if (!array) return; + C_FREE(array->array); + array->size = 0; + array->length = 0; +} + +c_Array_t* c_Array_New(c_size_t length, c_size_t size) { + c_Array_t* array; + C_NEW(array); + if (!array) { + return NULL; + } + c_Array_Init(array, length, size); + return array; +} + +void c_Array_Delete(c_Array_t** array) { + if (!array || !*array) return; + C_FREE((*array)->array); + C_FREE(*array); +} + +c_size_t c_Array_Size(c_Array_t* array) { + if (!array) return 0; + return array->size; +} + +c_size_t c_Array_Length(c_Array_t* array) { + if (!array) return 0; + return array->length; +} + +c_err_t c_Array_Get(c_Array_t* array, c_size_t index, void** data) { + if (!array || (index >= array->length) || !data) return C_ERR_PARAM; + *data = array->array + index * array->size; + return C_ERR_OK; +} + +c_err_t c_Array_Put(c_Array_t* array, c_size_t index, void* value) { + if (!array || !value || (index>=array->length)) return C_ERR_PARAM; + memcpy(array->array + index * array->size, value, array->size); + return C_ERR_OK; +} + +c_err_t c_Array_Resize(c_Array_t* array, c_size_t length) { + if (!array) return C_ERR_PARAM; + if (length==0) { + C_FREE(array->array); + }else if (array->length==0) { + array->array = C_ALLOC(length*array->size); + if (!array->array) { + return C_ERR_NOMEM; + } + }else { + array->array = C_REALLOC(array->array, length*array->size); + if (!array->array) { + return C_ERR_NOMEM; + } + } + array->length = length; + return C_ERR_OK; +} + +c_Array_t* c_Array_Copy(c_Array_t* array, c_size_t length) { + if (!array || length==0) return NULL; + c_Array_t* result; + C_NEW(result); + if (!result) { + return NULL; + } + c_Array_Init(result, length, array->size); + + if (result->length >= array->length && array->length > 0) { + memcpy(result->array, array->array, array->length * array->size); + }else if (array->length > result->length && result->length > 0) { + memcpy(result->array, array->array, result->length * array->size); + } + + return result; +} + +c_err_t c_Array_CopyTo(const c_Array_t* array, c_Array_t* copy) { + if (!array || !copy) return C_ERR_PARAM; + if (copy->length >= array->length && array->length > 0) { + memcpy(copy->array, array->array, array->length * array->size); + return C_ERR_OK; + }else if (array->length > copy->length && copy->length > 0) { + memcpy(copy->array, array->array, copy->length * array->size); + return C_ERR_OK; + } + return C_ERR_FAIL; +} + diff --git a/Foundation/c_Array.h b/Foundation/c_Array.h new file mode 100644 index 0000000..3fc56ec --- /dev/null +++ b/Foundation/c_Array.h @@ -0,0 +1,39 @@ +#ifndef INCLUDED_C_ARRAY_H +#define INCLUDED_C_ARRAY_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_Array_t { + c_size_t length; + c_size_t size; + uint8_t* array; +}c_Array_t; + +c_err_t c_Array_Init(c_Array_t* array, c_size_t length, c_size_t size); + +void c_Array_Destroy(c_Array_t* array); + +c_Array_t* c_Array_New(c_size_t length, c_size_t size); + +void c_Array_Delete(c_Array_t** array); + +c_size_t c_Array_Size(c_Array_t* array); + +c_size_t c_Array_Length(c_Array_t* array); + +c_err_t c_Array_Get(c_Array_t* array, c_size_t index, void** data); + +c_err_t c_Array_Put(c_Array_t* array, c_size_t index, void* value); + +c_err_t c_Array_Resize(c_Array_t* array, c_size_t length); + +c_Array_t* c_Array_Copy(c_Array_t* array, c_size_t length); + +c_err_t c_Array_CopyTo(const c_Array_t* array, c_Array_t* copy); + +#endif /*INCLUDED_C_ARRAY_H*/ diff --git a/Foundation/c_Array.t.c b/Foundation/c_Array.t.c new file mode 100644 index 0000000..a4cd76c --- /dev/null +++ b/Foundation/c_Array.t.c @@ -0,0 +1,141 @@ +#include "c_Array.h" +#include +#include +#include "c_Test.h" + + +/* ========================================================================== */ +/* TEST CASES */ +/* ========================================================================== */ + +C_TEST_FRAME_INIT(); + +// 1. Stack Initialization and Destruction Loop +void test_array_stack_init_destroy(void) { + c_Array_t arr; + c_err_t err = c_Array_Init(&arr, 5, sizeof(int)); + + C_ASSERT_EQ_INT(C_SUCCESS, err); + C_ASSERT_EQ_INT(5, c_Array_Length(&arr)); + C_ASSERT_EQ_INT(sizeof(int), c_Array_Size(&arr)); + C_ASSERT_PTR_NOT_NULL(arr.array); + + c_Array_Destroy(&arr); +} + +// 2. Heap Dynamic Allocation Lifecycles +void test_array_heap_new_delete(void) { + c_Array_t* arr = c_Array_New(10, sizeof(double)); + + C_ASSERT_PTR_NOT_NULL(arr); + C_ASSERT_EQ_INT(10, c_Array_Length(arr)); + C_ASSERT_EQ_INT(sizeof(double), c_Array_Size(arr)); + + c_Array_Delete(&arr); + C_ASSERT_PTR_NULL(arr); // Ensure pointer is zeroed out by reference parameter modification +} + +// 3. Put and Get Data Element Scenarios +void test_array_put_and_get(void) { + c_Array_t* arr = c_Array_New(3, sizeof(int)); + C_ASSERT_PTR_NOT_NULL(arr); + C_ASSERT_EQ_INT(3, arr->length); + + int val1 = 100, val2 = 200, val3 = 300; + + // Put elements inside length limits + C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Put(arr, 0, &val1)); + C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Put(arr, 1, &val2)); + C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Put(arr, 2, &val3)); + + // Out of bounds checks + int val_bad = 999; + C_ASSERT_EQ_INT(C_ERR_PARAM, c_Array_Put(arr, 3, &val_bad)); + + // Get verification + void* fetch_ptr = NULL; + C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Get(arr, 1, &fetch_ptr)); + C_ASSERT_PTR_NOT_NULL(fetch_ptr); + C_ASSERT_EQ_INT(200, *(int*)fetch_ptr); + + // Out of bounds get verification + C_ASSERT_EQ_INT(C_ERR_PARAM, c_Array_Get(arr, 5, &fetch_ptr)); + + c_Array_Delete(&arr); +} + +// 4. Memory Resizing Limits Verification +void test_array_resize(void) { + c_Array_t* arr = c_Array_New(2, sizeof(int)); + int val0 = 42, val1 = 84; + c_Array_Put(arr, 0, &val0); + c_Array_Put(arr, 1, &val1); + + // Resize up to 4 elements + C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Resize(arr, 4)); + C_ASSERT_EQ_INT(4, c_Array_Length(arr)); + + // Verify older elements remain structurally untouched + void* res_ptr = NULL; + C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Get(arr, 1, &res_ptr)); + C_ASSERT_EQ_INT(84, *(int*)res_ptr); + + // Resize down to 1 element + C_ASSERT_EQ_INT(C_SUCCESS, c_Array_Resize(arr, 1)); + C_ASSERT_EQ_INT(1, c_Array_Length(arr)); + + // Index 1 should now be unreachable / out of bounds + C_ASSERT_EQ_INT(C_ERR_PARAM, c_Array_Get(arr, 1, &res_ptr)); + + c_Array_Delete(&arr); +} + +// 5. Deep Copy Execution Verification +void test_array_copy_and_copy_to(void) { + c_Array_t* source = c_Array_New(3, sizeof(int)); + int a = 11, b = 22, c = 33; + c_Array_Put(source, 0, &a); + c_Array_Put(source, 1, &b); + c_Array_Put(source, 2, &c); + + // Test c_Array_Copy (creates a new array object on the heap) + c_Array_t* copied_arr = c_Array_Copy(source, 2); // copy only first 2 items + C_ASSERT_PTR_NOT_NULL(copied_arr); + C_ASSERT_EQ_INT(2, c_Array_Length(copied_arr)); + + void* data_ptr = NULL; + c_Array_Get(copied_arr, 1, &data_ptr); + C_ASSERT_EQ_INT(22, *(int*)data_ptr); + + // Test c_Array_CopyTo (copies into an already initialized destination) + c_Array_t dest; + c_Array_Init(&dest, 3, sizeof(int)); + + C_ASSERT_EQ_INT(C_SUCCESS, c_Array_CopyTo(source, &dest)); + + c_Array_Get(&dest, 2, &data_ptr); + C_ASSERT_EQ_INT(33, *(int*)data_ptr); + + // Clean up all resources + c_Array_Delete(&copied_arr); + c_Array_Destroy(&dest); + c_Array_Delete(&source); +} + +/* ========================================================================== */ +/* SUITE BINDINGS & ENTRY MAIN */ +/* ========================================================================== */ +static +void array_data_structure_suite(void) { + C_TEST_CASE_RUN(test_array_stack_init_destroy); + C_TEST_CASE_RUN(test_array_heap_new_delete); + C_TEST_CASE_RUN(test_array_put_and_get); + C_TEST_CASE_RUN(test_array_resize); + C_TEST_CASE_RUN(test_array_copy_and_copy_to); +} + +int main(void) { + C_TEST_SUITE_RUN(array_data_structure_suite); + C_TEST_FRAME_REPORT(); + return (g_test_ctx.tests_passed == g_test_ctx.tests_run) ? 0 : 1; +} \ No newline at end of file diff --git a/Foundation/c_ArrayList.c b/Foundation/c_ArrayList.c new file mode 100644 index 0000000..8d72bb7 --- /dev/null +++ b/Foundation/c_ArrayList.c @@ -0,0 +1,84 @@ +#include +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define DEFAULT_INITIAL_CAPACITY 4 + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_ArrayList_Init(c_ArrayList_t* self, c_size_t obj_size, c_size_t capacity) { + if (!self || obj_size == 0) return C_ERR_PARAM; + + self->obj_size = (int)obj_size; + self->capacity = (capacity > 0) ? capacity : DEFAULT_INITIAL_CAPACITY; + self->size = 0; + + // 分配連續記憶體空間:容量 * 單個物件大小 + self->array = C_ALLOC(self->capacity * self->obj_size); + if (!self->array) { + self->capacity = 0; + return C_ERR_NOMEM; + } + + return C_ERR_OK; +} + +void c_ArrayList_Destroy(c_ArrayList_t* self) { + if (!self) return; + C_FREE(self->array); + self->capacity = 0; + self->size = 0; + self->obj_size=0; +} + +c_err_t c_ArrayList_Add(c_ArrayList_t* self, void* obj) { + if (!self || !self->array || !obj) return C_ERR_PARAM; + + // 動態擴容邏輯 + if (self->size >= self->capacity) { + const c_size_t new_capacity = self->capacity<<1; + void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size); + if (!new_array) { + return C_ERR_NOMEM; + } + self->array = new_array; + self->capacity = new_capacity; + } + + // 計算目標記憶體地址並將物件內容複製進去 + char* target_addr = (char*)self->array + (self->size * self->obj_size); + memcpy(target_addr, obj, self->obj_size); + + self->size++; + return C_ERR_OK; +} + +void* c_ArrayList_Get(c_ArrayList_t* self, c_size_t index) { + if (!self || !self->array || index >= self->size) { + return NULL; + } + // 回傳內部記憶體塊中該元素的實際起始地址 + return (char*)self->array + (index * self->obj_size); +} + +c_err_t c_ArrayList_Remove(c_ArrayList_t* self, c_size_t index) { + if (!self || !self->array) return C_ERR_PARAM; + if (index >= self->size) return C_ERR_OUTOFBOUND; + + // 如果刪除的不是最後一個元素,需要將後方所有物件往前平移一個單位 + if (index < self->size - 1) { + char* dest = (char*)self->array + (index * self->obj_size); + const char* src = dest + self->obj_size; + const c_size_t num_elements_to_move = self->size - index - 1; + + // 使用 memmove 處理重疊記憶體區塊的複製 + memmove(dest, src, num_elements_to_move * self->obj_size); + } + + self->size--; + return C_ERR_OK; +} + diff --git a/Foundation/c_ArrayList.h b/Foundation/c_ArrayList.h new file mode 100644 index 0000000..7c99f79 --- /dev/null +++ b/Foundation/c_ArrayList.h @@ -0,0 +1,33 @@ +#ifndef INCLUDED_C_ARRAYLIST_H +#define INCLUDED_C_ARRAYLIST_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +typedef struct { + void* array; + int obj_size; + c_size_t capacity; + c_size_t size; +}c_ArrayList_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_ArrayList_Init(c_ArrayList_t* self, c_size_t obj_size, c_size_t capacity); + +void c_ArrayList_Destroy(c_ArrayList_t* self); + +c_err_t c_ArrayList_Add(c_ArrayList_t* self, void* obj); + +void* c_ArrayList_Get(c_ArrayList_t* self, c_size_t index); + +c_err_t c_ArrayList_Remove(c_ArrayList_t* self, c_size_t index); + +#endif /*INCLUDED_C_ARRAYLIST_H*/ diff --git a/Foundation/c_ArrayList.t.c b/Foundation/c_ArrayList.t.c new file mode 100644 index 0000000..e2399e5 --- /dev/null +++ b/Foundation/c_ArrayList.t.c @@ -0,0 +1,65 @@ +#include "c_ArrayList.h" +#include +#include +#include + +struct Vector2D { + double u, v; +}; + +/* ============================================================================== + * 🧪 测试全新通用 void* 缓冲区的初始化、自动扩容与随机 Remove 重排全周期行为 + * ============================================================================== */ +C_TEST_CASE(test_array_list_full_lifecycle_and_removal) +{ + c_ArrayList_t list; + /* 1. 初始化:装载自定义 Vector2D 结构体,初始最大可容纳元素数量卡死限制为 2 */ + c_err_t init_err = c_ArrayList_Init(&list, sizeof(struct Vector2D), 2); + C_ASSERT_INT_EQ(init_err, C_ERR_OK, "弹性自愈 ArrayList 初始化成功"); + C_ASSERT_INT_EQ(list.size, 0, "有效初始记录必须为 0"); + + struct Vector2D vec1 = { 11.1, 22.2 }; + struct Vector2D vec2 = { 33.3, 44.4 }; + struct Vector2D vec3 = { 55.5, 66.6 }; + + /* 2. 连续推入数据 */ + c_ArrayList_Add(&list, &vec1); + c_ArrayList_Add(&list, &vec2); + + /* 🎯 扩容看点:此时满员,第三次写入将强行逼迫池子在内部启动 2 -> 4 自动翻倍重分配 */ + c_err_t add_err = c_ArrayList_Add(&list, &vec3); + C_ASSERT_INT_EQ(add_err, C_ERR_OK, "耗尽时追加写入,分配器必须完成全自动无感自愈扩容"); + C_ASSERT_INT_EQ(list.size, 3, "当前有效装载数递增至 3"); + + /* 3. 验证数据内容的物理隔离完整度 */ + struct Vector2D* p_check1 = (struct Vector2D*)c_ArrayList_Get(&list, 1); + C_ASSERT(p_check1 != NULL, "随机读取索引 1 节点成功"); + C_ASSERT_DOUBLE_EQ(p_check1->u, 33.3, "重分配内存迁移后,原有位置 1 的数据必须毫发无损"); + + /* 4. 🛠️ 高能测试点:随机抹除中间位置 1 的节点 (即删掉 vec2) + 预期结果:原位置 2 的 vec3 ({55.5, 66.6}) 必须在 O(N) 速度下向前平移,填补顶替空位 1 */ + c_err_t remove_err = c_ArrayList_Remove(&list, 1); + C_ASSERT_INT_EQ(remove_err, C_ERR_OK, "执行中途位置随机移除成功"); + C_ASSERT_INT_EQ(list.size, 2, "移除数据后,有效数据计数平滑扣减为 2"); + + /* 5. 终极完整性断言:现在去 Get 原本的位置 1 */ + struct Vector2D* p_relocated = (struct Vector2D*)c_ArrayList_Get(&list, 1); + C_ASSERT(p_relocated != NULL, "重新获取平移顶替后的位置 1 节点成功"); + + /* 核心断言:原位置 2 的数据现在必须完美出现在位置 1 线上,且精度不发生移位错乱! */ + C_ASSERT_DOUBLE_EQ(p_relocated->u, 55.5, "元素向前滑动对齐后,浮点特征完好无损"); + C_ASSERT_DOUBLE_EQ(p_relocated->v, 66.6, "元素向前滑动对齐后,浮点特征完好无损"); + + /* 6. 边界越界捕获安全防御线 */ + void* invalid_ptr = c_ArrayList_Get(&list, 2); /* 此时由于删了一个,索引 2 已变为空旷越界区 */ + C_ASSERT(invalid_ptr == NULL, "越界获取已经被逻辑截断删除的位置必须安全回传 NULL"); + + c_ArrayList_Destroy(&list); +} + +int main(void) { + C_TEST_SUITE_BEGIN(BaseArrayListNewSpecificationTestSuite) + C_RUN_TEST_CASE(test_array_list_full_lifecycle_and_removal); + C_TEST_SUITE_END() +} + diff --git a/Foundation/c_ArrayQueue.c b/Foundation/c_ArrayQueue.c new file mode 100644 index 0000000..3009095 --- /dev/null +++ b/Foundation/c_ArrayQueue.c @@ -0,0 +1,97 @@ +#include +#include + +#define DEFAULT_INITIAL_CAPACITY 4 + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_ArrayQueue_Init(c_ArrayQueue_t* self, int obj_size, c_size_t capacity) { + if (!self || obj_size == 0) return C_ERR_PARAM; + + self->obj_size = obj_size; + self->capacity = (capacity > 0) ? capacity : DEFAULT_INITIAL_CAPACITY; + self->size = 0; + + self->array = C_ALLOC(self->capacity * self->obj_size); + if (!self->array) { + self->capacity = 0; + return C_ERR_NOMEM; + } + + return C_ERR_OK; +} + +void c_ArrayQueue_Destroy(c_ArrayQueue_t* self) { + if (!self) return; + + C_FREE(self->array); + + self->capacity = 0; + self->size = 0; + self->obj_size = 0; +} + +c_err_t c_ArrayQueue_Push(c_ArrayQueue_t* self, void* obj) { + if (!self || !self->array || !obj) return C_ERR_PARAM; + + if (self->size >= self->capacity) { + const c_size_t new_capacity = self->capacity << 1; + void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size); + if (!new_array) { + return C_ERR_NOMEM; + } + self->array = new_array; + self->capacity = new_capacity; + } + + char* target = (char*)self->array + (self->size * self->obj_size); + memcpy(target, obj, self->obj_size); + self->size++; + + return C_ERR_OK; +} + +c_err_t c_ArrayQueue_Pop(c_ArrayQueue_t* self, void* obj) { + if (!self || !self->array ) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; // 佇列已空 + + char* pop_src = (char*)self->array; // 最前端索引 0 的地址 + + // 1. 直接值複製到呼叫端提供的記憶體中 + if (obj) { + memcpy(obj, pop_src, self->obj_size); + } + + // 2. 如果佇列內還有其他元素,將它們向前平移一個單位 + if (self->size > 1) { + char* dest = pop_src; + const char* src = pop_src + self->obj_size; + const c_size_t num_elements_to_move = self->size - 1; + memmove(dest, src, num_elements_to_move * self->obj_size); + } + + self->size--; + return C_ERR_OK; +} + +c_err_t c_ArrayQueue_Remove(c_ArrayQueue_t* self, c_size_t index) { + if (!self || !self->array) return C_ERR_PARAM; + if (index >= self->size) return C_ERR_OUTOFBOUND; + + if (index < self->size - 1) { + char* dest = (char*)self->array + (index * self->obj_size); + const char* src = dest + self->obj_size; + const c_size_t num_elements_to_move = self->size - index - 1; + memmove(dest, src, num_elements_to_move * self->obj_size); + } + + self->size--; + return C_ERR_OK; +} + +void* c_ArrayQueue_Peek(c_ArrayQueue_t* self) { + if (!self || !self->array || self->size == 0) return NULL; + return self->array; // 最前端索引 0 的起始地址即是 array 本身 +} + diff --git a/Foundation/c_ArrayQueue.h b/Foundation/c_ArrayQueue.h new file mode 100644 index 0000000..012ca81 --- /dev/null +++ b/Foundation/c_ArrayQueue.h @@ -0,0 +1,33 @@ +#ifndef INCLUDED_C_ARRAYQUEUE_H +#define INCLUDED_C_ARRAYQUEUE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + void* array; + int obj_size; + c_size_t capacity; + c_size_t size; +}c_ArrayQueue_t; + + +c_err_t c_ArrayQueue_Init(c_ArrayQueue_t* self, int obj_size, c_size_t capacity); + +void c_ArrayQueue_Destroy(c_ArrayQueue_t* self); + +c_err_t c_ArrayQueue_Push(c_ArrayQueue_t* self, void* obj); + +c_err_t c_ArrayQueue_Pop(c_ArrayQueue_t* self, void* obj); + +void* c_ArrayQueue_Peek(c_ArrayQueue_t* self); + +c_err_t c_ArrayQueue_Remove(c_ArrayQueue_t* self, c_size_t index); + +#endif /*INCLUDED_C_ARRAYQUEUE_H*/ diff --git a/Foundation/c_ArrayStack.c b/Foundation/c_ArrayStack.c new file mode 100644 index 0000000..dc42697 --- /dev/null +++ b/Foundation/c_ArrayStack.c @@ -0,0 +1,89 @@ +#include "c_ArrayStack.h" +#include + +#define DEFAULT_INITIAL_CAPACITY 4 + +c_err_t c_ArrayStack_Init(c_ArrayStack_t* self, int obj_size, c_size_t capacity) { + if (!self || obj_size <= 0) return C_ERR_PARAM; + + self->obj_size = obj_size; + self->capacity = (capacity > 0) ? capacity : DEFAULT_INITIAL_CAPACITY; + self->size = 0; + + self->array = C_ALLOC(self->capacity * self->obj_size); + if (!self->array) { + self->capacity = 0; + return C_ERR_NOMEM; + } + + return C_ERR_OK; +} + + +void c_ArrayStack_Destroy(c_ArrayStack_t* self) { + if (!self) return; + + C_FREE(self->array); + self->capacity = 0; + self->size = 0; + self->obj_size = 0; +} + +c_err_t c_ArrayStack_Push(c_ArrayStack_t* self, void* obj) { + if (!self || !self->array || !obj) return C_ERR_PARAM; + + if (self->size >= self->capacity) { + const c_size_t new_capacity = self->capacity << 1; + void* new_array = C_REALLOC(self->array, new_capacity * self->obj_size); + if (!new_array) { + return C_ERR_NOMEM; + } + self->array = new_array; + self->capacity = new_capacity; + } + + // 計算頂端目標記憶體地址並寫入資料 + char* target = (char*)self->array + (self->size * self->obj_size); + memcpy(target, obj, self->obj_size); + self->size++; + + return C_ERR_OK; +} + +c_err_t c_ArrayStack_Pop(c_ArrayStack_t* self, void* obj) { + if (!self || !self->array || !obj) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; // 堆疊已空 + + // 取得位於 size - 1 的堆疊頂端元素地址 + const char* pop_src = (char*)self->array + ((self->size - 1) * self->obj_size); + + // 直接複製到呼叫端提供的記憶體中 + memcpy(obj, pop_src, self->obj_size); + + self->size--; + return C_ERR_OK; +} + +void* c_ArrayStack_Peek(c_ArrayStack_t* self) { + if (!self || !self->array || self->size == 0) return NULL; + return (char*)self->array + ((self->size - 1) * self->obj_size); +} + +c_err_t c_ArrayStack_Remove(c_ArrayStack_t* self, c_size_t index) { + if (!self || !self->array) return C_ERR_PARAM; + if (index >= self->size) return C_ERR_OUTOFBOUND; + + // 如果刪除的不是頂端元素,則後續元素需向前平移一個單位 + if (index < self->size - 1) { + char* dest = (char*)self->array + (index * self->obj_size); + const char* src = dest + self->obj_size; + const c_size_t num_elements_to_move = self->size - index - 1; + memmove(dest, src, num_elements_to_move * self->obj_size); + } + + self->size--; + return C_ERR_OK; +} + + + diff --git a/Foundation/c_ArrayStack.h b/Foundation/c_ArrayStack.h new file mode 100644 index 0000000..e33b6cf --- /dev/null +++ b/Foundation/c_ArrayStack.h @@ -0,0 +1,32 @@ +#ifndef INCLUDED_C_ARRAYSTACK_H +#define INCLUDED_C_ARRAYSTACK_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +typedef struct { + void* array; + int obj_size; + c_size_t capacity; + c_size_t size; +} c_ArrayStack_t; + +c_err_t c_ArrayStack_Init(c_ArrayStack_t* self, int obj_size, c_size_t capacity); +void c_ArrayStack_Destroy(c_ArrayStack_t* self); +c_err_t c_ArrayStack_Push(c_ArrayStack_t* self, void* obj); +c_err_t c_ArrayStack_Pop(c_ArrayStack_t* self, void* obj); +void* c_ArrayStack_Peek(c_ArrayStack_t* self); +c_err_t c_ArrayStack_Remove(c_ArrayStack_t* self, c_size_t index); + +C_STATIC_FORCE_INLINE +c_bool_t c_ArrayStack_IsEmpty(c_ArrayStack_t* self) { + return self->size == 0; +} + +#endif /*INCLUDED_C_ARRAYSTACK_H*/ diff --git a/Foundation/c_Atomic.c b/Foundation/c_Atomic.c new file mode 100644 index 0000000..e22b7a7 --- /dev/null +++ b/Foundation/c_Atomic.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_Atomic.h b/Foundation/c_Atomic.h new file mode 100644 index 0000000..33edafb --- /dev/null +++ b/Foundation/c_Atomic.h @@ -0,0 +1,147 @@ +#ifndef INCLUDED_C_ATOMIC_H +#define INCLUDED_C_ATOMIC_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#if defined(_MSC_VER) + #include + + // Windows 平台原子型態定義 + typedef volatile char c_atomic_8_t; + typedef volatile long c_atomic_32_t; + typedef volatile __int64 c_atomic_64_t; + typedef volatile int c_atomic_int_t; + typedef void* volatile c_atomic_ptr_t; + typedef volatile char c_atomic_bool_t; + + // MSVC 8-Bit (uint8_t/bool) 原子加減法模擬 (利用 CAS 迴圈確保硬體原子性) + static inline char _msvc_atomic_fetch_add_8(volatile char* p, char v) { + char old_val; + do { + old_val = *p; + } while (InterlockedCompareExchange8((volatile char*)p, (char)(old_val + v), old_val) != old_val); + return old_val; + } + + static inline char _msvc_atomic_fetch_sub_8(volatile char* p, char v) { + char old_val; + do { + old_val = *p; + } while (InterlockedCompareExchange8((volatile char*)p, (char)(old_val - v), old_val) != old_val); + return old_val; + } + +#elif defined(__GNUC__) || defined(__clang__) + // GCC/Clang 平台原子型態定義 (與標準 C 原始型態無縫相容) + typedef int8_t c_atomic_8_t; + typedef int32_t c_atomic_32_t; + typedef int64_t c_atomic_64_t; + typedef int c_atomic_int_t; + typedef void* c_atomic_ptr_t; + typedef bool c_atomic_bool_t; + + // GCC/Clang 內建函數自帶全自動型態泛型 + #define _C_ATOMIC_LOAD_ANY(p) __atomic_load_n((p), __ATOMIC_SEQ_CST) + #define _C_ATOMIC_ADD_ANY(p, v) __atomic_fetch_add((p), (v), __ATOMIC_SEQ_CST) + #define _C_ATOMIC_SUB_ANY(p, v) __atomic_fetch_sub((p), (v), __ATOMIC_SEQ_CST) + #define _C_ATOMIC_EXCH_ANY(p, v) __atomic_exchange_n((p), (v), __ATOMIC_SEQ_CST) + +#else + #error "當前編譯器環境不支援硬體級跨平台原子操作!" +#endif + +/* ================================================================================================================== */ +/* 2. 強型態的特化 API 提供 */ + +#define c_atomic_init_8(p, v) (*(p) = (v)) +#define c_atomic_init_32(p, v) (*(p) = (v)) +#define c_atomic_init_64(p, v) (*(p) = (v)) +#define c_atomic_init_int(p, v) (*(p) = (v)) +#define c_atomic_init_ptr(p, v) (*(p) = (v)) +#define c_atomic_init_bool(p, v) (*(p) = (v)) + +#ifdef _MSC_VER + // ---- 8-Bit (uint8_t) ---- + #define c_atomic_load_8(p) InterlockedCompareExchange8((volatile char*)(p), 0, 0) + #define c_atomic_fetch_add_8(p, v) _msvc_atomic_fetch_add_8((volatile char*)(p), (char)(v)) + #define c_atomic_fetch_sub_8(p, v) _msvc_atomic_fetch_sub_8((volatile char*)(p), (char)(v)) + // ---- 32-Bit ---- + #define c_atomic_load_32(p) InterlockedCompareExchange((volatile LONG*)(p), 0, 0) + #define c_atomic_fetch_add_32(p, v) InterlockedExchangeAdd((volatile LONG*)(p), (LONG)(v)) + #define c_atomic_fetch_sub_32(p, v) InterlockedExchangeAdd((volatile LONG*)(p), -(LONG)(v)) + // ---- 64-Bit ---- + #define c_atomic_load_64(p) InterlockedCompareExchange64((volatile LONG64*)(p), 0, 0) + #define c_atomic_fetch_add_64(p, v) InterlockedExchangeAdd64((volatile LONG64*)(p), (LONGLONG)(v)) + #define c_atomic_fetch_sub_64(p, v) InterlockedExchangeAdd64((volatile LONG64*)(p), -(LONGLONG)(v)) + // ---- Pointer (void*) ---- + #define c_atomic_load_ptr(p) InterlockedCompareExchangePointer((void* volatile*)(p), NULL, NULL) + #define c_atomic_exchange_ptr(p, v) InterlockedExchangePointer((void* volatile*)(p), (void*)(v)) + // ---- Bool ---- + #define c_atomic_load_bool(p) (InterlockedCompareExchange8((volatile char*)(p), 0, 0) != 0) + #define c_atomic_exchange_bool(p, v) (InterlockedExchange8((volatile char*)(p), (char)(v)) != 0) +#else + // GCC/Clang 特化映射(全型態直接套用系統內建泛型,編譯效率極高) + #define c_atomic_load_8(p) _C_ATOMIC_LOAD_ANY(p) + #define c_atomic_fetch_add_8(p, v) _C_ATOMIC_ADD_ANY(p, v) + #define c_atomic_fetch_sub_8(p, v) _C_ATOMIC_SUB_ANY(p, v) + + #define c_atomic_load_32(p) _C_ATOMIC_LOAD_ANY(p) + #define c_atomic_fetch_add_32(p, v) _C_ATOMIC_ADD_ANY(p, v) + #define c_atomic_fetch_sub_32(p, v) _C_ATOMIC_SUB_ANY(p, v) + + #define c_atomic_load_64(p) _C_ATOMIC_LOAD_ANY(p) + #define c_atomic_fetch_add_64(p, v) _C_ATOMIC_ADD_ANY(p, v) + #define c_atomic_fetch_sub_64(p, v) _C_ATOMIC_SUB_ANY(p, v) + + #define c_atomic_load_ptr(p) _C_ATOMIC_LOAD_ANY(p) + #define c_atomic_exchange_ptr(p, v) _C_ATOMIC_EXCH_ANY(p, v) + + #define c_atomic_load_bool(p) _C_ATOMIC_LOAD_ANY(p) + #define c_atomic_exchange_bool(p, v) _C_ATOMIC_EXCH_ANY(p, v) +#endif + +// 原生 Int 動態配適映射 +#define c_atomic_load_int(p) (sizeof(int) == 8 ? (int)c_atomic_load_64((c_atomic_64_t*)(p)) : (int)c_atomic_load_32((c_atomic_32_t*)(p))) +#define c_atomic_fetch_add_int(p, v) (sizeof(int) == 8 ? (int)c_atomic_fetch_add_64((c_atomic_64_t*)(p), v) : (int)c_atomic_fetch_add_32((c_atomic_32_t*)(p), v)) +#define c_atomic_fetch_sub_int(p, v) (sizeof(int) == 8 ? (int)c_atomic_fetch_sub_64((c_atomic_64_t*)(p), v) : (int)c_atomic_fetch_sub_32((c_atomic_32_t*)(p), v)) + +/* ================================================================================================================== */ +/* 3. 全型態 C11 _Generic 萬能泛型巨集分派 */ + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define C_ATOMIC_LOAD(p) _Generic(*(p), \ + bool: c_atomic_load_bool((c_atomic_bool_t*)(p)), \ + int8_t: c_atomic_load_8((c_atomic_8_t*)(p)), \ + uint8_t: c_atomic_load_8((c_atomic_8_t*)(p)), \ + int32_t: c_atomic_load_32((c_atomic_32_t*)(p)), \ + uint32_t: c_atomic_load_32((c_atomic_32_t*)(p)), \ + int64_t: c_atomic_load_64((c_atomic_64_t*)(p)), \ + uint64_t: c_atomic_load_64((c_atomic_64_t*)(p)), \ + void*: c_atomic_load_ptr((c_atomic_ptr_t*)(p)) \ + ) + + #define C_ATOMIC_FETCH_ADD(p, v) _Generic(*(p), \ + int8_t: c_atomic_fetch_add_8((c_atomic_8_t*)(p), (v)), \ + uint8_t: c_atomic_fetch_add_8((c_atomic_8_t*)(p), (v)), \ + int32_t: c_atomic_fetch_add_32((c_atomic_32_t*)(p), (v)), \ + uint32_t: c_atomic_fetch_add_32((c_atomic_32_t*)(p), (v)), \ + int64_t: c_atomic_fetch_add_64((c_atomic_64_t*)(p), (v)), \ + uint64_t: c_atomic_fetch_add_64((c_atomic_64_t*)(p), (v)) \ + ) + + #define C_ATOMIC_FETCH_SUB(p, v) _Generic(*(p), \ + int8_t: c_atomic_fetch_sub_8((c_atomic_8_t*)(p), (v)), \ + uint8_t: c_atomic_fetch_sub_8((c_atomic_8_t*)(p), (v)), \ + int32_t: c_atomic_fetch_sub_32((c_atomic_32_t*)(p), (v)), \ + uint32_t: c_atomic_fetch_sub_32((c_atomic_32_t*)(p), (v)), \ + int64_t: c_atomic_fetch_sub_64((c_atomic_64_t*)(p), (v)), \ + uint64_t: c_atomic_fetch_sub_64((c_atomic_64_t*)(p), (v)) \ + ) +#endif + +#endif /*INCLUDED_C_ATOMIC_H*/ diff --git a/Foundation/c_ByteRingBuffer.c b/Foundation/c_ByteRingBuffer.c new file mode 100644 index 0000000..71d1245 --- /dev/null +++ b/Foundation/c_ByteRingBuffer.c @@ -0,0 +1,543 @@ +#include +#include +#include +#include +#include +#include + + +c_err_t c_ByteRingBuffer_Init(c_ByteRingBuffer_t* self, c_size_t capacity) { + if (!self || capacity == 0) return C_ERR_PARAM; + + self->capacity = capacity; + self->head = 0; + self->tail = 0; + self->is_full = C_FALSE; + + self->buffer = (uint8_t*)C_ALLOC(self->capacity); + if (!self->buffer) { + self->capacity = 0; + return C_ERR_NOMEM; + } + + return C_ERR_OK; +} + +void c_ByteRingBuffer_Destroy(c_ByteRingBuffer_t* self) { + if (!self) return; + + C_FREE(self->buffer); + self->capacity = 0; + self->head = 0; + self->tail = 0; + self->is_full = C_FALSE; +} + +// Writes a single byte. O(1) performance. +c_err_t c_ByteRingBuffer_WriteByte(c_ByteRingBuffer_t* self, uint8_t byte) { + if (!self || !self->buffer) return C_ERR_PARAM; + if (self->is_full) return C_ERR_FULL; + + self->buffer[self->tail] = byte; + self->tail = (self->tail + 1) % self->capacity; + + if (self->tail == self->head) { + self->is_full = C_TRUE; + } + + return C_ERR_OK; +} + +// Reads a single byte. O(1) performance. +c_err_t c_ByteRingBuffer_ReadByte(c_ByteRingBuffer_t* self, uint8_t* out_byte) { + if (!self || !self->buffer || !out_byte) return C_ERR_PARAM; + if (c_ByteRingBuffer_IsEmpty(self)) return C_ERR_EMPTY; + + *out_byte = self->buffer[self->head]; + self->head = (self->head + 1) % self->capacity; + self->is_full = C_FALSE; + + return C_ERR_OK; +} + +// Writes an entire chunk of bytes. Returns the total number of bytes successfully written. +c_size_t c_ByteRingBuffer_WriteBuffer(c_ByteRingBuffer_t* self, const uint8_t* src, c_size_t len) { + if (!self || !self->buffer || !src || len == 0) return 0; + + c_size_t bytes_written = 0; + while (bytes_written < len && !self->is_full) { + self->buffer[self->tail] = src[bytes_written]; + self->tail = (self->tail + 1) % self->capacity; + + if (self->tail == self->head) { + self->is_full = C_TRUE; + } + bytes_written++; + } + return bytes_written; +} + +// Reads an entire chunk of bytes. Returns the total number of bytes successfully read. +c_size_t c_ByteRingBuffer_ReadBuffer(c_ByteRingBuffer_t* self, uint8_t* dest, c_size_t len) { + if (!self || !self->buffer || !dest || len == 0) return 0; + + c_size_t bytes_read = 0; + while (bytes_read < len && !c_ByteRingBuffer_IsEmpty(self)) { + dest[bytes_read] = self->buffer[self->head]; + self->head = (self->head + 1) % self->capacity; + self->is_full = C_FALSE; + bytes_read++; + } + return bytes_read; +} + +c_size_t c_ByteRingBuffer_GetSize(const c_ByteRingBuffer_t* self) { + if (!self || !self->buffer) return 0; + if (self->is_full) return self->capacity; + + if (self->tail >= self->head) { + return self->tail - self->head; + } else { + return self->capacity + self->tail - self->head; + } +} + +c_bool_t c_ByteRingBuffer_IsEmpty(const c_ByteRingBuffer_t* self) { + if (!self) return C_TRUE; + return (self->head == self->tail) && !self->is_full; +} + +c_bool_t c_ByteRingBuffer_IsFull(const c_ByteRingBuffer_t* self) { + if (!self) return C_FALSE; + return self->is_full; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +void c_ByteRingBuffer_WriteByteOverwrite(c_ByteRingBuffer_t* self, uint8_t byte) { + if (!self || !self->buffer) return; + + if (self->is_full) { + // Advance head position to discard the oldest item before placing the new data + self->head = (self->head + 1) % self->capacity; + } + + self->buffer[self->tail] = byte; + self->tail = (self->tail + 1) % self->capacity; + + if (self->tail == self->head) { + self->is_full = C_TRUE; + } +} + +c_size_t c_ByteRingBuffer_WriteBufferOverwrite(c_ByteRingBuffer_t* self, const uint8_t* src, c_size_t len) { + if (!self || !self->buffer || !src || len == 0) return 0; + + // Boundary edge-case optimization: If incoming payload exceeds entire capacity, + // only the absolute last subset window matching 'capacity' will survive anyway. + if (len >= self->capacity) { + src += (len - self->capacity); + len = self->capacity; + + // Blindly overwrite entire buffer matrix instantly + memcpy(self->buffer, src, len); + self->head = 0; + self->tail = 0; + self->is_full = C_TRUE; + return len; + } + + c_size_t size = c_ByteRingBuffer_GetSize(self); + c_size_t free_space = self->capacity - size; + + // Track if writing this string length overflows existing configurations + if (len > free_space) { + c_size_t overwrite_count = len - free_space; + self->head = (self->head + overwrite_count) % self->capacity; + } + + // Segment 1: Physical top copy action + c_size_t space_to_end = self->capacity - self->tail; + c_size_t first_chunk = (len < space_to_end) ? len : space_to_end; + memcpy(self->buffer + self->tail, src, first_chunk); + + // Segment 2: Physical circular bottom split copy action + c_size_t second_chunk = len - first_chunk; + if (second_chunk > 0) { + memcpy(self->buffer, src + first_chunk, second_chunk); + self->tail = second_chunk; + } else { + self->tail = (self->tail + first_chunk) % self->capacity; + } + + if (self->tail == self->head) { + self->is_full = C_TRUE; + } else { + self->is_full = C_FALSE; // Only set false if it didn't completely fill out layout thresholds + } + + return len; +} + +c_err_t c_ByteRingBuffer_PeekByte(const c_ByteRingBuffer_t* self, uint8_t* out_byte) { + if (!self || !self->buffer || !out_byte) return C_ERR_PARAM; + if (c_ByteRingBuffer_IsEmpty(self)) return -3; // Underflow + + *out_byte = self->buffer[self->head]; + return C_SUCCESS; +} + +c_size_t c_ByteRingBuffer_PeekBuffer(const c_ByteRingBuffer_t* self, uint8_t* dest, c_size_t len) { + if (!self || !self->buffer || !dest || len == 0) return 0; + + c_size_t available_bytes = c_ByteRingBuffer_GetSize(self); + if (len > available_bytes) { + len = available_bytes; + } + + if (len == 0) return 0; + + // Duplicate standard ReadBuffer chunk copy patterns without changing the 'head' cursor state + c_size_t bytes_to_end = self->capacity - self->head; + c_size_t first_chunk = (len < bytes_to_end) ? len : bytes_to_end; + memcpy(dest, self->buffer + self->head, first_chunk); + + c_size_t second_chunk = len - first_chunk; + if (second_chunk > 0) { + memcpy(dest + first_chunk, self->buffer, second_chunk); + } + + return len; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_size_t c_ByteRingBuffer_Discard(c_ByteRingBuffer_t* self, c_size_t len) { + if (!self || !self->buffer || len == 0) return 0; + + c_size_t available_bytes = c_ByteRingBuffer_GetSize(self); + if (len > available_bytes) { + len = available_bytes; + } + + if (len == 0) return 0; + + self->head = (self->head + len) % self->capacity; + self->is_full = C_FALSE; // Dropping bytes guarantees it is no longer full + + return len; +} + +const uint8_t* c_ByteRingBuffer_GetReadPtr(const c_ByteRingBuffer_t* self, c_size_t* out_contiguous_len) { + if (!self || !self->buffer || !out_contiguous_len) return NULL; + + *out_contiguous_len = 0; + if (c_ByteRingBuffer_IsEmpty(self)) return NULL; + + if (self->tail > self->head) { + // Data path is completely linear up to the tail index position + *out_contiguous_len = self->tail - self->head; + } else { + // Data path wraps around; the first contiguous stretch runs to the array tail boundary + *out_contiguous_len = self->capacity - self->head; + } + + return self->buffer + self->head; +} + +uint8_t* c_ByteRingBuffer_GetWritePtr(const c_ByteRingBuffer_t* self, c_size_t* out_contiguous_len) { + if (!self || !self->buffer || !out_contiguous_len) return NULL; + + *out_contiguous_len = 0; + if (self->is_full) return NULL; + + if (self->tail >= self->head) { + // Free space path runs from tail up to the absolute array wrap boundary + // Special case adjustment: If head is exactly at index 0, we can write up to capacity - 1 + // but since is_full flag tracking isolates capacity limits, write blocks up to standard edge boundaries. + *out_contiguous_len = self->capacity - self->tail; + + // Minor modification check: If head index configuration is further up but tail wraps, + // don't overlap onto the head index area until the next subsequent hardware layout fetch pass. + if (self->head == 0 && *out_contiguous_len == self->capacity) { + // Full allocation window available + } + } else { + // Free space path is bounded cleanly between tail position and head position index spaces + *out_contiguous_len = self->head - self->tail; + } + + return self->buffer + self->tail; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +C_STATIC_FORCE_INLINE +uint8_t c_ByteRingBuffer_GetAtRelativeInternal(const c_ByteRingBuffer_t* self, c_size_t relative_offset) { + c_size_t absolute_index = (self->head + relative_offset) % self->capacity; + return self->buffer[absolute_index]; +} + +c_index_t c_ByteRingBuffer_IndexOfBuffer(const c_ByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) { + if (!self || !self->buffer || !pattern || pattern_len == 0) return C_ERR_FAIL; + + c_size_t total_size = c_ByteRingBuffer_GetSize(self); + if (pattern_len > total_size) return C_ERR_FAIL; + + // Slide search window across the total available size boundary + c_size_t max_search_offset = total_size - pattern_len; + + for (c_size_t offset = 0; offset <= max_search_offset; offset++) { + c_bool_t match_found = C_TRUE; + + // Perform relative window byte sequence comparison + for (c_size_t p_idx = 0; p_idx < pattern_len; p_idx++) { + if (c_ByteRingBuffer_GetAtRelativeInternal(self, offset + p_idx) != pattern[p_idx]) { + match_found = C_FALSE; + break; + } + } + + if (match_found) { + return (c_index_t)offset; // Returns offset relative to current head position + } + } + + return C_ERR_FAIL; +} + +c_index_t c_ByteRingBuffer_IndexOfByte(const c_ByteRingBuffer_t* self, uint8_t target) { + if (!self || !self->buffer) return C_ERR_FAIL; + + c_size_t total_size = c_ByteRingBuffer_GetSize(self); + if (total_size == 0) return C_ERR_FAIL; + + // Linear pass mapping relative pointers natively across internal split layers + for (c_size_t offset = 0; offset < total_size; offset++) { + if (c_ByteRingBuffer_GetAtRelativeInternal(self, offset) == target) { + return (c_index_t)offset; + } + } + + return C_ERR_FAIL; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_index_t c_ByteRingBuffer_LastIndexOfBuffer(const c_ByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) { + if (!self || !self->buffer || !pattern || pattern_len == 0) return C_ERR_FAIL; + + c_size_t total_size = c_ByteRingBuffer_GetSize(self); + if (pattern_len > total_size) return C_ERR_FAIL; + + // Scan backwards from the largest possible relative offset down to index 0 + c_size_t max_search_offset = total_size - pattern_len; + + for (c_size_t offset = max_search_offset; ; offset--) { + c_bool_t match_found = C_TRUE; + + for (c_size_t p_idx = 0; p_idx < pattern_len; p_idx++) { + if (c_ByteRingBuffer_GetAtRelativeInternal(self, offset + p_idx) != pattern[p_idx]) { + match_found = C_FALSE; + break; + } + } + + if (match_found) { + return (c_index_t)offset; + } + + if (offset == 0) break; // Secure unsigned loop breakout guard rail + } + + return C_ERR_FAIL; +} + +c_size_t c_ByteRingBuffer_ReadUntilToken(c_ByteRingBuffer_t* self, const uint8_t* token, c_size_t token_len, uint8_t* dest, c_size_t dest_max_len) { + if (!self || !self->buffer || !token || token_len == 0 || !dest || dest_max_len == 0) return 0; + + // Step 1: Scan for the token sequence location relative to the head + c_index_t match_offset = c_ByteRingBuffer_IndexOfBuffer(self, token, token_len); + if (match_offset == C_ERR_FAIL) { + return 0; // Token sequence not currently hosted inside the window pipeline + } + + // Step 2: Compute total structural extraction requirements including the token depth + c_size_t aggregate_bytes = (c_size_t)match_offset + token_len; + + // Absolute safety check: Safeguard against destination memory container overflows + if (aggregate_bytes > dest_max_len) { + return 0; // Reject processing since target container is too small to safely store the complete payload string + } + + // Step 3: Perform standard destructive consumption via ReadBuffer + c_size_t bytes_extracted = c_ByteRingBuffer_ReadBuffer(self, dest, aggregate_bytes); + return bytes_extracted; +} + +c_err_t c_ByteRingBuffer_GetAtRelative(const c_ByteRingBuffer_t* self, c_size_t relative_offset, uint8_t* out_byte) { + if (!self || !self->buffer || !out_byte) return C_ERR_PARAM; + + // Validate that the request falls inside the populated byte array span + c_size_t active_size = c_ByteRingBuffer_GetSize(self); + if (relative_offset >= active_size) { + return C_ERR_OUTOFBOUND; + } + + // Safely fold the relative offset over physical ring buffer wrapping thresholds + c_size_t absolute_index = (self->head + relative_offset) % self->capacity; + + *out_byte = self->buffer[absolute_index]; + return C_SUCCESS; +} + +c_bool_t c_ByteRingBuffer_Is(const c_ByteRingBuffer_t* self, c_index_t offset, uint8_t value) { + // Return false immediately if the buffer is uninitialized or the offset is negative + if (!self || !self->buffer || offset < 0) { + return C_FALSE; + } + + // Verify that the requested relative offset falls within the currently readable window + c_size_t active_size = c_ByteRingBuffer_GetSize(self); + if ((c_size_t)offset >= active_size) { + return C_FALSE; + } + + // Map the relative offset to the physical, wrapped array index bounds + c_size_t absolute_index = (self->head + (c_size_t)offset) % self->capacity; + + // Evaluate content identity match condition + return (self->buffer[absolute_index] == value) ? C_TRUE : C_FALSE; +} + + +int c_ByteRingBuffer_Memcmp(const c_ByteRingBuffer_t* self, c_size_t offset, const uint8_t* buffer, c_size_t len) { + if (!self || !self->buffer || !buffer) { + return C_ERR_PARAM; + } + if (len == 0) { + return 0; // Empty comparison matches instantly + } + + c_size_t active_size = c_ByteRingBuffer_GetSize(self); + // Boundary check: Verify the comparison window falls entirely within readable buffer constraints + if (offset >= active_size || (offset + len) > active_size) { + return C_ERR_OUTOFBOUND; + } + + // Resolve the real physical starting index inside the memory array + c_size_t absolute_start = (self->head + offset) % self->capacity; + + // Calculate how many contiguous bytes run in a straight line up to the array boundary edge + c_size_t bytes_to_end = self->capacity - absolute_start; + + if (len <= bytes_to_end) { + // Scenario 1: The target verification window is completely contiguous + return memcmp(self->buffer + absolute_start, buffer, len); + } else { + // Scenario 2: The window wraps around the physical edge bounds. Execute a split-block comparison. + + // Pass A: Compare up to the wrapping array edge boundary + int first_segment_match = memcmp(self->buffer + absolute_start, buffer, bytes_to_end); + if (first_segment_match != 0) { + return first_segment_match; // Return mismatch direction immediately + } + + // Pass B: Wrap around to index 0 and compare the remainder sequence + c_size_t remaining_bytes = len - bytes_to_end; + return memcmp(self->buffer, buffer + bytes_to_end, remaining_bytes); + } +} + +c_err_t c_ByteRingBuffer_Strtoul(const c_ByteRingBuffer_t* self, c_size_t offset, int base, unsigned long* out_value, c_size_t* out_end_offset) { + if (!self || !self->buffer || !out_value) return C_ERR_PARAM; + + c_size_t total_size = c_ByteRingBuffer_GetSize(self); + if (offset >= total_size) return C_ERR_OUTOFBOUND; + + // 1. Skip leading whitespace using the internal helper function + c_size_t scan_idx = offset; + while (scan_idx < total_size) { + uint8_t byte = c_ByteRingBuffer_GetAtRelativeInternal(self, scan_idx); + if (!isspace(byte)) break; + scan_idx++; + } + + if (scan_idx == total_size) return C_ERR_PARAM; // Buffer contains only whitespace + + // 2. Measure the exact alphanumeric chunk layout width boundary + c_size_t start_numeric_offset = scan_idx; + c_size_t numeric_len = 0; + while (scan_idx < total_size) { + uint8_t byte = c_ByteRingBuffer_GetAtRelativeInternal(self, scan_idx); + // Track hex modifiers (x, X), signs, and alphanumeric digits + if (!isalnum(byte) && byte != '+' && byte != '-') { + break; + } + numeric_len++; + scan_idx++; + } + + if (numeric_len == 0) return C_ERR_PARAM; + + // 3. Compute absolute pointers and optimize memory operations based on wrap layouts + c_size_t absolute_start = (self->head + start_numeric_offset) % self->capacity; + c_size_t bytes_to_end = self->capacity - absolute_start; + + unsigned long result = 0; + char* parse_end = NULL; + int current_errno = errno; + errno = 0; + + if (numeric_len <= bytes_to_end) { + // Linear path optimization: Parse directly out of the contiguous array space + const char* flat_ptr = (const char*)(self->buffer + absolute_start); + result = strtoul(flat_ptr, &parse_end, base); + + c_size_t parsed_bytes = (c_size_t)(parse_end - flat_ptr); + if (parsed_bytes == 0 || parse_end == flat_ptr) { + errno = current_errno; + return C_ERR_PARAM; + } + + if (errno == ERANGE) return C_ERR_OUTOFBOUND; + + *out_value = result; + if (out_end_offset) { + *out_end_offset = start_numeric_offset + parsed_bytes; + } + } else { + // Fragmented Wrap handling path: Copy across loop slices onto a small stack array + if (numeric_len >= 64) return C_ERR_OUTOFBOUND; // Enforce safe parsing limits + + char stack_scratch[64]; + for (c_size_t i = 0; i < numeric_len; i++) { + stack_scratch[i] = (char)c_ByteRingBuffer_GetAtRelativeInternal(self, start_numeric_offset + i); + } + stack_scratch[numeric_len] = '\0'; // Guarantee safe string boundary termination + + result = strtoul(stack_scratch, &parse_end, base); + c_size_t parsed_bytes = (c_size_t)(parse_end - stack_scratch); + + if (parsed_bytes == 0 || parse_end == stack_scratch) { + errno = current_errno; + return C_ERR_PARAM; + } + + if (errno == ERANGE) return C_ERR_OUTOFBOUND; + + *out_value = result; + if (out_end_offset) { + *out_end_offset = start_numeric_offset + parsed_bytes; + } + } + + errno = current_errno; // Restore system state integrity flags cleanly + return C_SUCCESS; +} diff --git a/Foundation/c_ByteRingBuffer.h b/Foundation/c_ByteRingBuffer.h new file mode 100644 index 0000000..cd5d7b8 --- /dev/null +++ b/Foundation/c_ByteRingBuffer.h @@ -0,0 +1,140 @@ +#ifndef INCLUDED_C_BYTERINGBUFFER_H +#define INCLUDED_C_BYTERINGBUFFER_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + uint8_t* buffer; + c_size_t capacity; + c_size_t head; + c_size_t tail; + c_bool_t is_full; +}c_ByteRingBuffer_t; + +c_err_t c_ByteRingBuffer_Init(c_ByteRingBuffer_t* self, c_size_t capacity); +void c_ByteRingBuffer_Destroy(c_ByteRingBuffer_t* self); + +c_err_t c_ByteRingBuffer_WriteByte(c_ByteRingBuffer_t* self, uint8_t byte); +c_err_t c_ByteRingBuffer_ReadByte(c_ByteRingBuffer_t* self, uint8_t* out_byte); + +c_size_t c_ByteRingBuffer_WriteBuffer(c_ByteRingBuffer_t* self, const uint8_t* src, c_size_t len); +c_size_t c_ByteRingBuffer_ReadBuffer(c_ByteRingBuffer_t* self, uint8_t* dest, c_size_t len); + +c_size_t c_ByteRingBuffer_GetSize(const c_ByteRingBuffer_t* self); +c_bool_t c_ByteRingBuffer_IsEmpty(const c_ByteRingBuffer_t* self); +c_bool_t c_ByteRingBuffer_IsFull(const c_ByteRingBuffer_t* self); + +/* + * Writes a single byte, overwriting the oldest byte if the buffer is full. + */ +void c_ByteRingBuffer_WriteByteOverwrite(c_ByteRingBuffer_t* self, uint8_t byte); + +/* + * Writes a buffer span, overwriting the oldest bytes continuously if capacity is exceeded. + */ +c_size_t c_ByteRingBuffer_WriteBufferOverwrite(c_ByteRingBuffer_t* self, const uint8_t* src, c_size_t len); + +/* + * Inspects a single byte at the head position without removing it. + */ +c_err_t c_ByteRingBuffer_PeekByte(const c_ByteRingBuffer_t* self, uint8_t* out_byte); + +/* + * Inspects up to 'len' bytes starting from the head position without removing them. + * Returns the actual number of bytes peeked. + */ +c_size_t c_ByteRingBuffer_PeekBuffer(const c_ByteRingBuffer_t* self, uint8_t* dest, c_size_t len); + + +/* + * Advances the head pointer to drop up to 'len' bytes without copying data. + * Returns the actual number of bytes dropped. + */ +c_size_t c_ByteRingBuffer_Discard(c_ByteRingBuffer_t* self, c_size_t len); + +/* + * Returns the direct linear address to read the first available contiguous memory block. + * @param out_contiguous_len: Populated with the byte depth length of the straight chunk line. + * @return Pointer into the structural array channel, or NULL if buffer is empty. + */ +const uint8_t* c_ByteRingBuffer_GetReadPtr(const c_ByteRingBuffer_t* self, c_size_t* out_contiguous_len); + +/* + * Returns the direct linear address to write into the first available contiguous free memory block. + * @param out_contiguous_len: Populated with the space depth length of the straight chunk line. + * @return Pointer into the structural array channel, or NULL if buffer is full. + */ +uint8_t* c_ByteRingBuffer_GetWritePtr(const c_ByteRingBuffer_t* self, c_size_t* out_contiguous_len); + +/* + * Searches for the first occurrence of a byte sequence (pattern) within the ring buffer. + * Returns the relative offset from the current head pointer (0 to size-1), or C_ERR_NOT_FOUND. + */ +c_index_t c_ByteRingBuffer_IndexOfBuffer(const c_ByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len); + +/* + * Searches for the first occurrence of a single byte within the ring buffer. + * Returns the relative offset from the current head pointer (0 to size-1), or C_ERR_NOT_FOUND. + */ +c_index_t c_ByteRingBuffer_IndexOfByte(const c_ByteRingBuffer_t* self, uint8_t target); + +/* + * Searches for the last occurrence of a byte sequence within the ring buffer. + * Returns the relative offset from the current head pointer (0 to size-1), or C_ERR_NOT_FOUND. + */ +c_index_t c_ByteRingBuffer_LastIndexOfBuffer(const c_ByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len); + +/* + * Consumes and extracts data into 'dest' up to and including the specified token sequence. + * Returns the total number of bytes read and placed into dest, or 0 if token is not found. + */ +c_size_t c_ByteRingBuffer_ReadUntilToken(c_ByteRingBuffer_t* self, const uint8_t* token, c_size_t token_len, uint8_t* dest, c_size_t dest_max_len); + +/* + * Retrieves a single byte from the buffer at a relative index position from the head. + * @param self: The ring buffer instance. + * @param relative_offset: The offset relative to the head pointer (0 = oldest unread byte, size-1 = newest byte). + * @param out_byte: Destination pointer for the extracted byte. + * @return C_SUCCESS on clean execution, C_ERR_INVALID_PARAM, or C_ERR_OUT_OF_BOUNDS. + */ +c_err_t c_ByteRingBuffer_GetAtRelative(const c_ByteRingBuffer_t* self, c_size_t relative_offset, uint8_t* out_byte); + +/* + * Checks if the byte at a specific relative offset from the head matches the given value. + * @param self: The ring buffer instance. + * @param offset: The relative offset from the current head pointer (0 = oldest unread byte). + * @param value: The expected byte value to compare against. + * @return C_TRUE (1) if it matches perfectly, C_FALSE (0) if it mismatches, is empty, or out of bounds. + */ +c_bool_t c_ByteRingBuffer_Is(const c_ByteRingBuffer_t* self, c_index_t offset, uint8_t value); + +/* + * Compares the contents of the ring buffer starting at a relative offset with an external flat buffer. + * @param self: The ring buffer instance. + * @param offset: The relative offset from the current head pointer to start comparing from. + * @param buffer: The external memory array to compare against. + * @param len: The number of bytes to compare. + * @return 0 if the memory blocks match exactly, < 0 if the ring buffer data is lexicographically smaller, + * > 0 if it is larger. Returns (C_ERR_INVALID_PARAM) or (C_ERR_OUT_OF_BOUNDS) on range violations. + */ +int c_ByteRingBuffer_Memcmp(const c_ByteRingBuffer_t* self, c_size_t offset, const uint8_t* buffer, c_size_t len); + +/* + * Parses an unsigned long value from the ring buffer starting at a specific relative offset. + * @param self: The ring buffer instance. + * @param offset: The relative offset from the current head pointer to start parsing from. + * @param base: The number base system to parse (0 for auto-detection, 2-36). + * @param out_value: Destination pointer for the parsed unsigned long. + * @param out_end_offset: Optional destination pointer for the relative offset immediately following the parsed number. + * @return C_SUCCESS on clean execution, C_ERR_INVALID_PARAM, or C_ERR_OUT_OF_BOUNDS. + */ +c_err_t c_ByteRingBuffer_Strtoul(const c_ByteRingBuffer_t* self, c_size_t offset, int base, unsigned long* out_value, c_size_t* out_end_offset); + + +#endif /*INCLUDED_C_BYTERINGBUFFER_H*/ diff --git a/Foundation/c_ByteRingBuffer.t.c b/Foundation/c_ByteRingBuffer.t.c new file mode 100644 index 0000000..d5eceac --- /dev/null +++ b/Foundation/c_ByteRingBuffer.t.c @@ -0,0 +1,346 @@ +#include "c_ByteRingBuffer.h" +#include +#include +#include + +#define RUN_TEST_CASE(test_func) \ + do { \ + printf("[RUNNING] %-40s ... ", #test_func); \ + fflush(stdout); \ + test_func(); \ + printf("[PASSED]\n"); \ + } while (0) + +void test_ring_buffer_behavior(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 5) == C_SUCCESS); + assert(c_ByteRingBuffer_IsEmpty(&ring) == C_TRUE); + + // Fill to capacity bounds + assert(c_ByteRingBuffer_WriteByte(&ring, 0xAA) == C_SUCCESS); + assert(c_ByteRingBuffer_WriteByte(&ring, 0xBB) == C_SUCCESS); + assert(c_ByteRingBuffer_WriteByte(&ring, 0xCC) == C_SUCCESS); + assert(c_ByteRingBuffer_GetSize(&ring) == 3); + + uint8_t batch_src[3] = {0x11, 0x22, 0x33}; + // Should write only 2 bytes because total capacity limit is 5 + assert(c_ByteRingBuffer_WriteBuffer(&ring, batch_src, 3) == 2); + assert(c_ByteRingBuffer_IsFull(&ring) == C_TRUE); + + // Verify retrieval matches FIFO sequencing rules + uint8_t read_byte = 0; + assert(c_ByteRingBuffer_ReadByte(&ring, &read_byte) == C_SUCCESS); + assert(read_byte == 0xAA); + assert(c_ByteRingBuffer_IsFull(&ring) == C_FALSE); + + // Bulk wrap-around extraction + uint8_t batch_dest[4]; + assert(c_ByteRingBuffer_ReadBuffer(&ring, batch_dest, 4) == 4); + assert(batch_dest[0] == 0xBB); + assert(batch_dest[1] == 0xCC); + assert(batch_dest[2] == 0x11); + assert(batch_dest[3] == 0x22); + + assert(c_ByteRingBuffer_IsEmpty(&ring) == C_TRUE); + c_ByteRingBuffer_Destroy(&ring); +} + +static void test_overwrite_and_peek_mechanics(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 4) == C_SUCCESS); // Capacity = 4 + + // 1. Validate Single Overwrite + c_ByteRingBuffer_WriteByte(&ring, 0x01); + c_ByteRingBuffer_WriteByte(&ring, 0x02); + c_ByteRingBuffer_WriteByte(&ring, 0x03); + c_ByteRingBuffer_WriteByte(&ring, 0x04); // Buffer now full: [0x01, 0x02, 0x03, 0x04] + assert(c_ByteRingBuffer_IsFull(&ring) == C_TRUE); + + c_ByteRingBuffer_WriteByteOverwrite(&ring, 0x05); // 0x01 gets evicted. Head shifts to 0x02 + + uint8_t peek_check = 0; + assert(c_ByteRingBuffer_PeekByte(&ring, &peek_check) == C_SUCCESS); + assert(peek_check == 0x02); // FIFO rules dictate oldest remaining byte is 0x02 + + // 2. Validate Bulk Overwrite Loops + uint8_t incoming_stream[3] = {0x06, 0x07, 0x08}; + // Ring has 4 bytes capacity. Writing 3 bytes over an already full buffer evicts [0x02, 0x03, 0x04] + assert(c_ByteRingBuffer_WriteBufferOverwrite(&ring, incoming_stream, 3) == 3); + + uint8_t verification_dump[4] = {0}; + c_size_t read_out = c_ByteRingBuffer_PeekBuffer(&ring, verification_dump, 4); + assert(read_out == 4); + assert(verification_dump[0] == 0x05); // Preserved from previous transaction + assert(verification_dump[1] == 0x06); + assert(verification_dump[2] == 0x07); + assert(verification_dump[3] == 0x08); + + // 3. Confirm Peek leaves data sequence entirely untouched + assert(c_ByteRingBuffer_GetSize(&ring) == 4); + + c_ByteRingBuffer_Destroy(&ring); +} + +static void test_dma_and_discard_mechanics(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8 + + // Force initialization of data that loops around the internal ring memory map + uint8_t payload[] = {0xA1, 0xA2, 0xA3, 0xA4, 0xA5}; + c_ByteRingBuffer_WriteBuffer(&ring, payload, 5); + + // Discard oldest 2 items: drops 0xA1, 0xA2. Cursors shift. + assert(c_ByteRingBuffer_Discard(&ring, 2) == 2); + assert(c_ByteRingBuffer_GetSize(&ring) == 3); + + // Write some more to force a wrap-around layout + uint8_t wrap_payload[] = {0xB1, 0xB2, 0xB3, 0xB4}; + c_ByteRingBuffer_WriteBuffer(&ring, wrap_payload, 4); // Total size now = 7 bytes + + // Validate GetReadPtr isolates Block Segment 1 cleanly + c_size_t read_chunk_len = 0; + const uint8_t* read_ptr = c_ByteRingBuffer_GetReadPtr(&ring, &read_chunk_len); + + assert(read_ptr != NULL); + // Head was shifted to index 2. 8 - 2 = 6 available linearly up to memory array edge + assert(read_chunk_len == 6); + assert(read_ptr[0] == 0xA3); // First remaining item + + // Clear those processed items via direct execution tracking + c_ByteRingBuffer_Discard(&ring, read_chunk_len); + + // Call secondary pass to capture remaining wrapped bytes + read_ptr = c_ByteRingBuffer_GetReadPtr(&ring, &read_chunk_len); + assert(read_chunk_len == 1); + assert(read_ptr[0] == 0xB4); // Wrapped character check + + c_ByteRingBuffer_Destroy(&ring); +} + +static void test_ring_buffer_index_searching(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8 + + // Force a data write footprint that wraps around the buffer margins + uint8_t standard_fill[] = {0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44}; + c_ByteRingBuffer_WriteBuffer(&ring, standard_fill, 8); + + // Discard 4 elements to position head cursor at absolute index 4 + c_ByteRingBuffer_Discard(&ring, 4); + + // Append sequence to cross edge wrap boundaries cleanly + uint8_t wrap_fill[] = {0x55, 0x66, 0x77}; + c_ByteRingBuffer_WriteBuffer(&ring, wrap_fill, 3); + // Dynamic ring content layout from head: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77] + + // 1. Single byte search lookup match + assert(c_ByteRingBuffer_IndexOfByte(&ring, 0x33) == 2); // Relative offset index 2 from head + assert(c_ByteRingBuffer_IndexOfByte(&ring, 0x99) == C_ERR_FAIL); + + // 2. Pattern buffer sequence scan (testing across structural wrap-around edge boundaries) + uint8_t search_pattern[] = {0x44, 0x55, 0x66}; + c_index_t match_offset = c_ByteRingBuffer_IndexOfBuffer(&ring, search_pattern, 3); + + assert(match_offset == 3); // 0x44 sits exactly at relative offset position 3 + + // Application Workflow Integration: Cleanly discard up to token match prefix point + c_ByteRingBuffer_Discard(&ring, (c_size_t)match_offset); + uint8_t current_head_byte = 0; + c_ByteRingBuffer_PeekByte(&ring, ¤t_head_byte); + assert(current_head_byte == 0x44); // The buffer head has been successfully synchronized to the token location + + c_ByteRingBuffer_Destroy(&ring); +} + +static void test_reverse_search_and_token_stream(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8 + + uint8_t raw_payload[] = {0xAA, 0x11, 0x22, 0xBB, 0x11, 0x22, 0xCC, 0xDD}; + c_ByteRingBuffer_WriteBuffer(&ring, raw_payload, 8); + + uint8_t pattern[] = {0x11, 0x22}; + + // 1. Verify Reverse Pattern Detection Matches Latest Occurrence + assert(c_ByteRingBuffer_IndexOfBuffer(&ring, pattern, 2) == 1); // First pair starts at offset 1 + assert(c_ByteRingBuffer_LastIndexOfBuffer(&ring, pattern, 2) == 4); // Latest pair starts at offset 4 + + // 2. Clear out buffer to run frame serialization test + c_ByteRingBuffer_Discard(&ring, 8); + + uint8_t stream_data[] = {'P', 'a', 'c', 'k', 'e', 't', '\r', '\n'}; + c_ByteRingBuffer_WriteBuffer(&ring, stream_data, 8); + + uint8_t frame_terminator[] = {'\r', '\n'}; + uint8_t output_staging[16] = {0}; + + // Fail Case: Pass a staging array that is structurally too cramped to hold the payload safely + assert(c_ByteRingBuffer_ReadUntilToken(&ring, frame_terminator, 2, output_staging, 5) == 0); + assert(c_ByteRingBuffer_GetSize(&ring) == 8); // Data remains locked safely inside the ring + + // Success Case: Pass a valid container size to execute frame extraction + c_size_t read_bytes = c_ByteRingBuffer_ReadUntilToken(&ring, frame_terminator, 2, output_staging, 16); + assert(read_bytes == 8); + assert(memcmp(output_staging, "Packet\r\n", 8) == 0); + assert(c_ByteRingBuffer_IsEmpty(&ring) == C_TRUE); // Frame has been cleanly consumed from the queue + + c_ByteRingBuffer_Destroy(&ring); +} + +static void test_relative_random_access(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 4) == C_SUCCESS); // Capacity = 4 + + uint8_t seed_data[] = {0x10, 0x20, 0x30}; + c_ByteRingBuffer_WriteBuffer(&ring, seed_data, 3); + + // Shift the head pointer downstream via a single byte consumption + uint8_t discard_sink = 0; + c_ByteRingBuffer_ReadByte(&ring, &discard_sink); // 0x10 dropped. Head points to 0x20 + + // Append items to cross physical wrapping thresholds cleanly + c_ByteRingBuffer_WriteByte(&ring, 0x40); + c_ByteRingBuffer_WriteByte(&ring, 0x50); // Buffer contents: [0x20, 0x30, 0x40, 0x50] + + uint8_t extracted_byte = 0; + + // 1. Verify standard random read coordinates relative to the head position + assert(c_ByteRingBuffer_GetAtRelative(&ring, 0, &extracted_byte) == C_SUCCESS); + assert(extracted_byte == 0x20); // Relative 0 points directly to the current head + + assert(c_ByteRingBuffer_GetAtRelative(&ring, 2, &extracted_byte) == C_SUCCESS); + assert(extracted_byte == 0x40); // Wrapped index check + + assert(c_ByteRingBuffer_GetAtRelative(&ring, 3, &extracted_byte) == C_SUCCESS); + assert(extracted_byte == 0x50); // Newest unread byte check + + // 2. Validate bounds-checking flags + assert(c_ByteRingBuffer_GetAtRelative(&ring, 4, &extracted_byte) == C_ERR_OUTOFBOUND); + assert(c_ByteRingBuffer_GetAtRelative(&ring, 99, &extracted_byte) == C_ERR_OUTOFBOUND); + assert(c_ByteRingBuffer_GetAtRelative(NULL, 0, &extracted_byte) == C_ERR_PARAM); + + c_ByteRingBuffer_Destroy(&ring); +} + +static void test_conditional_is_validator(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 4) == C_SUCCESS); + + uint8_t input_stream[] = {0xAA, 0xBB, 0xCC}; + c_ByteRingBuffer_WriteBuffer(&ring, input_stream, 3); + + // 1. Validate clean true/false matches relative to the head + assert(c_ByteRingBuffer_Is(&ring, 0, 0xAA) == C_TRUE); // Oldest byte at head matches + assert(c_ByteRingBuffer_Is(&ring, 1, 0xBB) == C_TRUE); // Next element matches + assert(c_ByteRingBuffer_Is(&ring, 1, 0x99) == C_FALSE); // Mismatch returns false + + // Consume 1 byte to advance the head pointer and test wrapping boundaries + uint8_t sink = 0; + c_ByteRingBuffer_ReadByte(&ring, &sink); // Head now points to 0xBB + c_ByteRingBuffer_WriteByte(&ring, 0xDD); // Layout: [..., 0xBB, 0xCC, 0xDD] + + // 2. Re-verify offsets after structural pointer wrap shifts + assert(c_ByteRingBuffer_Is(&ring, 0, 0xBB) == C_TRUE); // Head index position 0 is now 0xBB + assert(c_ByteRingBuffer_Is(&ring, 2, 0xDD) == C_TRUE); // Wrapped index element match + + // 3. Confirm error/bounds conditions return false instead of blowing up memory layout boundaries + assert(c_ByteRingBuffer_Is(&ring, 3, 0x00) == C_FALSE); // Out of bounds index + assert(c_ByteRingBuffer_Is(&ring, -5, 0xBB) == C_FALSE); // Negative index handling protection + assert(c_ByteRingBuffer_Is(NULL, 0, 0xBB) == C_FALSE); // NULL safety check + + c_ByteRingBuffer_Destroy(&ring); +} + +static void test_ring_buffer_memcmp(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 6) == C_SUCCESS); // Capacity = 6 + + uint8_t payload[] = {0x00, 0x11, 0x22, 0x33}; + c_ByteRingBuffer_WriteBuffer(&ring, payload, 4); + + // Consume 2 bytes to step the head pointer forward to absolute index 2 + uint8_t sink = 0; + c_ByteRingBuffer_ReadByte(&ring, &sink); + c_ByteRingBuffer_ReadByte(&ring, &sink); // Buffer active layout from head: [0x22, 0x33] + + // Append data to trigger an explicit physical wrap-around edge split layout + uint8_t wrap_payload[] = {0x44, 0x55, 0x66}; + c_ByteRingBuffer_WriteBuffer(&ring, wrap_payload, 3); + // Buffer dynamic content path tracking from head: [0x22, 0x33, 0x44, 0x55, 0x66] + // Physical layout behind indices inside array: [0x55, 0x66, 0x22, 0x33, 0x44, ...] + + // 1. Validate contiguous segment match comparisons + uint8_t check_a[] = {0x22, 0x33}; + assert(c_ByteRingBuffer_Memcmp(&ring, 0, check_a, 2) == 0); // Perfect contiguous match + + // 2. Validate multi-segment wrap-around comparison mechanics + uint8_t check_b[] = {0x33, 0x44, 0x55, 0x66}; + assert(c_ByteRingBuffer_Memcmp(&ring, 1, check_b, 4) == 0); // Perfect split wrap-around match + + // 3. Mismatch checks + uint8_t check_mismatch[] = {0x33, 0x44, 0x99, 0x66}; + assert(c_ByteRingBuffer_Memcmp(&ring, 1, check_mismatch, 4) != 0); // Identifies internal divergence + + // 4. Bounds and parameter checks + assert(c_ByteRingBuffer_Memcmp(&ring, 0, check_b, 100) == C_ERR_OUTOFBOUND); // Request width overflows content + assert(c_ByteRingBuffer_Memcmp(&ring, 99, check_b, 1) == C_ERR_OUTOFBOUND); // Start pointer invalid + assert(c_ByteRingBuffer_Memcmp(NULL, 0, check_b, 1) == C_ERR_PARAM); + + c_ByteRingBuffer_Destroy(&ring); +} +static void test_ring_buffer_strtoul(void) { + c_ByteRingBuffer_t ring; + assert(c_ByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8 + + // 1. Standard Linear Base-10 Parsing + uint8_t input_a[] = {'1', '2', '3', '4', ' ', 'A', 'B', 'C'}; + c_ByteRingBuffer_WriteBuffer(&ring, input_a, 8); + + unsigned long parsed_val = 0; + c_size_t end_offset = 0; + + assert(c_ByteRingBuffer_Strtoul(&ring, 0, 10, &parsed_val, &end_offset) == C_SUCCESS); + assert(parsed_val == 1234); + assert(end_offset == 4); // Points exactly to the trailing space character offset + + // Reset buffer tracking lines + c_ByteRingBuffer_Discard(&ring, 8); + + // 2. Fragmented Wrap Hex Parsing + // Pre-fill 5 elements to push cursor indices near wrapping layout boundaries + uint8_t pre_fill[] = {0, 0, 0, 0, 0}; + c_ByteRingBuffer_WriteBuffer(&ring, pre_fill, 5); + c_ByteRingBuffer_Discard(&ring, 5); // Head pointer sits at physical index 5 + + // Write Hex parameter payload string ("0x2F") across memory boundaries + uint8_t input_hex[] = {'0', 'x', '2', 'F'}; + c_ByteRingBuffer_WriteBuffer(&ring, input_hex, 4); + + assert(c_ByteRingBuffer_Strtoul(&ring, 0, 16, &parsed_val, &end_offset) == C_SUCCESS); + assert(parsed_val == 47); // 0x2F translates to decimal 47 + assert(end_offset == 4); + + c_ByteRingBuffer_Destroy(&ring); +} + + +int main() { + printf("==================================================\n"); + printf(" Starting c_ByteRingBuffer Unit Testing Suite\n"); + printf("==================================================\n\n"); + + RUN_TEST_CASE(test_ring_buffer_behavior); + RUN_TEST_CASE(test_overwrite_and_peek_mechanics); + RUN_TEST_CASE(test_dma_and_discard_mechanics); + RUN_TEST_CASE(test_ring_buffer_index_searching); + RUN_TEST_CASE(test_reverse_search_and_token_stream); + RUN_TEST_CASE(test_relative_random_access); + RUN_TEST_CASE(test_conditional_is_validator); + RUN_TEST_CASE(test_ring_buffer_memcmp); + RUN_TEST_CASE(test_ring_buffer_strtoul); + + printf("\n==================================================\n"); + printf(" Success! Byte RingBuffer tests passed!\n"); + printf("==================================================\n"); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_Cond.c b/Foundation/c_Cond.c new file mode 100644 index 0000000..7ba2151 --- /dev/null +++ b/Foundation/c_Cond.c @@ -0,0 +1,88 @@ +#include + +#if defined(PLATFORM_POSIX) +#include +#include +#endif + +c_err_t c_Cond_Init(c_Cond_t* cond) { + if (!cond) return C_ERR_PARAM; + +#if defined(PLATFORM_WINDOWS) + // Windows 的条件变量初始化只是一个清零操作,不会失败 + InitializeConditionVariable(&cond->handle); + cond->is_initialized = C_TRUE; + return C_ERR_OK; +#elif defined(PLATFORM_POSIX) + if (pthread_cond_init(&cond->handle, NULL) == 0) { + cond->is_initialized = C_TRUE; + return C_ERR_OK; + } + return C_ERR_FAIL; +#endif +} + + +void c_Cond_Destroy(c_Cond_t* cond) { + if (!cond || !cond->is_initialized) return; + +#if defined(PLATFORM_WINDOWS) + // Windows 的 CONDITION_VARIABLE 不需要显式销毁(内核会自动回收) +#elif defined(PLATFORM_POSIX) + pthread_cond_destroy(&cond->handle); +#endif + cond->is_initialized = C_FALSE; +} + +void c_Cond_Wait(c_Cond_t* cond, c_Mutex_t* mutex) { + if (!cond || !cond->is_initialized || !mutex || !mutex->is_initialized) return; + +#if defined(PLATFORM_WINDOWS) + // SleepConditionVariableCS 会自动在内部释放传入的锁,并在唤醒时重新重新获取锁 + SleepConditionVariableCS(&cond->handle, &mutex->handle, INFINITE); +#elif defined(PLATFORM_POSIX) + pthread_cond_wait(&cond->handle, &mutex->handle); +#endif +} + +c_bool_t c_Cond_TimedWait(c_Cond_t* cond, c_Mutex_t* mutex, c_uint_t timeout_ms) { + if (!cond || !cond->is_initialized || !mutex || !mutex->is_initialized) return false; + +#if defined(PLATFORM_WINDOWS) + // 返回非 0 表示成功(收到信号),返回 0 表示超时 + return SleepConditionVariableCS(&cond->handle, &mutex->handle, timeout_ms) != 0; +#elif defined(PLATFORM_POSIX) + struct timespec ts; + struct timeval tv; + gettimeofday(&tv, NULL); + + // 计算绝对终止时间 + long long ns = (long long)tv.tv_usec * 1000 + (long long)timeout_ms * 1000000; + ts.tv_sec = tv.tv_sec + ns / 1000000000LL; + ts.tv_nsec = ns % 1000000000LL; + + // 返回 0 表示成功收到信号 + return pthread_cond_timedwait(&cond->handle, &mutex->handle, &ts) == 0; +#endif +} + +void c_Cond_Signal(c_Cond_t* cond) { + if (!cond || !cond->is_initialized) return; + +#if defined(PLATFORM_WINDOWS) + WakeConditionVariable(&cond->handle); +#elif defined(PLATFORM_POSIX) + pthread_cond_signal(&cond->handle); +#endif +} + +void c_Cond_Broadcast(c_Cond_t* cond) { + if (!cond || !cond->is_initialized) return; + +#if defined(PLATFORM_WINDOWS) + WakeAllConditionVariable(&cond->handle); +#elif defined(PLATFORM_POSIX) + pthread_cond_broadcast(&cond->handle); +#endif +} + diff --git a/Foundation/c_Cond.h b/Foundation/c_Cond.h new file mode 100644 index 0000000..6ff2f07 --- /dev/null +++ b/Foundation/c_Cond.h @@ -0,0 +1,32 @@ +#ifndef INCLUDED_C_COND_H +#define INCLUDED_C_COND_H + +#ifndef INCLUDED_C_MUTEX_H +#include +#endif /*INCLUDED_C_MUTEX_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { +#if defined(PLATFORM_WINDOWS) + CONDITION_VARIABLE handle; +#elif defined(PLATFORM_POSIX) + pthread_cond_t handle; +#endif + c_bool_t is_initialized; +} c_Cond_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_Cond_Init(c_Cond_t* cond); +void c_Cond_Destroy(c_Cond_t* cond); +void c_Cond_Wait(c_Cond_t* cond, c_Mutex_t* mutex); +c_bool_t c_Cond_TimedWait(c_Cond_t* cond, c_Mutex_t* mutex, c_uint_t timeout_ms); +void c_Cond_Signal(c_Cond_t* cond); +void c_Cond_Broadcast(c_Cond_t* cond); + + + +#endif /*INCLUDED_C_COND_H*/ diff --git a/Foundation/c_Console.c b/Foundation/c_Console.c new file mode 100644 index 0000000..8102b3f --- /dev/null +++ b/Foundation/c_Console.c @@ -0,0 +1,571 @@ +#include +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + +#else +#include +#include +#include +#include +#endif + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// 仅在 POSIX (Linux/macOS) 平台下使用的全局变量,用于恢复终端 +#if defined(_WIN32) && defined(_WIN64) +// 备份用户进入程序前的原始代码页 +static UINT orig_in_cp = 0; +static UINT orig_out_cp = 0; +#else +static struct termios orig_termios; +static int is_terminal_initialized = 0; +#endif + +// 内部私有回调:用于程序退出时自动恢复终端属性 +static void c_Console_ResetOnExit(void) { +#if defined(_WIN32) || defined(_WIN64) + // 恢复 Windows 用户原本的代码页环境,避免污染用户的 CMD/PowerShell 终端 + if (orig_in_cp != 0) SetConsoleCP(orig_in_cp); + if (orig_out_cp != 0) SetConsoleOutputCP(orig_out_cp); +#else + // Linux 恢复原始 Raw Mode 设置(参考上一轮代码) + extern void disable_raw_mode(void); + disable_raw_mode(); +#endif +} + +/** + * @brief 跨平台初始化控制台环境 + * Windows: 激活全局 ANSI 转义序列支持 + * Linux/macOS: 关闭行缓冲、关闭按键回显,并注册退出恢复钩子 + */ +void c_Console_Init(void) { + // 1. 全平台通用的 C 标准库本地化声明(促使 printf/scanf 内部行为适配 UTF-8) + setlocale(LC_ALL, ".UTF-8"); + +#if defined(_WIN32) || defined(_WIN64) + // ---------------------------------------------------- + // Windows 平台:激活 VT 模式并全面强制 UTF-8 (65001) + // ---------------------------------------------------- + // 备份旧代码页 + orig_in_cp = GetConsoleCP(); + orig_out_cp = GetConsoleOutputCP(); + + // 强行设为 UTF-8 编码(65001 == CP_UTF8) + SetConsoleCP(CP_UTF8); // 影响控制台输入(如 scanf/fgets) + SetConsoleOutputCP(CP_UTF8); // 影响控制台输出(如 printf) + + // 注册退出钩子,以便程序正常退出或 exit 时自动还原环境 + atexit(c_Console_ResetOnExit); + + // 激活虚拟终端 (VT) 从而支持 ANSI 转义序列 + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut != INVALID_HANDLE_VALUE) { + DWORD dwMode = 0; + if (GetConsoleMode(hOut, &dwMode)) { + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, dwMode); + } + } + + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + if (hIn!=INVALID_HANDLE_VALUE) { + DWORD mode; + // 1. 获取当前控制台的输入模式 + if (GetConsoleMode(hIn, &mode)) { + // 2. 启用鼠标输入 + mode |= ENABLE_MOUSE_INPUT; + + // 3. 禁用快速编辑模式(非常关键!如果不禁用,鼠标事件会被控制台自身拦截用于复制文本) + mode &= ~ENABLE_QUICK_EDIT_MODE; + + // 4. 启用扩展属性(如果要修改 QUICK_EDIT,必须同时带上这个标志) + mode |= ENABLE_EXTENDED_FLAGS; + + // 5. 应用新模式 + SetConsoleMode(hIn, mode); + } + } + + +#else + // ---------------------------------------------------- + // Linux/macOS 平台:开启 Raw Mode(沿用上一轮逻辑) + // ---------------------------------------------------- + extern struct termios orig_termios; + extern int is_terminal_initialized; + + if (!isatty(STDIN_FILENO)) return; + if (tcgetattr(STDIN_FILENO, &orig_termios) < 0) return; + + atexit(c_Console_ResetOnExit); + is_terminal_initialized = 1; + + struct termios raw = orig_termios; + raw.c_lflag &= ~(ICANON | ECHO); // 禁用回显和标准缓冲 + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +#endif + + // 确保标准输出缓冲区立即刷新 + fflush(stdout); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +int c_Console_kbhit(void) { +#if defined(_WIN32) || defined(_WIN64) + return _kbhit(); +#else + struct timeval tv = {0, 0}; + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + return select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv) > 0; +#endif +} + + +int c_Console_getch(void) { +#if defined(_WIN32) || defined(_WIN64) + return _getch(); +#else + char ch = 0; + if (read(STDIN_FILENO, &ch, 1) < 0) return 0; + return ch; +#endif +} + +void c_Console_Sleep(int milliseconds) { +#if defined(_WIN32) || defined(_WIN64) + Sleep(milliseconds); +#else + usleep(milliseconds * 1000); +#endif +} +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +void c_Console_BlankLine(int y) { + // 1. 将光标移动到目标行的第 1 列 + // \033[%d;1H : 移动到第 y 行,第 1 列 + printf("\033[%d;1H", y); + + // 2. 清除从光标位置到行尾的所有内容(现代终端推荐直接用 2K 清除整行) + // \033[2K : 清除光标所在的整行内容,但光标位置保持不变 + printf("\033[2K"); + + // 3. 强制刷新输出缓冲区,确保屏幕立刻更新 + fflush(stdout); +} + +/** + * @brief 从指定坐标 (x, y) 开始清空到该行的末尾 + */ +void c_Console_BlankLineFrom(int x, int y) { + printf("\033[%d;%dH", y, x); // 移动到 (x, y) + printf("\033[0K"); // 清除从当前光标到行尾 + fflush(stdout); +} + +/** + * @brief 跨平台移动控制台光标到指定坐标 + * @param x 目标列坐标 (Column/Horizontal),从 1 开始计,自左向右递增 + * @param y 目标行坐标 (Row/Vertical),从 1 开始计,自上向下递增 + */ +void c_Console_GotoXY(int x, int y) { + // 防御性保护:ANSI 坐标必须从 1 开始 + if (x < 1) x = 1; + if (y < 1) y = 1; + + // \033[%d;%dH : 第一个参数是行(y),第二个参数是列(x) + // 这是 ANSI X3.64 标准的固定顺序,切勿将 x 和 y 的位置颠倒 + printf("\033[%d;%dH", y, x); + + // 强制刷新缓冲区,确保光标立即移动到位 + fflush(stdout); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +void c_Console_GetSize(int *width, int *height) { + if (!width || !height) return; + +#if defined(_WIN32) || defined(_WIN64) + CONSOLE_SCREEN_BUFFER_INFO csbi; + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(hOut, &csbi)) { + // srWindow 存储了当前可视窗口的矩形边界(0-based 坐标) + *width = csbi.srWindow.Right - csbi.srWindow.Left + 1; + *height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + } else { + *width = 80; // 失败时的兜底默认值 + *height = 25; + } +#else + struct winsize w; + // 使用 ioctl 的 TIOCGWINSZ 标志直接获取终端窗口大小 + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) { + *width = w.ws_col; + *height = w.ws_row; + } else { + *width = 80; // 失败时的兜底默认值 + *height = 25; + } +#endif +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +int c_Console_GetVisualWidth(const char *str) { + if (!str) return 0; + + int visual_width = 0; + int i = 0; + + while (str[i] != '\0') { + unsigned char c = (unsigned char)str[i]; + + if (c < 0x80) { + // ---------------------------------------------------- + // 1. ASCII 字符 (0x00 - 0x7F) -> 占 1 字节,视觉宽度 1 + // ---------------------------------------------------- + visual_width += 1; + i += 1; + } + else if ((c & 0xE0) == 0xC0) { + // ---------------------------------------------------- + // 2. 2字节 UTF-8 字符 (如部分拉丁文、希腊字母) -> 视觉宽度 1 + // ---------------------------------------------------- + visual_width += 1; + i += 2; + } + else if ((c & 0xF0) == 0xE0) { + // ---------------------------------------------------- + // 3. 3字节 UTF-8 字符 (绝大多数中日韩汉字、常用标点) -> 视觉宽度 2 + // ---------------------------------------------------- + visual_width += 2; + i += 3; + } + else if ((c & 0xF8) == 0xF0) { + // ---------------------------------------------------- + // 4. 4字节 UTF-8 字符 (如 Emoji 表情、生僻字) -> 视觉宽度 2 + // ---------------------------------------------------- + visual_width += 2; + i += 4; + } + else { + // 异常安全降级处理 + i += 1; + } + } + return visual_width; +} + +void c_Console_PrintCenter(int y, int box_width, const char *str) { + if (!str) return; + + // 1. 计算文本的实际视觉宽度 + int text_width = c_Console_GetVisualWidth(str); + + // 2. 计算居中所需的左侧起始 X 坐标 (1-based 坐标系) + int start_x = (box_width - text_width) / 2 + 1; + if (start_x < 1) start_x = 1; // 边界防御 + + // 3. 跨平台移动光标并打印 + // 注意:需要使用外部已实现的 c_Console_GotoXY,此处以标准 ANSI 语法演示 + printf("\033[%d;%dH%s", y, start_x, str); + fflush(stdout); +} + +void c_Console_PrintLeftAligned(const char *str, int align_len) { + if (!str) return; + + // 打印文本 + printf("%s", str); + + // 计算实际占用的宽度,并动态补齐缺失的物理空格 + int text_width = c_Console_GetVisualWidth(str); + int padding = align_len - text_width; + + for (int i = 0; i < padding; i++) { + putchar(' '); + } +} + + +int c_Console_WriteXY(int x, int y, const char *format, ...) { + // 1. 防御性保护:确保坐标合法 + if (x < 1) x = 1; + if (y < 1) y = 1; + + // 2. 移动光标到目标坐标 (使用 ANSI CUP 序列) + printf("\033[%d;%dH", y, x); + + // 3. 处理 C 语言可变参数并安全输出 + va_list args; + va_start(args, format); + int result = vprintf(format, args); // 核心:使用 vprintf 将参数包直接输出到控制台 + va_end(args); + + // 4. 强制刷新输出缓冲区,确保文本和光标立即更新,防止画面滞后 + fflush(stdout); + + return result; +} + + +int c_Console_WriteColorXY(int x, int y, c_ConsoleColor_t fg, c_ConsoleColor_t bg, const char *format, ...) { + // 1. 防御性保护:确保坐标合法 + if (x < 1) x = 1; + if (y < 1) y = 1; + + // 2. 移动光标到指定位置 + printf("\033[%d;%dH", y, x); + + // 3. 构建并发送 ANSI 颜色控制序列 + // 标准前景色: 30-37, 高亮前景色: 90-97 + // 标准背景色: 40-47, 高亮背景色: 100-107 + if (fg != C_COLOR_NONE) { + if (fg < 8) { + printf("\033[%dm", 30 + fg); // 标准前景色 + } else { + printf("\033[%dm", 90 + (fg - 8)); // 高亮前景色 + } + } + if (bg != C_COLOR_NONE) { + if (bg < 8) { + printf("\033[%dm", 40 + bg); // 标准背景色 + } else { + printf("\033[%dm", 100 + (bg - 8));// 高亮背景色 + } + } + + // 4. 处理可变参数并安全输出文本 + va_list args; + va_start(args, format); + int result = vprintf(format, args); + va_end(args); + + // 5. 关键:格式化文本打印结束后,**必须重置颜色**,否则会污染后续的所有打印 + printf("\033[0m"); + + // 6. 强制刷新输出缓冲区 + fflush(stdout); + + return result; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +void c_Console_EnableMouse(void) { +#if defined(_WIN32) || defined(_WIN64) + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hIn, &dwMode); + // 启用鼠标输入标志 + dwMode |= ENABLE_MOUSE_INPUT; + // 如果启用了快速编辑模式(QuickEdit),会导致鼠标点击被系统拦截变成选中文字,必须关闭 + dwMode &= ~ENABLE_QUICK_EDIT_MODE; + SetConsoleMode(hIn, dwMode); +#else + // Linux/macOS: 发送 ANSI 序列开启鼠标追踪 + // \033[?1003h: 追踪所有鼠标动作(包括移动、点击、释放) + // \033[?1006h: 启用 SGR 鼠标编码模式(现代终端标配,坐标支持超过 255) + printf("\033[?1003h\033[?1006h"); + fflush(stdout); +#endif +} + + +void c_Console_DisableMouse(void) { +#if !defined(_WIN32) && !defined(_WIN64) + printf("\033[?1003l\033[?1006l"); // 关闭鼠标追踪 + fflush(stdout); +#endif +} + +/** + * @brief 跨平台非阻塞读取鼠标事件 + * @param mouse_evt 用于接收转换后的通用鼠标事件结构体 + * @return int 如果成功捕获到鼠标事件返回 1,否则返回 0 + */ +int c_Console_ReadMouse(c_ConsoleMouseEvent_t *mouse_evt) { + if (!mouse_evt) return 0; + mouse_evt->type = MOUSE_EVENT_NONE; + +#if defined(_WIN32) || defined(_WIN64) + // ---------------------------------------------------- + // Windows 平台实现 + // ---------------------------------------------------- + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + DWORD numEvents = 0; + + // 检查输入缓冲区中是否有事件 + GetNumberOfConsoleInputEvents(hIn, &numEvents); + if (numEvents == 0) return 0; + + INPUT_RECORD inRec; + DWORD numRead = 0; + // 窥视而不直接移出,先确认是不是鼠标事件 + PeekConsoleInput(hIn, &inRec, 1, &numRead); + + if (numRead > 0 && inRec.EventType == MOUSE_EVENT) { + // 确实是鼠标事件,正式读出 + ReadConsoleInput(hIn, &inRec, 1, &numRead); + MOUSE_EVENT_RECORD mer = inRec.Event.MouseEvent; + + // 转换坐标 (Windows 是 0-based,我们要统一转换为 1-based) + mouse_evt->x = mer.dwMousePosition.X + 1; + mouse_evt->y = mer.dwMousePosition.Y + 1; + + // 判断事件类型 + if (mer.dwEventFlags == 0) { + // 点击或释放 + if (mer.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) { + mouse_evt->type = MOUSE_EVENT_PRESS_LEFT; + } else if (mer.dwButtonState & RIGHTMOST_BUTTON_PRESSED) { + mouse_evt->type = MOUSE_EVENT_PRESS_RIGHT; + } else if (mer.dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) { + mouse_evt->type = MOUSE_EVENT_PRESS_MIDDLE; + } else { + mouse_evt->type = MOUSE_EVENT_RELEASE; + } + } else if (mer.dwEventFlags == MOUSE_WHEELED) { + // 滚轮滚动 (HIWORD 为正向上,为负向下) + if ((short)HIWORD(mer.dwButtonState) > 0) { + mouse_evt->type = MOUSE_EVENT_WHEEL_UP; + } else { + mouse_evt->type = MOUSE_EVENT_WHEEL_DOWN; + } + } else if (mer.dwEventFlags == MOUSE_MOVED) { + mouse_evt->type = MOUSE_EVENT_MOVE; + } + return 1; + } else if (numRead > 0) { + // 如果不是鼠标事件(比如是键盘事件),则丢弃或由其他函数处理 + ReadConsoleInput(hIn, &inRec, 1, &numRead); + } + +#else + // ---------------------------------------------------- + // Linux/macOS 平台实现 (解析 SGR 鼠标协议序列) + // SGR 格式一般为: \033[<按钮代号>;X坐标;Y坐标M (或 m 代表释放) + // ---------------------------------------------------- + + if (!c_Console_kbhit()) return 0; + + int ch = c_Console_getch(); + if (ch == 27) { // 捕获到 ESC (\033) + if (c_Console_getch() == '[') { + if (c_Console_getch() == '<') { + int btn = 0, x = 0, y = 0; + char action = 0; + + // 解析 SGR 密文参数,例如 "0;45;12M" + // 现代终端标准,直接使用 scanf 变体或手动循环读取 + if (scanf("%d;%d;%d%c", &btn, &x, &y, &action) == 4) { + mouse_evt->x = x; + mouse_evt->y = y; + + if (action == 'm') { + mouse_evt->type = MOUSE_EVENT_RELEASE; + } else if (action == 'M') { + if (btn == 0) mouse_evt->type = MOUSE_EVENT_PRESS_LEFT; + else if (btn == 1) mouse_evt->type = MOUSE_EVENT_PRESS_MIDDLE; + else if (btn == 2) mouse_evt->type = MOUSE_EVENT_PRESS_RIGHT; + else if (btn == 32) mouse_evt->type = MOUSE_EVENT_MOVE; // 伴随左键的移动 + else if (btn == 35) mouse_evt->type = MOUSE_EVENT_MOVE; // 纯移动 + else if (btn == 64) mouse_evt->type = MOUSE_EVENT_WHEEL_UP; + else if (btn == 65) mouse_evt->type = MOUSE_EVENT_WHEEL_DOWN; + } + return 1; + } + } + } + } +#endif + return 0; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 跨平台精准读取并转换键盘按键(包含方向键、Esc、Enter) + * @note 必须确保终端已通过 c_Console_Init() 切换到非阻塞/Raw 模式 + * @return int 返回转换后的统一 c_ConsoleKeyCode_t 键值,若为普通字符则原样返回其 ASCII 码 + */ +int c_Console_ReadKey(void) { + if (!c_Console_kbhit()) return C_KEY_UNKNOWN; + + int ch = c_Console_getch(); + +#if defined(_WIN32) || defined(_WIN64) + // ---------------------------------------------------- + // Windows 平台:处理扩展键双字节 + // ---------------------------------------------------- + if (ch == 0 || ch == 224) { + // 遇到特殊前缀,必须紧接着读取第二个字节 + int sub_ch = _getch(); + switch (sub_ch) { + case 72: return C_KEY_UP; + case 80: return C_KEY_DOWN; + case 75: return C_KEY_LEFT; + case 77: return C_KEY_RIGHT; + default: return C_KEY_UNKNOWN; + } + } + + // Windows 的回车可能返回 '\r' (13),统一规范化 + if (ch == '\r' || ch == '\n') return C_KEY_ENTER; + + return ch; // 普通 ASCII 字符 (如 'w', 'a', 's', 'd') 直接原样返回 + +#else + // ---------------------------------------------------- + // Linux/macOS 平台:处理 ANSI 键盘转义序列流 + // ---------------------------------------------------- + if (ch == 27) { // 捕获到第一个字节是 ESC + // 立即检查缓冲区,看后面有没有跟着字符。如果没有,说明用户真的只按了独立的 Esc 键 + if (!c_Console_kbhit()) { + return C_KEY_ESC; + } + + int next1 = c_Console_getch(); + if (next1 == '[') { + if (!c_Console_kbhit()) return C_KEY_UNKNOWN; + int next2 = c_Console_getch(); + + // 标准方向键序列判定: ESC [ A/B/C/D + switch (next2) { + case 'A': return C_KEY_UP; + case 'B': return C_KEY_DOWN; + case 'C': return C_KEY_RIGHT; + case 'D': return C_KEY_LEFT; + default: return C_KEY_UNKNOWN; + } + } + return C_KEY_UNKNOWN; + } + + // Linux 退格键适配 + if (ch == 127) return C_KEY_BACKSPACE; + // 回车键统一 + if (ch == '\n' || ch == '\r') return C_KEY_ENTER; + + return ch; // 普通 ASCII 字符原样返回 +#endif +} + diff --git a/Foundation/c_Console.h b/Foundation/c_Console.h new file mode 100644 index 0000000..eccb0a9 --- /dev/null +++ b/Foundation/c_Console.h @@ -0,0 +1,241 @@ +#ifndef INCLUDED_C_CONSOLE_H +#define INCLUDED_C_CONSOLE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_STDIO_H +#define INCLUDED_STDIO_H +#include +#endif /*INCLUDED_STDIO_H*/ + + +#if defined(_WIN32) || defined(_WIN64) + #include + #include +#else + #include + #include + #include +#endif + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// ========================================== +// 2. 终端控制 ANSI 转义宏 (全平台通用) +// ========================================== +#define CONSOLE_CLEAR() printf("\033[2J\033[H") // 清屏并将光标归位 +#define CONSOLE_GOTOXY(x, y) printf("\033[%d;%dH", (y), (x)) // 移动光标 (1-based) +#define CONSOLE_HIDE_CURSOR() printf("\033[?25l") // 隐藏光标 +#define CONSOLE_SHOW_CURSOR() printf("\033[?25h") // 显示光标 +#define CONSOLE_COLOR_RESET() printf("\033[0m") // 重置属性 +#define CONSOLE_COLOR_RED() printf("\033[1;31m") // 高亮红 +#define CONSOLE_COLOR_GREEN() printf("\033[1;32m") // 高亮绿 +#define CONSOLE_COLOR_BLUE() printf("\033[1;34m") // 高亮蓝 + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 控制台 16 色标准颜色枚举(同时适用于前景色和背景色计算) + */ +typedef enum { + C_COLOR_BLACK = 0, + C_COLOR_RED = 1, + C_COLOR_GREEN = 2, + C_COLOR_YELLOW = 3, + C_COLOR_BLUE = 4, + C_COLOR_MAGENTA = 5, + C_COLOR_CYAN = 6, + C_COLOR_WHITE = 7, + + // 高亮色系列(加粗/明亮) + C_COLOR_BRIGHT_BLACK = 8, + C_COLOR_BRIGHT_RED = 9, + C_COLOR_BRIGHT_GREEN = 10, + C_COLOR_BRIGHT_YELLOW = 11, + C_COLOR_BRIGHT_BLUE = 12, + C_COLOR_BRIGHT_MAGENTA = 13, + C_COLOR_BRIGHT_CYAN = 14, + C_COLOR_BRIGHT_WHITE = 15, + + C_COLOR_NONE = -1 // 不改变原有颜色(保持默认) +} c_ConsoleColor_t; + +// 鼠标事件类型 +typedef enum { + MOUSE_EVENT_NONE = 0, + MOUSE_EVENT_PRESS_LEFT, // 左键按下 + MOUSE_EVENT_PRESS_RIGHT, // 右键按下 + MOUSE_EVENT_PRESS_MIDDLE, // 中键按下 + MOUSE_EVENT_RELEASE, // 任意键释放 + MOUSE_EVENT_WHEEL_UP, // 滚轮向上滚动 + MOUSE_EVENT_WHEEL_DOWN, // 滚轮向下滚动 + MOUSE_EVENT_MOVE // 鼠标移动 +} c_ConsoleMouseEventType_t; + +// 统一的鼠标事件结构体 +typedef struct { + c_ConsoleMouseEventType_t type; // 事件类型 + int x; // 触发时的列坐标 (从 1 开始) + int y; // 触发时的行坐标 (从 1 开始) +} c_ConsoleMouseEvent_t; + +typedef enum { + // 基础控制键(单字节即可判定的按键) + C_KEY_UNKNOWN = 0, + C_KEY_ENTER = 13, // 统一回车键 + C_KEY_ESC = 27, // 统一 ESC 键 + C_KEY_SPACE = 32, // 空格键 + C_KEY_BACKSPACE = 127,// 退格键 + + // 特殊扩展按键(方向键) + C_KEY_UP = 1001, + C_KEY_DOWN = 1002, + C_KEY_LEFT = 1003, + C_KEY_RIGHT = 1004 +} c_ConsoleKeyCode_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 跨平台初始化控制台环境 + * Windows: 激活全局 ANSI 转义序列支持 + * Linux/macOS: 关闭行缓冲、关闭按键回显,并注册退出恢复钩子 + */ +void c_Console_Init(void); + +/** + * @brief 跨平台非阻塞检查是否有键盘输入 + * @return + */ +int c_Console_kbhit(void); + +/** + * @brief 跨平台直接读取单个按键字符 + * @return + */ +int c_Console_getch(void); + +/** + * + * @param milliseconds + */ +void c_Console_Sleep(int milliseconds); + +/** + * @brief 跨平台清除控制台中的指定行 + * @param y 目标行的纵坐标 (从 1 开始计) + */ +void c_Console_BlankLine(int y); + + +/** + * @brief 从指定坐标 (x, y) 开始清空到该行的末尾 + */ +void c_Console_BlankLineFrom(int x, int y); + + +/** + * @brief 跨平台移动控制台光标到指定坐标 + * @param x 目标列坐标 (Column/Horizontal),从 1 开始计,自左向右递增 + * @param y 目标行坐标 (Row/Vertical),从 1 开始计,自上向下递增 + */ +void c_Console_GotoXY(int x, int y); + +/** + * @brief 跨平台获取当前控制台窗口的大小(宽高) + * @param width 用于接收总列数(X方向字符数)的指针 + * @param height 用于接收总行数(Y方向字符数)的指针 + */ +void c_Console_GetSize(int *width, int *height); + + +/** + * @brief 在控制台指定行的指定总宽度内,将 UTF-8 文本居中打印 + * @param y 目标行坐标 (从 1 开始) + * @param box_width 容器的总宽度(例如整个控制台的宽度,或一个 UI 矩形框的宽度) + * @param str 要打印的 UTF-8 字符串 + */ +void c_Console_PrintCenter(int y, int box_width, const char *str); + +/** + * @brief 精准计算一个 UTF-8 字符串在控制台上的实际“光标显示宽度” + * @param str 输入的 UTF-8 字符串 + * @return 屏幕实际占用的列数(英文字符算 1,中文/日文/韩文等全角字符算 2) + */ +int c_Console_GetVisualWidth(const char *str); + +/** + * @brief 格式化等宽对齐打印(常用于表格、菜单选项对齐) + * @param str 要打印的文本 + * @param align_len 限定的视觉总宽度(若文本不足此宽度,自动在右侧补齐空格) + */ +void c_Console_PrintLeftAligned(const char *str, int align_len); + +/** + * @brief 跨平台在指定坐标处格式化输出 UTF-8 文本 + * @param x 目标列坐标 (从 1 开始计) + * @param y 目标行坐标 (从 1 开始计) + * @param format 格式化字符串 (与 printf 完全一致,如 "Score: %d") + * @param ... 可变参数 + * @return 成功打印的物理字节数(若失败返回负数) + */ +int c_Console_WriteXY(int x, int y, const char *format, ...); + +/** + * @brief 跨平台在指定坐标处,以指定的颜色格式化输出文本 + * @param x 目标列坐标 (从 1 开始) + * @param y 目标行坐标 (从 1 开始) + * @param fg 前景色 (文字颜色),传入 C_COLOR_NONE 表示不修改 + * @param bg 背景色 (文字底色),传入 C_COLOR_NONE 表示不修改 + * @param format 格式化字符串 + * @param ... 可变参数 + * @return 成功打印的物理字节数 + */ +int c_Console_WriteColorXY(int x, int y, c_ConsoleColor_t fg, c_ConsoleColor_t bg, const char *format, ...); + +/** + * @brief 开启鼠标事件追踪 + */ +void c_Console_EnableMouse(void); + +/** + * @brief 关闭鼠标事件追踪(程序退出时必须调用,避免污染用户终端) + */ +void c_Console_DisableMouse(void); + +/** + * @brief 跨平台非阻塞读取鼠标事件 + * @param mouse_evt 用于接收转换后的通用鼠标事件结构体 + * @return int 如果成功捕获到鼠标事件返回 1,否则返回 0 + */ +int c_Console_ReadMouse(c_ConsoleMouseEvent_t *mouse_evt); + + +/** + * @brief 跨平台精准读取并转换键盘按键(包含方向键、Esc、Enter) + * @note 必须确保终端已通过 c_Console_Init() 切换到非阻塞/Raw 模式 + * @return int 返回转换后的统一 c_ConsoleKeyCode_t 键值,若为普通字符则原样返回其 ASCII 码 + */ +int c_Console_ReadKey(void); + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +void c_Console_HideCursor(void) { + printf("\033[?25l"); +} + +C_STATIC_FORCE_INLINE +void c_Console_ShowCursor(void) { + printf("\033[?25h"); +} + + +#endif /*INCLUDED_C_CONSOLE_H*/ diff --git a/Foundation/c_Console.t.c b/Foundation/c_Console.t.c new file mode 100644 index 0000000..49248d6 --- /dev/null +++ b/Foundation/c_Console.t.c @@ -0,0 +1,59 @@ +#include "c_Console.h" +#include +#include + +int main() { + c_Console_Init(); + CONSOLE_CLEAR(); + CONSOLE_HIDE_CURSOR(); + + int x = 10, y = 5; + int running = 1; + + CONSOLE_GOTOXY(1, 1); + CONSOLE_COLOR_GREEN(); + printf("=== 跨平台控制台框架示例 ==="); + CONSOLE_GOTOXY(1, 2); + CONSOLE_COLOR_RESET(); + printf("控制键: W/A/S/D 移动,Q 退出\n"); + + while (running) { + // 1. 处理输入 + if (c_Console_kbhit()) { + int key = c_Console_getch(); + + // 擦除旧位置 + CONSOLE_GOTOXY(x, y); + printf(" "); + + switch (key) { + case 'w': case 'W': y--; break; + case 's': case 'S': y++; break; + case 'a': case 'A': x--; break; + case 'd': case 'D': x++; break; + case 'q': case 'Q': running = 0; break; + default: break; + } + + // 边界约束 + if (x < 1) x = 1; + if (y < 3) y = 3; + } + + // 2. 渲染画面 + CONSOLE_GOTOXY(x, y); + CONSOLE_COLOR_RED(); + printf("@"); // 绘制玩家 + fflush(stdout); // 刷新标准输出缓冲区(防止字符滞留) + + // 3. 控制帧率 (约 30 FPS) + c_Console_Sleep(33); + } + + // 退出处理 + CONSOLE_CLEAR(); + CONSOLE_SHOW_CURSOR(); + CONSOLE_COLOR_RESET(); + printf("程序已安全退出。\n"); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_Console_Menu.t.c b/Foundation/c_Console_Menu.t.c new file mode 100644 index 0000000..3c379ee --- /dev/null +++ b/Foundation/c_Console_Menu.t.c @@ -0,0 +1,127 @@ +#include "c_Console.h" +#include +#include + +int main(void) { + // 1. 全局初始化控制台环境(支持 UTF-8、启用鼠标追踪、隐藏光标、Raw模式) + c_Console_Init(); + CONSOLE_CLEAR(); + CONSOLE_HIDE_CURSOR(); + + int screen_w = 80, screen_h = 25; + c_Console_GetSize(&screen_w, &screen_h); + + // 2. 绘制可视化测试台的固定 UI 边框与标题 + c_Console_WriteColorXY(1, 1, C_COLOR_BRIGHT_WHITE, C_COLOR_BLUE, "==== 跨平台 c_Console 自动化全功能可视化测试台 ===="); + c_Console_WriteColorXY(1, 2, C_COLOR_BRIGHT_BLACK, C_COLOR_NONE, "操作指南: 按 [方向键 ↑↓←→] 测试键盘,用 [鼠标点击/滚轮] 测试动态事件。按 [Esc] 键退出。"); + + // 3. 绘制 UTF-8 中英文混排对照区,验证等宽对齐算法 + c_Console_WriteColorXY(2, 5, C_COLOR_BRIGHT_CYAN, C_COLOR_NONE, "【1. UTF-8 宽度测试对齐基准线】:"); + c_Console_WriteColorXY(2, 6, C_COLOR_WHITE, C_COLOR_NONE, "字符串A: [C++ 2026]") ; + c_Console_WriteColorXY(2, 7, C_COLOR_WHITE, C_COLOR_NONE, "字符串B: [C语言实现]") ; + + // 动态计算并打印视觉宽度进行自检 + int lenA = c_Console_GetVisualWidth("[C++ 2026]"); + int lenB = c_Console_GetVisualWidth("[C语言实现]"); + c_Console_WriteColorXY(30, 6, C_COLOR_YELLOW, C_COLOR_NONE, "<- 判定宽度: %d (理论应为 10)", lenA); + c_Console_WriteColorXY(30, 7, C_COLOR_YELLOW, C_COLOR_NONE, "<- 判定宽度: %d (理论应为 10)", lenB); + + // 4. 绘制两个交互式虚拟测试按钮供鼠标点击 + c_Console_WriteColorXY(5, 10, C_COLOR_WHITE, C_COLOR_GREEN, " [ 按钮 A ] "); + c_Console_WriteColorXY(20, 10, C_COLOR_WHITE, C_COLOR_MAGENTA, " [ 按钮 B ] "); + + // 键盘移动小方块的起始坐标状态 + int k_x = 5, k_y = 15; + c_Console_WriteColorXY(k_x, k_y, C_COLOR_BRIGHT_RED, C_COLOR_NONE, "■"); + + int running = 1; + while (running) { + // ---------------------------------------------------- + // 测试项 A: 键盘输入流测试 + // ---------------------------------------------------- + int key = c_Console_ReadKey(); + if (key != C_KEY_UNKNOWN) { + // 清空第 12 行并更新键盘状态 + printf("\033[12;1H\033[2K"); + + if (key == C_KEY_ESC) { + running = 0; // 按 Esc 键退出 + } + else if (key == C_KEY_UP || key == C_KEY_DOWN || key == C_KEY_LEFT || key == C_KEY_RIGHT) { + // 擦除旧位置 + c_Console_WriteColorXY(k_x, k_y, C_COLOR_NONE, C_COLOR_NONE, " "); + // 响应方向键 + if (key == C_KEY_UP) k_y--; + if (key == C_KEY_DOWN) k_y++; + if (key == C_KEY_LEFT) k_x -= 2; // 方块占2个物理宽度 + if (key == C_KEY_RIGHT) k_x += 2; + // 边界约束 + if (k_x < 1) k_x = 1; if (k_y < 13) k_y = 13; + // 绘制新位置 + c_Console_WriteColorXY(k_x, k_y, C_COLOR_BRIGHT_RED, C_COLOR_NONE, "■"); + + c_Console_WriteColorXY(2, 12, C_COLOR_BRIGHT_GREEN, C_COLOR_NONE, "键盘响应: [方向键] 成功触发! 当前方块坐标: (%d, %d)", k_x, k_y); + } + else if (key == C_KEY_ENTER) { + c_Console_WriteColorXY(2, 12, C_COLOR_BRIGHT_GREEN, C_COLOR_NONE, "键盘响应: [Enter 回车键] 成功触发!"); + } + else { + c_Console_WriteColorXY(2, 12, C_COLOR_BRIGHT_GREEN, C_COLOR_NONE, "键盘响应: 普通字符键 [%c] ASCII: %d", (char)key, key); + } + } + + // ---------------------------------------------------- + // 测试项 B: 鼠标与滚轮流测试 + // ---------------------------------------------------- + c_ConsoleMouseEvent_t mouse_evt; + if (c_Console_ReadMouse(&mouse_evt)) { + // 在第 18 行实时刷新鼠标动作 + printf("\033[18;1H\033[2K"); + + switch (mouse_evt.type) { + case MOUSE_EVENT_PRESS_LEFT: + c_Console_WriteColorXY(2, 18, C_COLOR_BRIGHT_YELLOW, C_COLOR_NONE, "鼠标响应: 【左键点击】 坐标: (%d, %d)", mouse_evt.x, mouse_evt.y); + + // 按钮矩形区域碰撞检测 + if (mouse_evt.y == 10) { + if (mouse_evt.x >= 5 && mouse_evt.x <= 15) { + c_Console_WriteColorXY(5, 11, C_COLOR_BRIGHT_RED, C_COLOR_NONE, "🔥 触发 A !"); + } else if (mouse_evt.x >= 20 && mouse_evt.x <= 30) { + c_Console_WriteColorXY(20, 11, C_COLOR_BRIGHT_RED, C_COLOR_NONE, "🔥 触发 B !"); + } + } + break; + case MOUSE_EVENT_PRESS_RIGHT: + c_Console_WriteColorXY(2, 18, C_COLOR_BRIGHT_YELLOW, C_COLOR_NONE, "鼠标响应: 【右键点击】 坐标: (%d, %d)", mouse_evt.x, mouse_evt.y); + break; + case MOUSE_EVENT_WHEEL_UP: + c_Console_WriteColorXY(2, 18, C_COLOR_BRIGHT_BLUE, C_COLOR_NONE, "滚轮响应: 【向上滚动 ↑】 坐标: (%d, %d)", mouse_evt.x, mouse_evt.y); + break; + case MOUSE_EVENT_WHEEL_DOWN: + c_Console_WriteColorXY(2, 18, C_COLOR_BRIGHT_BLUE, C_COLOR_NONE, "滚轮响应: 【向下滚动 ↓】 坐标: (%d, %d)", mouse_evt.x, mouse_evt.y); + break; + case MOUSE_EVENT_MOVE: + // 右下角高频显示当前准心坐标 + c_Console_WriteColorXY(screen_w - 20, 3, C_COLOR_BRIGHT_BLACK, C_COLOR_NONE, "鼠标悬停: (%03d, %03d)", mouse_evt.x, mouse_evt.y); + break; + case MOUSE_EVENT_RELEASE: + // 释放鼠标时清理按钮下方的触发提示字样 + printf("\033[11;1H\033[2K"); + break; + default: + break; + } + } + + // 控制刷新帧率(约 50 FPS),避免 CPU 满载 + c_Console_Sleep(20); + } + + // 5. 退出处理:清屏并让 atexit 自动恢复终端原始设置 + printf("\033[2J\033[H"); + CONSOLE_CLEAR(); + CONSOLE_SHOW_CURSOR(); + CONSOLE_COLOR_RESET(); + return 0; +} + diff --git a/Foundation/c_FastByteRingBuffer.c b/Foundation/c_FastByteRingBuffer.c new file mode 100644 index 0000000..1838ea0 --- /dev/null +++ b/Foundation/c_FastByteRingBuffer.c @@ -0,0 +1,544 @@ +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +c_size_t round_up_to_pow2(c_size_t v) { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; +#if (defined(__WORDSIZE) && __WORDSIZE == 64) || defined(_WIN64) || defined(__x86_64__) || defined(__aarch64__) + v |= v >> 32; +#endif + v++; + return v; +} + +C_STATIC_FORCE_INLINE +c_bool_t c_FastByteRingBuffer_IsPow2(c_size_t capacity) { + // A power of 2 must be greater than zero. + // The bitwise trick (capacity & (capacity - 1)) works because a power of 2 + // has exactly one bit set (e.g., 01000). Subtracting 1 flips all bits up to + // that set bit (e.g., 00111). Performing a bitwise AND yields exactly 0. + if (capacity == 0) { + return C_FALSE; + } + return ((capacity & (capacity - 1)) == 0) ? C_TRUE : C_FALSE; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_FastByteRingBuffer_Init(c_FastByteRingBuffer_t* self, c_size_t capacity) { + if (!self || capacity == 0) return C_ERR_PARAM; + if (!c_FastByteRingBuffer_IsPow2(capacity)) { + return C_ERR_PARAM; + } + // 自動向上對齊,確保符合 Power of Two + self->capacity = capacity; + self->mask = self->capacity - 1; // 建立遮罩 + self->head = 0; + self->tail = 0; + self->is_full = C_FALSE; + + self->buffer = (uint8_t*)malloc(self->capacity); + if (!self->buffer) { + self->capacity = 0; + self->mask = 0; + return C_ERR_NOMEM; + } + + return C_ERR_OK; +} + +void c_FastByteRingBuffer_Destroy(c_FastByteRingBuffer_t* self) { + if (!self) return; + + C_FREE(self->buffer); + self->capacity = 0; + self->mask = 0; + self->head = 0; + self->tail = 0; + self->is_full = C_FALSE; +} + +// 寫入單一單元組 (極速 O(1)) +c_err_t c_FastByteRingBuffer_WriteByte(c_FastByteRingBuffer_t* self, uint8_t byte) { + if (!self || !self->buffer) return C_ERR_PARAM; + if (self->is_full) return C_ERR_FULL; + + self->buffer[self->tail] = byte; + + // 使用高速位元與運算取代模除 % + self->tail = (self->tail + 1) & self->mask; + + if (self->tail == self->head) { + self->is_full = C_TRUE; + } + + return C_ERR_OK; +} + +// 讀取單一單元組 (極速 O(1)) +c_err_t c_FastByteRingBuffer_ReadByte(c_FastByteRingBuffer_t* self, uint8_t* out_byte) { + if (!self || !self->buffer || !out_byte) return C_ERR_PARAM; + if (c_FastByteRingBuffer_IsEmpty(self)) return C_ERR_EMPTY; + + *out_byte = self->buffer[self->head]; + + // 使用高速位元與運算取代模除 % + self->head = (self->head + 1) & self->mask; + self->is_full = C_FALSE; + + return C_ERR_OK; +} + + +c_size_t c_FastByteRingBuffer_WriteBuffer(c_FastByteRingBuffer_t* self, const uint8_t* src, c_size_t len) { + if (!self || !self->buffer || !src || len == 0) return 0; + if (self->is_full) return 0; + + // Calculate free space directly using fast size validation tracking + c_size_t current_size = c_FastByteRingBuffer_GetSize(self); + c_size_t free_space = self->capacity - current_size; + + // Clamp write operations to avoid overrunning existing readable elements + if (len > free_space) { + len = free_space; + } + + if (len == 0) return 0; + + // Segment 1: Write from tail index up to the physical end boundary of the backing array + c_size_t space_to_end = self->capacity - self->tail; + c_size_t first_chunk = (len < space_to_end) ? len : space_to_end; + memcpy(self->buffer + self->tail, src, first_chunk); + + // Segment 2: Wrap around to index 0 using bitwise optimizations if a split block configuration is required + c_size_t second_chunk = len - first_chunk; + if (second_chunk > 0) { + memcpy(self->buffer, src + first_chunk, second_chunk); + self->tail = second_chunk; // The wrapped tail calculation reduces cleanly to second_chunk + } else { + // Fast tail stepping path utilizing the mask constant + self->tail = (self->tail + first_chunk) & self->mask; + } + + // Set the full status flag if the cursors perfectly intersect + if (self->tail == self->head) { + self->is_full = C_TRUE; + } + + return len; +} + + +// 區塊讀取 +c_size_t c_FastByteRingBuffer_ReadBuffer(c_FastByteRingBuffer_t* self, uint8_t* dest, c_size_t len) { + if (!self || !self->buffer || !dest || len == 0) return 0; + + c_size_t bytes_read = 0; + while (bytes_read < len && !c_FastByteRingBuffer_IsEmpty(self)) { + dest[bytes_read] = self->buffer[self->head]; + self->head = (self->head + 1) & self->mask; + self->is_full = C_FALSE; + bytes_read++; + } + return bytes_read; +} + +c_size_t c_FastByteRingBuffer_GetSize(const c_FastByteRingBuffer_t* self) { + if (!self || !self->buffer) return 0; + if (self->is_full) return self->capacity; + + if (self->tail >= self->head) { + return self->tail - self->head; + } else { + return self->capacity + self->tail - self->head; + } +} + +c_bool_t c_FastByteRingBuffer_IsEmpty(const c_FastByteRingBuffer_t* self) { + if (!self) return C_TRUE; + return (self->head == self->tail) && !self->is_full; +} + +c_bool_t c_FastByteRingBuffer_IsFull(const c_FastByteRingBuffer_t* self) { + if (!self) return C_FALSE; + return self->is_full; +} + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + + +C_STATIC_FORCE_INLINE +uint8_t c_FastByteRingBuffer_GetAtRelativeInternal(const c_FastByteRingBuffer_t* self, c_size_t relative_offset) { + c_size_t absolute_index = (self->head + relative_offset) & self->mask; + return self->buffer[absolute_index]; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ + +void c_FastByteRingBuffer_WriteByteOverwrite(c_FastByteRingBuffer_t* self, uint8_t byte) { + if (!self || !self->buffer) return; + + if (self->is_full) { + // 環形陣列已滿時,強制將讀取指標向前推一格,拋棄最舊數據 + self->head = (self->head + 1) & self->mask; + } + + self->buffer[self->tail] = byte; + self->tail = (self->tail + 1) & self->mask; + + if (self->tail == self->head) { + self->is_full = C_TRUE; + } +} + +c_size_t c_FastByteRingBuffer_WriteBufferOverwrite(c_FastByteRingBuffer_t* self, const uint8_t* src, c_size_t len) { + if (!self || !self->buffer || !src || len == 0) return 0; + + // 邊界極端優化:如果寫入長度超過總容量,只有最後符合容量大小的數據能存活 + if (len >= self->capacity) { + src += (len - self->capacity); + len = self->capacity; + + memcpy(self->buffer, src, len); + self->head = 0; + self->tail = 0; + self->is_full = C_TRUE; + return len; + } + + c_size_t size = c_FastByteRingBuffer_GetSize(self); + c_size_t free_space = self->capacity - size; + + // 若寫入長度大於剩餘空間,計算溢出量並自動同步前推 head 指標 + if (len > free_space) { + c_size_t overwrite_count = len - free_space; + self->head = (self->head + overwrite_count) & self->mask; + } + + // 分段一:從 tail 寫入到物理內存陣列末尾 + c_size_t space_to_end = self->capacity - self->tail; + c_size_t first_chunk = (len < space_to_end) ? len : space_to_end; + memcpy(self->buffer + self->tail, src, first_chunk); + + // 分段二:折返到內存陣列開頭寫入剩餘數據 + c_size_t second_chunk = len - first_chunk; + if (second_chunk > 0) { + memcpy(self->buffer, src + first_chunk, second_chunk); + self->tail = second_chunk; + } else { + self->tail = (self->tail + first_chunk) & self->mask; + } + + if (self->tail == self->head) { + self->is_full = C_TRUE; + } else { + self->is_full = C_FALSE; + } + + return len; +} + +c_err_t c_FastByteRingBuffer_PeekByte(const c_FastByteRingBuffer_t* self, uint8_t* out_byte) { + if (!self || !self->buffer || !out_byte) return C_ERR_PARAM; + if (c_FastByteRingBuffer_IsEmpty(self)) return C_ERR_EMPTY; + + *out_byte = self->buffer[self->head]; + return C_SUCCESS; +} + +c_size_t c_FastByteRingBuffer_PeekBuffer(const c_FastByteRingBuffer_t* self, uint8_t* dest, c_size_t len) { + if (!self || !self->buffer || !dest || len == 0) return 0; + + c_size_t available_bytes = c_FastByteRingBuffer_GetSize(self); + if (len > available_bytes) { + len = available_bytes; + } + + if (len == 0) return 0; + + // 複製標準讀取邏輯,但完全不變動真實核心 head 指標狀態 + c_size_t bytes_to_end = self->capacity - self->head; + c_size_t first_chunk = (len < bytes_to_end) ? len : bytes_to_end; + memcpy(dest, self->buffer + self->head, first_chunk); + + c_size_t second_chunk = len - first_chunk; + if (second_chunk > 0) { + memcpy(dest + first_chunk, self->buffer, second_chunk); + } + + return len; +} + +c_size_t c_FastByteRingBuffer_Discard(c_FastByteRingBuffer_t* self, c_size_t len) { + if (!self || !self->buffer || len == 0) return 0; + + c_size_t available_bytes = c_FastByteRingBuffer_GetSize(self); + if (len > available_bytes) { + len = available_bytes; + } + + if (len == 0) return 0; + + self->head = (self->head + len) & self->mask; + self->is_full = C_FALSE; + + return len; +} + +const uint8_t* c_FastByteRingBuffer_GetReadPtr(const c_FastByteRingBuffer_t* self, c_size_t* out_contiguous_len) { + if (!self || !self->buffer || !out_contiguous_len) return NULL; + + *out_contiguous_len = 0; + if (c_FastByteRingBuffer_IsEmpty(self)) return NULL; + + if (self->tail > self->head) { + *out_contiguous_len = self->tail - self->head; + } else { + *out_contiguous_len = self->capacity - self->head; + } + + return self->buffer + self->head; +} + +uint8_t* c_FastByteRingBuffer_GetWritePtr(const c_FastByteRingBuffer_t* self, c_size_t* out_contiguous_len) { + if (!self || !self->buffer || !out_contiguous_len) return NULL; + + *out_contiguous_len = 0; + if (self->is_full) return NULL; + + if (self->tail >= self->head) { + *out_contiguous_len = self->capacity - self->tail; + } else { + *out_contiguous_len = self->head - self->tail; + } + + return self->buffer + self->tail; +} + +c_index_t c_FastByteRingBuffer_IndexOfByte(const c_FastByteRingBuffer_t* self, uint8_t target) { + if (!self || !self->buffer) return C_ERR_FAIL; + + c_size_t total_size = c_FastByteRingBuffer_GetSize(self); + for (c_size_t offset = 0; offset < total_size; offset++) { + if (c_FastByteRingBuffer_GetAtRelativeInternal(self, offset) == target) { + return (c_index_t)offset; + } + } + return C_ERR_FAIL; +} + +c_index_t c_FastByteRingBuffer_IndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) { + if (!self || !self->buffer || !pattern || pattern_len == 0) return C_ERR_FAIL; + + c_size_t total_size = c_FastByteRingBuffer_GetSize(self); + if (pattern_len > total_size) return C_ERR_FAIL; + + c_size_t max_search_offset = total_size - pattern_len; + + for (c_size_t offset = 0; offset <= max_search_offset; offset++) { + c_bool_t match_found = C_TRUE; + + for (c_size_t p_idx = 0; p_idx < pattern_len; p_idx++) { + if (c_FastByteRingBuffer_GetAtRelativeInternal(self, offset + p_idx) != pattern[p_idx]) { + match_found = C_FALSE; + break; + } + } + + if (match_found) { + return (c_index_t)offset; + } + } + return C_ERR_FAIL; +} + +c_index_t c_FastByteRingBuffer_LastIndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) { + if (!self || !self->buffer || !pattern || pattern_len == 0) return C_ERR_FAIL; + + c_size_t total_size = c_FastByteRingBuffer_GetSize(self); + if (pattern_len > total_size) return C_ERR_FAIL; + + c_size_t max_search_offset = total_size - pattern_len; + + for (c_size_t offset = max_search_offset; ; offset--) { + c_bool_t match_found = C_TRUE; + + for (c_size_t p_idx = 0; p_idx < pattern_len; p_idx++) { + if (c_FastByteRingBuffer_GetAtRelativeInternal(self, offset + p_idx) != pattern[p_idx]) { + match_found = C_FALSE; + break; + } + } + + if (match_found) { + return (c_index_t)offset; + } + + if (offset == 0) break; + } + return C_ERR_FAIL; +} + +c_size_t c_FastByteRingBuffer_ReadUntilToken(c_FastByteRingBuffer_t* self, const uint8_t* token, c_size_t token_len, uint8_t* dest, c_size_t dest_max_len) { + if (!self || !self->buffer || !token || token_len == 0 || !dest || dest_max_len == 0) return 0; + + c_index_t match_offset = c_FastByteRingBuffer_IndexOfBuffer(self, token, token_len); + if (match_offset == C_ERR_FAIL) { + return 0; + } + + c_size_t aggregate_bytes = (c_size_t)match_offset + token_len; + if (aggregate_bytes > dest_max_len) { + return 0; // 避免目標緩衝區溢出 + } + + return c_FastByteRingBuffer_ReadBuffer(self, dest, aggregate_bytes); +} + +c_err_t c_FastByteRingBuffer_GetAtRelative(const c_FastByteRingBuffer_t* self, c_size_t relative_offset, uint8_t* out_byte) { + if (!self || !self->buffer || !out_byte) return C_ERR_PARAM; + + c_size_t active_size = c_FastByteRingBuffer_GetSize(self); + if (relative_offset >= active_size) { + return C_ERR_OUTOFBOUND; + } + + *out_byte = c_FastByteRingBuffer_GetAtRelativeInternal(self, relative_offset); + return C_SUCCESS; +} + +c_bool_t c_FastByteRingBuffer_Is(const c_FastByteRingBuffer_t* self, c_index_t offset, uint8_t value) { + if (!self || !self->buffer || offset < 0) return C_FALSE; + + c_size_t active_size = c_FastByteRingBuffer_GetSize(self); + if ((c_size_t)offset >= active_size) return C_FALSE; + + return (c_FastByteRingBuffer_GetAtRelativeInternal(self, (c_size_t)offset) == value) ? C_TRUE : C_FALSE; +} + +int c_FastByteRingBuffer_Memcmp(const c_FastByteRingBuffer_t* self, c_size_t offset, const uint8_t* buffer, c_size_t len) { + if (!self || !self->buffer || !buffer) return C_ERR_PARAM; + if (len == 0) return 0; + + c_size_t active_size = c_FastByteRingBuffer_GetSize(self); + if (offset >= active_size || (offset + len) > active_size) { + return C_ERR_OUTOFBOUND; + } + + c_size_t absolute_start = (self->head + offset) & self->mask; + c_size_t bytes_to_end = self->capacity - absolute_start; + + if (len <= bytes_to_end) { + return memcmp(self->buffer + absolute_start, buffer, len); + } else { + int first_segment_match = memcmp(self->buffer + absolute_start, buffer, bytes_to_end); + if (first_segment_match != 0) { + return first_segment_match; + } + return memcmp(self->buffer, buffer + bytes_to_end, len - bytes_to_end); + } +} + + + +c_err_t c_FastByteRingBuffer_Strtoul(const c_FastByteRingBuffer_t* self, c_size_t offset, int base, unsigned long* out_value, c_size_t* out_end_offset) { + if (!self || !self->buffer || !out_value) return C_ERR_PARAM; + + c_size_t total_size = c_FastByteRingBuffer_GetSize(self); + if (offset >= total_size) return C_ERR_OUTOFBOUND; + + // 1. Skip leading spaces safely using fast inline relative scans + c_size_t scan_idx = offset; + while (scan_idx < total_size) { + uint8_t byte = c_FastByteRingBuffer_GetAtRelativeInternal(self, scan_idx); + if (!isspace(byte)) break; + scan_idx++; + } + + if (scan_idx == total_size) return C_ERR_PARAM; // Contains only whitespace + + // 2. Locate trailing delimiters to calculate numeric chunk span width + c_size_t start_numeric_offset = scan_idx; + c_size_t numeric_len = 0; + while (scan_idx < total_size) { + uint8_t byte = c_FastByteRingBuffer_GetAtRelativeInternal(self, scan_idx); + // Include numerical modifiers (+, -), hex identifiers (x, X) and alphanumeric bases + if (!isalnum(byte) && byte != '+' && byte != '-') { + break; + } + numeric_len++; + scan_idx++; + } + + if (numeric_len == 0) return C_ERR_PARAM; + + // 3. Resolve the starting index using bitwise mask instead of slow % operators + c_size_t absolute_start = (self->head + start_numeric_offset) & self->mask; + c_size_t bytes_to_end = self->capacity - absolute_start; + + unsigned long result = 0; + char* parse_end = NULL; + int current_errno = errno; + errno = 0; + + // Fast-path: Segment runs contiguous without wrapping lines + if (numeric_len <= bytes_to_end) { + const char* flat_ptr = (const char*)(self->buffer + absolute_start); + result = strtoul(flat_ptr, &parse_end, base); + + c_size_t parsed_bytes = (c_size_t)(parse_end - flat_ptr); + if (parsed_bytes == 0 || parse_end == flat_ptr) { + errno = current_errno; + return C_ERR_PARAM; + } + + if (errno == ERANGE) return C_ERR_OUTOFBOUND; + + *out_value = result; + if (out_end_offset) { + *out_end_offset = start_numeric_offset + parsed_bytes; + } + } else { + // Slow-path: Structural wrap configuration requires localized stack flattening + if (numeric_len >= 64) return C_ERR_OUTOFBOUND; + + char stack_scratch[64]; + for (c_size_t i = 0; i < numeric_len; i++) { + stack_scratch[i] = (char)c_FastByteRingBuffer_GetAtRelativeInternal(self, start_numeric_offset + i); + } + stack_scratch[numeric_len] = '\0'; // Guarantee absolute zero string termination + + result = strtoul(stack_scratch, &parse_end, base); + c_size_t parsed_bytes = (c_size_t)(parse_end - stack_scratch); + + if (parsed_bytes == 0 || parse_end == stack_scratch) { + errno = current_errno; + return C_ERR_PARAM; + } + + if (errno == ERANGE) return C_ERR_OUTOFBOUND; + + *out_value = result; + if (out_end_offset) { + *out_end_offset = start_numeric_offset + parsed_bytes; + } + } + + errno = current_errno; // Preserve runtime environment variables smoothly + return C_SUCCESS; +} diff --git a/Foundation/c_FastByteRingBuffer.h b/Foundation/c_FastByteRingBuffer.h new file mode 100644 index 0000000..263796d --- /dev/null +++ b/Foundation/c_FastByteRingBuffer.h @@ -0,0 +1,66 @@ +#ifndef INCLUDED_C_FASTBYTERINGBUFFER_H +#define INCLUDED_C_FASTBYTERINGBUFFER_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + uint8_t* buffer; // 連續的位元組陣列 + c_size_t capacity; // 必為 2 的冪次方 (e.g., 16, 32, 64, 256) + c_size_t mask; // 快取 (capacity - 1),用來進行位元與運算 + c_size_t head; // 讀取指標 + c_size_t tail; // 寫入指標 + c_bool_t is_full; // 狀態區分旗標 +} c_FastByteRingBuffer_t; + +c_err_t c_FastByteRingBuffer_Init(c_FastByteRingBuffer_t* self, c_size_t capacity); +void c_FastByteRingBuffer_Destroy(c_FastByteRingBuffer_t* self); + +c_err_t c_FastByteRingBuffer_WriteByte(c_FastByteRingBuffer_t* self, uint8_t byte); +c_err_t c_FastByteRingBuffer_ReadByte(c_FastByteRingBuffer_t* self, uint8_t* out_byte); + +/* + * Writes a bulk buffer span into the fast ring buffer without overwriting existing data. + * @param self: The fast ring buffer instance. + * @param src: Source byte array pointer. + * @param len: Number of bytes to transfer. + * @return The actual number of bytes written into the buffer. + */ +c_size_t c_FastByteRingBuffer_WriteBuffer(c_FastByteRingBuffer_t* self, const uint8_t* src, c_size_t len); + +c_size_t c_FastByteRingBuffer_ReadBuffer(c_FastByteRingBuffer_t* self, uint8_t* dest, c_size_t len); + +c_size_t c_FastByteRingBuffer_GetSize(const c_FastByteRingBuffer_t* self); +c_bool_t c_FastByteRingBuffer_IsEmpty(const c_FastByteRingBuffer_t* self); +c_bool_t c_FastByteRingBuffer_IsFull(const c_FastByteRingBuffer_t* self); + + +/* 新增的進階控制接口 */ +void c_FastByteRingBuffer_WriteByteOverwrite(c_FastByteRingBuffer_t* self, uint8_t byte); +c_size_t c_FastByteRingBuffer_WriteBufferOverwrite(c_FastByteRingBuffer_t* self, const uint8_t* src, c_size_t len); + +c_err_t c_FastByteRingBuffer_PeekByte(const c_FastByteRingBuffer_t* self, uint8_t* out_byte); +c_size_t c_FastByteRingBuffer_PeekBuffer(const c_FastByteRingBuffer_t* self, uint8_t* dest, c_size_t len); + +c_size_t c_FastByteRingBuffer_Discard(c_FastByteRingBuffer_t* self, c_size_t len); +const uint8_t* c_FastByteRingBuffer_GetReadPtr(const c_FastByteRingBuffer_t* self, c_size_t* out_contiguous_len); +uint8_t* c_FastByteRingBuffer_GetWritePtr(const c_FastByteRingBuffer_t* self, c_size_t* out_contiguous_len); + + +c_index_t c_FastByteRingBuffer_IndexOfByte(const c_FastByteRingBuffer_t* self, uint8_t target); +c_index_t c_FastByteRingBuffer_IndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len); +c_index_t c_FastByteRingBuffer_LastIndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len); + +c_size_t c_FastByteRingBuffer_ReadUntilToken(c_FastByteRingBuffer_t* self, const uint8_t* token, c_size_t token_len, uint8_t* dest, c_size_t dest_max_len); +c_err_t c_FastByteRingBuffer_GetAtRelative(const c_FastByteRingBuffer_t* self, c_size_t relative_offset, uint8_t* out_byte); +c_bool_t c_FastByteRingBuffer_Is(const c_FastByteRingBuffer_t* self, c_index_t offset, uint8_t value); +int c_FastByteRingBuffer_Memcmp(const c_FastByteRingBuffer_t* self, c_size_t offset, const uint8_t* buffer, c_size_t len); +c_err_t c_FastByteRingBuffer_Strtoul(const c_FastByteRingBuffer_t* self, c_size_t offset, int base, unsigned long* out_value, c_size_t* out_end_offset); + + +#endif /*INCLUDED_C_FASTBYTERINGBUFFER_H*/ diff --git a/Foundation/c_FastByteRingBuffer.t.c b/Foundation/c_FastByteRingBuffer.t.c new file mode 100644 index 0000000..0444190 --- /dev/null +++ b/Foundation/c_FastByteRingBuffer.t.c @@ -0,0 +1,338 @@ +#include "c_FastByteRingBuffer.h" +#include +#include + +#define RUN_TEST_CASE(test_func) \ + do { \ + printf("[RUNNING] %-40s ... ", #test_func); \ + fflush(stdout); \ + test_func(); \ + printf("[PASSED]\n"); \ + } while (0) + +void test_ring_buffer_behavior(void) { + c_FastByteRingBuffer_t ring; + // Capacity initialization must be a power of two + assert(c_FastByteRingBuffer_Init(&ring, 4) == C_SUCCESS); + + uint8_t input_stream[] = {0x11, 0x22, 0x33}; + + // 1. Verify standard incremental writing actions + assert(c_FastByteRingBuffer_WriteBuffer(&ring, input_stream, 3) == 3); + assert(c_FastByteRingBuffer_GetSize(&ring) == 3); + + // 2. Verify overflow rejection clamping protection + uint8_t flood_stream[] = {0x44, 0x55}; + // Only 1 byte of free space remains out of total capacity 4 + assert(c_FastByteRingBuffer_WriteBuffer(&ring, flood_stream, 2) == 1); + assert(c_FastByteRingBuffer_IsFull(&ring) == C_TRUE); + + // Check data integrity via peek operations + uint8_t output_peek[4] = {0}; + c_FastByteRingBuffer_PeekBuffer(&ring, output_peek, 4); + assert(output_peek[0] == 0x11); + assert(output_peek[3] == 0x44); // 0x44 filled the last slot; 0x55 was cleanly rejected + + c_FastByteRingBuffer_Destroy(&ring); +} + +static void test_overwrite_and_peek_mechanics(void) { + c_FastByteRingBuffer_t ring; + assert(c_FastByteRingBuffer_Init(&ring, 4) == C_SUCCESS); // Capacity = 4 + + // 1. Validate Single Overwrite + c_FastByteRingBuffer_WriteByte(&ring, 0x01); + c_FastByteRingBuffer_WriteByte(&ring, 0x02); + c_FastByteRingBuffer_WriteByte(&ring, 0x03); + c_FastByteRingBuffer_WriteByte(&ring, 0x04); // Buffer now full: [0x01, 0x02, 0x03, 0x04] + assert(c_FastByteRingBuffer_IsFull(&ring) == C_TRUE); + + c_FastByteRingBuffer_WriteByteOverwrite(&ring, 0x05); // 0x01 gets evicted. Head shifts to 0x02 + + uint8_t peek_check = 0; + assert(c_FastByteRingBuffer_PeekByte(&ring, &peek_check) == C_SUCCESS); + assert(peek_check == 0x02); // FIFO rules dictate oldest remaining byte is 0x02 + + // 2. Validate Bulk Overwrite Loops + uint8_t incoming_stream[3] = {0x06, 0x07, 0x08}; + // Ring has 4 bytes capacity. Writing 3 bytes over an already full buffer evicts [0x02, 0x03, 0x04] + assert(c_FastByteRingBuffer_WriteBufferOverwrite(&ring, incoming_stream, 3) == 3); + + uint8_t verification_dump[4] = {0}; + c_size_t read_out = c_FastByteRingBuffer_PeekBuffer(&ring, verification_dump, 4); + assert(read_out == 4); + assert(verification_dump[0] == 0x05); // Preserved from previous transaction + assert(verification_dump[1] == 0x06); + assert(verification_dump[2] == 0x07); + assert(verification_dump[3] == 0x08); + + // 3. Confirm Peek leaves data sequence entirely untouched + assert(c_FastByteRingBuffer_GetSize(&ring) == 4); + + c_FastByteRingBuffer_Destroy(&ring); +} + +static void test_dma_and_discard_mechanics(void) { + c_FastByteRingBuffer_t ring; + assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8 + + // Force initialization of data that loops around the internal ring memory map + uint8_t payload[] = {0xA1, 0xA2, 0xA3, 0xA4, 0xA5}; + c_FastByteRingBuffer_WriteBuffer(&ring, payload, 5); + + // Discard oldest 2 items: drops 0xA1, 0xA2. Cursors shift. + assert(c_FastByteRingBuffer_Discard(&ring, 2) == 2); + assert(c_FastByteRingBuffer_GetSize(&ring) == 3); + + // Write some more to force a wrap-around layout + uint8_t wrap_payload[] = {0xB1, 0xB2, 0xB3, 0xB4}; + c_FastByteRingBuffer_WriteBuffer(&ring, wrap_payload, 4); // Total size now = 7 bytes + + // Validate GetReadPtr isolates Block Segment 1 cleanly + c_size_t read_chunk_len = 0; + const uint8_t* read_ptr = c_FastByteRingBuffer_GetReadPtr(&ring, &read_chunk_len); + + assert(read_ptr != NULL); + // Head was shifted to index 2. 8 - 2 = 6 available linearly up to memory array edge + assert(read_chunk_len == 6); + assert(read_ptr[0] == 0xA3); // First remaining item + + // Clear those processed items via direct execution tracking + c_FastByteRingBuffer_Discard(&ring, read_chunk_len); + + // Call secondary pass to capture remaining wrapped bytes + read_ptr = c_FastByteRingBuffer_GetReadPtr(&ring, &read_chunk_len); + assert(read_chunk_len == 1); + assert(read_ptr[0] == 0xB4); // Wrapped character check + + c_FastByteRingBuffer_Destroy(&ring); +} + +static void test_ring_buffer_index_searching(void) { + c_FastByteRingBuffer_t ring; + assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8 + + // Force a data write footprint that wraps around the buffer margins + uint8_t standard_fill[] = {0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44}; + c_FastByteRingBuffer_WriteBuffer(&ring, standard_fill, 8); + + // Discard 4 elements to position head cursor at absolute index 4 + c_FastByteRingBuffer_Discard(&ring, 4); + + // Append sequence to cross edge wrap boundaries cleanly + uint8_t wrap_fill[] = {0x55, 0x66, 0x77}; + c_FastByteRingBuffer_WriteBuffer(&ring, wrap_fill, 3); + // Dynamic ring content layout from head: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77] + + // 1. Single byte search lookup match + assert(c_FastByteRingBuffer_IndexOfByte(&ring, 0x33) == 2); // Relative offset index 2 from head + assert(c_FastByteRingBuffer_IndexOfByte(&ring, 0x99) == C_ERR_NOT_FOUND); + + // 2. Pattern buffer sequence scan (testing across structural wrap-around edge boundaries) + uint8_t search_pattern[] = {0x44, 0x55, 0x66}; + c_index_t match_offset = c_FastByteRingBuffer_IndexOfBuffer(&ring, search_pattern, 3); + + assert(match_offset == 3); // 0x44 sits exactly at relative offset position 3 + + // Application Workflow Integration: Cleanly discard up to token match prefix point + c_FastByteRingBuffer_Discard(&ring, (c_size_t)match_offset); + uint8_t current_head_byte = 0; + c_FastByteRingBuffer_PeekByte(&ring, ¤t_head_byte); + assert(current_head_byte == 0x44); // The buffer head has been successfully synchronized to the token location + + c_FastByteRingBuffer_Destroy(&ring); +} + +static void test_reverse_search_and_token_stream(void) { + c_FastByteRingBuffer_t ring; + assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8 + + uint8_t raw_payload[] = {0xAA, 0x11, 0x22, 0xBB, 0x11, 0x22, 0xCC, 0xDD}; + c_FastByteRingBuffer_WriteBuffer(&ring, raw_payload, 8); + + uint8_t pattern[] = {0x11, 0x22}; + + // 1. Verify Reverse Pattern Detection Matches Latest Occurrence + assert(c_FastByteRingBuffer_IndexOfBuffer(&ring, pattern, 2) == 1); // First pair starts at offset 1 + assert(c_FastByteRingBuffer_LastIndexOfBuffer(&ring, pattern, 2) == 4); // Latest pair starts at offset 4 + + // 2. Clear out buffer to run frame serialization test + c_FastByteRingBuffer_Discard(&ring, 8); + + uint8_t stream_data[] = {'P', 'a', 'c', 'k', 'e', 't', '\r', '\n'}; + c_FastByteRingBuffer_WriteBuffer(&ring, stream_data, 8); + + uint8_t frame_terminator[] = {'\r', '\n'}; + uint8_t output_staging[16] = {0}; + + // Fail Case: Pass a staging array that is structurally too cramped to hold the payload safely + assert(c_FastByteRingBuffer_ReadUntilToken(&ring, frame_terminator, 2, output_staging, 5) == 0); + assert(c_FastByteRingBuffer_GetSize(&ring) == 8); // Data remains locked safely inside the ring + + // Success Case: Pass a valid container size to execute frame extraction + c_size_t read_bytes = c_FastByteRingBuffer_ReadUntilToken(&ring, frame_terminator, 2, output_staging, 16); + assert(read_bytes == 8); + assert(memcmp(output_staging, "Packet\r\n", 8) == 0); + assert(c_FastByteRingBuffer_IsEmpty(&ring) == C_TRUE); // Frame has been cleanly consumed from the queue + + c_FastByteRingBuffer_Destroy(&ring); +} + +static void test_relative_random_access(void) { + c_FastByteRingBuffer_t ring; + assert(c_FastByteRingBuffer_Init(&ring, 4) == C_SUCCESS); // Capacity = 4 + + uint8_t seed_data[] = {0x10, 0x20, 0x30}; + c_FastByteRingBuffer_WriteBuffer(&ring, seed_data, 3); + + // Shift the head pointer downstream via a single byte consumption + uint8_t discard_sink = 0; + c_FastByteRingBuffer_ReadByte(&ring, &discard_sink); // 0x10 dropped. Head points to 0x20 + + // Append items to cross physical wrapping thresholds cleanly + c_FastByteRingBuffer_WriteByte(&ring, 0x40); + c_FastByteRingBuffer_WriteByte(&ring, 0x50); // Buffer contents: [0x20, 0x30, 0x40, 0x50] + + uint8_t extracted_byte = 0; + + // 1. Verify standard random read coordinates relative to the head position + assert(c_FastByteRingBuffer_GetAtRelative(&ring, 0, &extracted_byte) == C_SUCCESS); + assert(extracted_byte == 0x20); // Relative 0 points directly to the current head + + assert(c_FastByteRingBuffer_GetAtRelative(&ring, 2, &extracted_byte) == C_SUCCESS); + assert(extracted_byte == 0x40); // Wrapped index check + + assert(c_FastByteRingBuffer_GetAtRelative(&ring, 3, &extracted_byte) == C_SUCCESS); + assert(extracted_byte == 0x50); // Newest unread byte check + + // 2. Validate bounds-checking flags + assert(c_FastByteRingBuffer_GetAtRelative(&ring, 4, &extracted_byte) == C_ERR_OUT_OF_BOUNDS); + assert(c_FastByteRingBuffer_GetAtRelative(&ring, 99, &extracted_byte) == C_ERR_OUT_OF_BOUNDS); + assert(c_FastByteRingBuffer_GetAtRelative(NULL, 0, &extracted_byte) == C_ERR_INVALID_PARAM); + + c_FastByteRingBuffer_Destroy(&ring); +} + +static void test_conditional_is_validator(void) { + c_FastByteRingBuffer_t ring; + assert(c_FastByteRingBuffer_Init(&ring, 4) == C_SUCCESS); + + uint8_t input_stream[] = {0xAA, 0xBB, 0xCC}; + c_FastByteRingBuffer_WriteBuffer(&ring, input_stream, 3); + + // 1. Validate clean true/false matches relative to the head + assert(c_FastByteRingBuffer_Is(&ring, 0, 0xAA) == C_TRUE); // Oldest byte at head matches + assert(c_FastByteRingBuffer_Is(&ring, 1, 0xBB) == C_TRUE); // Next element matches + assert(c_FastByteRingBuffer_Is(&ring, 1, 0x99) == C_FALSE); // Mismatch returns false + + // Consume 1 byte to advance the head pointer and test wrapping boundaries + uint8_t sink = 0; + c_FastByteRingBuffer_ReadByte(&ring, &sink); // Head now points to 0xBB + c_FastByteRingBuffer_WriteByte(&ring, 0xDD); // Layout: [..., 0xBB, 0xCC, 0xDD] + + // 2. Re-verify offsets after structural pointer wrap shifts + assert(c_FastByteRingBuffer_Is(&ring, 0, 0xBB) == C_TRUE); // Head index position 0 is now 0xBB + assert(c_FastByteRingBuffer_Is(&ring, 2, 0xDD) == C_TRUE); // Wrapped index element match + + // 3. Confirm error/bounds conditions return false instead of blowing up memory layout boundaries + assert(c_FastByteRingBuffer_Is(&ring, 3, 0x00) == C_FALSE); // Out of bounds index + assert(c_FastByteRingBuffer_Is(&ring, -5, 0xBB) == C_FALSE); // Negative index handling protection + assert(c_FastByteRingBuffer_Is(NULL, 0, 0xBB) == C_FALSE); // NULL safety check + + c_FastByteRingBuffer_Destroy(&ring); +} + +static void test_ring_buffer_memcmp(void) { + c_FastByteRingBuffer_t ring; + assert(c_FastByteRingBuffer_Init(&ring, 6) == C_ERR_INVALID_PARAM); // Capacity = 6 + + assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 6 + uint8_t payload[] = {0x00, 0x11, 0x22, 0x33}; + c_FastByteRingBuffer_WriteBuffer(&ring, payload, 4); + + // Consume 2 bytes to step the head pointer forward to absolute index 2 + uint8_t sink = 0; + c_FastByteRingBuffer_ReadByte(&ring, &sink); + c_FastByteRingBuffer_ReadByte(&ring, &sink); // Buffer active layout from head: [0x22, 0x33] + + // Append data to trigger an explicit physical wrap-around edge split layout + uint8_t wrap_payload[] = {0x44, 0x55, 0x66}; + c_FastByteRingBuffer_WriteBuffer(&ring, wrap_payload, 3); + // Buffer dynamic content path tracking from head: [0x22, 0x33, 0x44, 0x55, 0x66] + // Physical layout behind indices inside array: [0x55, 0x66, 0x22, 0x33, 0x44, ...] + + // 1. Validate contiguous segment match comparisons + uint8_t check_a[] = {0x22, 0x33}; + assert(c_FastByteRingBuffer_Memcmp(&ring, 0, check_a, 2) == 0); // Perfect contiguous match + + // 2. Validate multi-segment wrap-around comparison mechanics + uint8_t check_b[] = {0x33, 0x44, 0x55, 0x66}; + assert(c_FastByteRingBuffer_Memcmp(&ring, 1, check_b, 4) == 0); // Perfect split wrap-around match + + // 3. Mismatch checks + uint8_t check_mismatch[] = {0x33, 0x44, 0x99, 0x66}; + assert(c_FastByteRingBuffer_Memcmp(&ring, 1, check_mismatch, 4) != 0); // Identifies internal divergence + + // 4. Bounds and parameter checks + assert(c_FastByteRingBuffer_Memcmp(&ring, 0, check_b, 100) == C_ERR_OUT_OF_BOUNDS); // Request width overflows content + assert(c_FastByteRingBuffer_Memcmp(&ring, 99, check_b, 1) == C_ERR_OUT_OF_BOUNDS); // Start pointer invalid + assert(c_FastByteRingBuffer_Memcmp(NULL, 0, check_b, 1) == C_ERR_INVALID_PARAM); + + c_FastByteRingBuffer_Destroy(&ring); +} +static void test_ring_buffer_strtoul(void) { + c_FastByteRingBuffer_t ring; + assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8 + + // 1. Standard Linear Base-10 Parsing + uint8_t input_a[] = {'1', '2', '3', '4', ' ', 'A', 'B', 'C'}; + c_FastByteRingBuffer_WriteBuffer(&ring, input_a, 8); + + unsigned long parsed_val = 0; + c_size_t end_offset = 0; + + assert(c_FastByteRingBuffer_Strtoul(&ring, 0, 10, &parsed_val, &end_offset) == C_SUCCESS); + assert(parsed_val == 1234); + assert(end_offset == 4); // Points exactly to the trailing space character offset + + // Reset buffer tracking lines + c_FastByteRingBuffer_Discard(&ring, 8); + + // 2. Fragmented Wrap Hex Parsing + // Pre-fill 5 elements to push cursor indices near wrapping layout boundaries + uint8_t pre_fill[] = {0, 0, 0, 0, 0}; + c_FastByteRingBuffer_WriteBuffer(&ring, pre_fill, 5); + c_FastByteRingBuffer_Discard(&ring, 5); // Head pointer sits at physical index 5 + + // Write Hex parameter payload string ("0x2F") across memory boundaries + uint8_t input_hex[] = {'0', 'x', '2', 'F'}; + c_FastByteRingBuffer_WriteBuffer(&ring, input_hex, 4); + + assert(c_FastByteRingBuffer_Strtoul(&ring, 0, 16, &parsed_val, &end_offset) == C_SUCCESS); + assert(parsed_val == 47); // 0x2F translates to decimal 47 + assert(end_offset == 4); + + c_FastByteRingBuffer_Destroy(&ring); +} + + +int main() { + printf("==================================================\n"); + printf(" Starting c_FastByteRingBuffer Unit Testing Suite\n"); + printf("==================================================\n\n"); + + RUN_TEST_CASE(test_ring_buffer_behavior); + RUN_TEST_CASE(test_overwrite_and_peek_mechanics); + RUN_TEST_CASE(test_dma_and_discard_mechanics); + RUN_TEST_CASE(test_ring_buffer_index_searching); + RUN_TEST_CASE(test_reverse_search_and_token_stream); + RUN_TEST_CASE(test_relative_random_access); + RUN_TEST_CASE(test_conditional_is_validator); + RUN_TEST_CASE(test_ring_buffer_memcmp); + RUN_TEST_CASE(test_ring_buffer_strtoul); + + printf("\n==================================================\n"); + printf(" Success! Byte RingBuffer tests passed!\n"); + printf("==================================================\n"); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_File.c b/Foundation/c_File.c new file mode 100644 index 0000000..110290b --- /dev/null +++ b/Foundation/c_File.c @@ -0,0 +1,279 @@ +#include +#ifdef _WIN32 + #include + #include // 提供 _get_osfhandle + #include + #define C_ACCESS(path) _access(path, 0) + #define C_MAKE_DIR(path) _mkdir(path) // Windows 下创建目录 +#else + #include // 提供 fsync + #include + #include + #define C_ACCESS(path) access(path, F_OK) + #define C_MAKE_DIR(path) mkdir(path, 0755) +#endif + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +#if defined(C_PLATFORM_IS_64BIT) + +// 跨平台 64 位 fseek 封装 +C_STATIC_FORCE_INLINE +int fn_fseek(FILE* fp, long long offset, int whence) { +#ifdef _WIN32 + return _fseeki64(fp, (__int64)offset, whence); +#else + // 在定义了 _FILE_OFFSET_BITS=64 的情况下,fseeko 接收 64 位的 off_t + return fseeko(fp, (off_t)offset, whence); +#endif +} + +// 跨平台 64 位 ftell 封装 +C_STATIC_FORCE_INLINE +long long fn_tell(FILE* fp) { +#ifdef _WIN32 + return (long long)_ftelli64(fp); +#else + return (long long)ftello(fp); +#endif +} + +#elif defined(C_PLATFORM_IS_32BIT) + +C_STATIC_FORCE_INLINE +int fn_fseek(FILE* fp, long long offset, int whence) { + return fseek(fp, (long)offset, whence); +} + +C_STATIC_FORCE_INLINE +long long fn_tell(FILE* fp) { + return (long long)ftell(fp); +} + +#endif + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_File_Open(c_File_t* file, const char* fileName, const char* mode) { + if (!file || !fileName || !mode) { + return C_ERR_FAIL; + } + + file->fp = fopen(fileName, mode); + if (!file->fp) { + return C_ERR_FAIL; + } + + return C_ERR_OK; +} + +void c_File_Close(c_File_t* file) { + if (file && file->fp) { + fclose(file->fp); + file->fp = NULL; + } +} + +c_err_t c_File_Read(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *read_size) { + if (!file || !file->fp || !buffer || buffer_size == 0) { + if (read_size) *read_size = 0; + return C_ERR_FAIL; + } + + // 从文件流中读取 1 字节 * buffer_size + size_t bytes_read = fread(buffer, 1, (size_t)buffer_size, file->fp); + + if (read_size) { + *read_size = (c_size_t)bytes_read; + } + + // 如果读取数量小于请求数量,需检查是出错还是到达文件末尾 + if (bytes_read < (size_t)buffer_size) { + if (ferror(file->fp)) { + return C_ERR_FAIL; // 发生读取错误 + } + } + + return C_ERR_OK; +} + +c_err_t c_File_Readline(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *read_size) { + if (!file || !file->fp || !buffer || buffer_size == 0) { + if (read_size) *read_size = 0; + return C_ERR_FAIL; + } + + // fgets 会自动在末尾加上 '\0',并且最多读取 buffer_size - 1 个字符 + char* result = fgets((char*)buffer, (int)buffer_size, file->fp); + + if (!result) { + if (read_size) *read_size = 0; + return C_ERR_FAIL; // 读取失败或到达文件末尾 + } + + if (read_size) { + *read_size = (c_size_t)strlen((char*)buffer); + } + + return C_ERR_OK; +} + +c_err_t c_File_Write(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *write_size) { + if (!file || !file->fp || !buffer || buffer_size == 0) { + if (write_size) *write_size = 0; + return C_ERR_FAIL; + } + + size_t bytes_written = fwrite(buffer, 1, (size_t)buffer_size, file->fp); + + if (write_size) { + *write_size = (c_size_t)bytes_written; + } + + // 如果实际写入字节数不等于预期,说明写入失败(如磁盘满) + if (bytes_written < (size_t)buffer_size) { + return C_ERR_FAIL; + } + + return C_ERR_OK; +} + +c_err_t c_File_Seek(c_File_t* file, long long position) { + if (!file || !file->fp) { + return C_ERR_PARAM; + } + const int result = fseek(file->fp, (long)position, SEEK_SET); + return (result == 0) ? C_ERR_OK : C_ERR_FAIL; +} + +long long c_File_Size(c_File_t* file) { + if (!file || !file->fp) { + return 0; + } + + // 1. 保存当前文件指针的位置 + long long current_pos = fn_tell(file->fp); + if (current_pos == -1LL) return 0; + + // 2. 将文件指针移到末尾 + if (fn_fseek(file->fp, 0, SEEK_END) != C_ERR_OK) return 0; + + // 3. 获取末尾位置即为文件大小 + long long size = fn_tell(file->fp); + + // 4. 恢复原先的文件指针位置 + fn_fseek(file->fp, current_pos, SEEK_SET); + + return (long long)((size < 0) ? 0 : size); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_File_Flush(c_File_t* file) { + if (!file || !file->fp) { + return C_ERR_FAIL; + } + + // Step 1: 先刷新 C 库自带的软件层缓冲区(将数据推入 OS 内核队列) + if (fflush(file->fp) != 0) { + return C_ERR_FAIL; + } + + // Step 2: 获取底层文件描述符,强行驱动硬件刷盘 +#ifdef _WIN32 + // 从 C 语言的 FILE* 提取 Windows 系统的文件句柄 HANDLE + int fd = _fileno(file->fp); + if (fd < 0) return C_ERR_FAIL; + + HANDLE hFile = (HANDLE)_get_osfhandle(fd); + if (hFile == INVALID_HANDLE_VALUE) return C_ERR_FAIL; + + // 强行驱动机械硬盘/SSD硬件将内核缓存写入物理介质 + if (!FlushFileBuffers(hFile)) { + return C_ERR_FAIL; + } +#else + // Linux / macOS 平台直接提取文件描述符 + int fd = fileno(file->fp); + if (fd < 0) return C_ERR_FAIL; + + // 触发 POSIX 的 fsync 系统调用,阻塞直到硬件写入完毕 + if (fsync(fd) != 0) { + return C_ERR_FAIL; + } +#endif + + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_bool_t c_File_IsExist(const char* fileName) { + if (!fileName) { + return C_FALSE; + } + + // 参数 0 代表 F_OK,即只检查文件是否存在 + // C_ACCESS 返回 0 表示文件存在且可访问,返回 -1 表示不存在或无权限 + if (C_ACCESS(fileName) == F_OK) { + return C_TRUE; + } + + return C_FALSE; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_File_MkDirs(const char* path) { + if (path == NULL || strlen(path) == 0) { + return C_ERR_PARAM; + } + + char temp_path[512]; + snprintf(temp_path, sizeof(temp_path), "%s", path); + size_t len = strlen(temp_path); + + // 统一将 Windows 的 '\\' 转换为 '/' 方便逐级切分 + for (int i = 0; i < len; i++) { + if (temp_path[i] == '\\') { + temp_path[i] = '/'; + } + } + + // 确保路径以 '/' 结尾,方便循环逻辑处理 + if (temp_path[len - 1] != '/') { + if (len + 1 < sizeof(temp_path)) { + temp_path[len] = '/'; + temp_path[len + 1] = '\0'; + len++; + } else { + return C_ERR_FAIL; // 路径超长 + } + } + + // 逐级探测并创建目录 + for (int i = 0; i < len; i++) { + // 每当遇到路径分隔符 '/' 时,尝试截断并创建当前层级目录 + if (temp_path[i] == '/' && i > 0) { + temp_path[i] = '\0'; // 临时截断字符串 + + // 检查当前级目录是否存在,不存在则创建 + if (C_ACCESS(temp_path) != 0) { + if (C_MAKE_DIR(temp_path) != 0) { + return C_ERR_FAIL; // 创建失败 + } + } + + temp_path[i] = '/'; // 恢复分隔符 + } + } + + return C_SUCCESS; +} \ No newline at end of file diff --git a/Foundation/c_File.h b/Foundation/c_File.h new file mode 100644 index 0000000..2c80404 --- /dev/null +++ b/Foundation/c_File.h @@ -0,0 +1,58 @@ +#ifndef INCLUDED_C_FILE_H +#define INCLUDED_C_FILE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_STDIO_H +#define INCLUDED_STDIO_H +#include +#endif /*INCLUDED_STDIO_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +typedef struct { + FILE* fp; +}c_File_t; + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_File_Open(c_File_t* file, const char* fileName, const char* mode); + +void c_File_Close(c_File_t* file); + +c_err_t c_File_Read(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *read_size); + +c_err_t c_File_Readline(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *read_size); + +c_err_t c_File_Write(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *write_size); + +long long c_File_Size(c_File_t* file); + +c_err_t c_File_Flush(c_File_t* file); + +c_err_t c_File_Seek(c_File_t* file, long long position); + +c_err_t c_File_MkDirs(const char* path); + +/** + * @brief 检查指定路径的文件或目录是否存在 + * @param fileName 文件的绝对路径或相对路径 + * @return 存在返回 C_TRUE,不存在或无权限返回 C_FALSE + */ +c_bool_t c_File_IsExist(const char* fileName); + +#endif /*INCLUDED_C_FILE_H*/ diff --git a/Foundation/c_FileIter.c b/Foundation/c_FileIter.c new file mode 100644 index 0000000..447e415 --- /dev/null +++ b/Foundation/c_FileIter.c @@ -0,0 +1,220 @@ +#include +#include +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +#ifdef _WIN32 + #include + typedef struct { + HANDLE hFind; + WIN32_FIND_DATAA findData; + } WinIterCtx; +#else + +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif + +#include +#include +#include +#include + +typedef struct { + DIR* dir; +} PosixIterCtx; + +#endif + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// 内部辅助函数:获取下一个有效条目(过滤 "." 和 "..") +static void c_FileIter_InternalFetch(c_FileIter_t* iter) { +#ifdef _WIN32 + WinIterCtx* ctx = (WinIterCtx*)iter->internal_ctx; + while (FindNextFileA(ctx->hFind, &ctx->findData)) { + if (strcmp(ctx->findData.cFileName, ".") == 0 || strcmp(ctx->findData.cFileName, "..") == 0) { + continue; // 过滤 + } + // 填充缓存 + strncpy(iter->current.name, ctx->findData.cFileName, sizeof(iter->current.name) - 1); + iter->current.name[sizeof(iter->current.name) - 1] = '\0'; + + if (ctx->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + iter->current.type = C_FILE_TYPE_DIR; + iter->current.size = 0; + } else { + iter->current.type = C_FILE_TYPE_REGULAR; + iter->current.size = ((unsigned long long)ctx->findData.nFileSizeHigh << 32) | ctx->findData.nFileSizeLow; + } + iter->has_next = C_TRUE; + return; + } +#else + PosixIterCtx* ctx = (PosixIterCtx*)iter->internal_ctx; + struct dirent* entry; + while ((entry = readdir(ctx->dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; // 过滤 + } + // 填充名字 + strncpy(iter->current.name, entry->d_name, sizeof(iter->current.name) - 1); + iter->current.name[sizeof(iter->current.name) - 1] = '\0'; + + // 组装完整路径获取大文件大小及类型 + char full_path[512]; + snprintf(full_path, sizeof(full_path), "%s/%s", iter->base_path, entry->d_name); + struct stat stat_buf; + + if (stat(full_path, &stat_buf) == 0) { + iter->current.size = (unsigned long long)stat_buf.st_size; + if (S_ISDIR(stat_buf.st_mode)) { + iter->current.type = C_FILE_TYPE_DIR; + } else if (S_ISREG(stat_buf.st_mode)) { + iter->current.type = C_FILE_TYPE_REGULAR; + } else { + iter->current.type = C_FILE_TYPE_UNKNOWN; + } + } else { + iter->current.type = C_FILE_TYPE_UNKNOWN; + iter->current.size = 0; + } + iter->has_next = C_TRUE; + return; + } +#endif + + // 没有更多文件了,触发自动资源清理 + iter->has_next = C_FALSE; + if (iter->internal_ctx) { +#ifdef _WIN32 + FindClose(((WinIterCtx*)iter->internal_ctx)->hFind); +#else + closedir(((PosixIterCtx*)iter->internal_ctx)->dir); +#endif + C_FREE(iter->internal_ctx); + iter->internal_ctx = NULL; + } +} + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_FileIter_Init(c_FileIter_t* iter, const char* dirpath) { + if (!iter || !dirpath) return C_ERR_FAIL; + + iter->internal_ctx = NULL; + iter->has_next = C_FALSE; + strncpy(iter->base_path, dirpath, sizeof(iter->base_path) - 1); + iter->base_path[sizeof(iter->base_path) - 1] = '\0'; + +#ifdef _WIN32 + WinIterCtx* ctx = (WinIterCtx*)C_ALLOC(sizeof(WinIterCtx)); + if (!ctx) return C_ERR_FAIL; + + char search_path[512]; + snprintf(search_path, sizeof(search_path), "%s\\*", dirpath); + + ctx->hFind = FindFirstFile(search_path, &ctx->findData); + if (ctx->hFind == INVALID_HANDLE_VALUE) { + C_FREE(ctx); + return C_ERR_FAIL; + } + iter->internal_ctx = ctx; + + // 检查第一个读取到的是否是 "." 或 "..",如果是,则继续查找有效文件 + if (strcmp(ctx->findData.cFileName, ".") == 0 || strcmp(ctx->findData.cFileName, "..") == 0) { + c_FileIter_InternalFetch(iter); + } else { + strncpy(iter->current.name, ctx->findData.cFileName, sizeof(iter->current.name) - 1); + iter->current.name[sizeof(iter->current.name) - 1] = '\0'; + if (ctx->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + iter->current.type = C_FILE_TYPE_DIR; + iter->current.size = 0; + } else { + iter->current.type = C_FILE_TYPE_REGULAR; + iter->current.size = ((unsigned long long)ctx->findData.nFileSizeHigh << 32) | ctx->findData.nFileSizeLow; + } + iter->has_next = C_TRUE; + } +#else + PosixIterCtx* ctx = (PosixIterCtx*)C_ALLOC(sizeof(PosixIterCtx)); + if (!ctx) return C_ERR_FAIL; + + ctx->dir = opendir(dirpath); + if (!ctx->dir) { + C_FREE(ctx); + return C_ERR_FAIL; + } + iter->internal_ctx = ctx; + + // 触发预读第一项 + c_FileIter_InternalFetch(iter); +#endif + + return C_ERR_OK; +} + +c_bool_t c_FileIter_HasNext(c_FileIter_t* iter) { + return (iter != NULL) ? iter->has_next : C_FALSE; +} + +void c_FileIter_Next(c_FileIter_t* iter) { + if (iter && iter->has_next) { + c_FileIter_InternalFetch(iter); + } +} + +const c_FileEntry_t* c_FileIter_Get(c_FileIter_t* iter) { + if (iter && iter->has_next) { + return &(iter->current); + } + return NULL; +} + +c_err_t c_FileIter_Remove(c_FileIter_t* iter) { + if (!iter || !iter->has_next) return C_ERR_FAIL; + + char full_path[512]; +#ifdef _WIN32 + snprintf(full_path, sizeof(full_path), "%s\\%s", iter->base_path, iter->current.name); +#else + snprintf(full_path, sizeof(full_path), "%s/%s", iter->base_path, iter->current.name); +#endif + + int res = -1; + if (iter->current.type == C_FILE_TYPE_DIR) { +#ifdef _WIN32 + res = RemoveDirectory(full_path) ? 0 : -1; +#else + res = rmdir(full_path); +#endif + } else { +#ifdef _WIN32 + res = DeleteFile(full_path) ? 0 : -1; +#else + res = unlink(full_path); +#endif + } + + // 执行完物理删除后,提前释放当前迭代器的内部流句柄,防止调用者后续发生未知异常 + iter->has_next = C_FALSE; + if (iter->internal_ctx) { +#ifdef _WIN32 + FindClose(((WinIterCtx*)iter->internal_ctx)->hFind); +#else + closedir(((PosixIterCtx*)iter->internal_ctx)->dir); +#endif + C_FREE(iter->internal_ctx); + iter->internal_ctx = NULL; + } + + return (res == 0) ? C_ERR_OK : C_ERR_FAIL; +} + diff --git a/Foundation/c_FileIter.h b/Foundation/c_FileIter.h new file mode 100644 index 0000000..fde3659 --- /dev/null +++ b/Foundation/c_FileIter.h @@ -0,0 +1,63 @@ +#ifndef INCLUDED_C_FILEITER_H +#define INCLUDED_C_FILEITER_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// 文件类型枚举 +typedef enum { + C_FILE_TYPE_REGULAR, // 普通文件 + C_FILE_TYPE_DIR, // 目录 + C_FILE_TYPE_UNKNOWN // 未知 +} c_FileType_t; + +// 迭代器当前指向的文件/目录条目信息 +typedef struct { + char name[256]; // 文件/文件夹名称 + c_FileType_t type; // 类型 + unsigned long long size; // 文件大小(适配大文件) +} c_FileEntry_t; + +// 迭代器结构体定义(隐藏平台差异) +typedef struct { + void* internal_ctx; // 内部上下文指针,用于隔离平台的目录句柄状态 + c_FileEntry_t current; // 当前读取到的条目缓存 + int has_next; // 是否还有下一个有效条目的状态标识 + char base_path[256]; // 记录初始目录路径(Remove 函数需要拼接完整路径) +} c_FileIter_t; + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* 目录迭代器 API */ + +/** + * @brief 初始化目录迭代器,并预读第一个有效条目 + */ +c_err_t c_FileIter_Init(c_FileIter_t* iter, const char* dirpath); + +/** + * @brief 判断是否还有下一个条目 + */ +c_bool_t c_FileIter_HasNext(c_FileIter_t* iter); + +/** + * @brief 步进到下一个有效的条目,并自动过滤 "." 和 ".." + */ +void c_FileIter_Next(c_FileIter_t* iter); + +/** + * @brief 获取当前条目的只读指针 + */ +const c_FileEntry_t* c_FileIter_Get(c_FileIter_t* iter); + +/** + * @brief 删除当前迭代器指向的文件或空目录,并自动安全释放迭代器资源 + */ +c_err_t c_FileIter_Remove(c_FileIter_t* iter); + + +#endif /*INCLUDED_C_FILEITER_H*/ diff --git a/Foundation/c_FileIter.t.c b/Foundation/c_FileIter.t.c new file mode 100644 index 0000000..4f170f4 --- /dev/null +++ b/Foundation/c_FileIter.t.c @@ -0,0 +1,33 @@ +#include "c_FileIter.h" +#include +#include + + +int main() { + c_FileIter_t iter; + + // 初始化迭代器,目标为当前目录 "." + if (c_FileIter_Init(&iter, ".") == C_ERR_OK) { + printf("开始遍历目录文件:\n"); + + // 使用经典的迭代器标准循环结构 + while (c_FileIter_HasNext(&iter)) { + const c_FileEntry_t* entry = c_FileIter_Get(&iter); + if (entry) { + if (entry->type == C_FILE_TYPE_DIR) { + printf("[DIR] %s\n", entry->name); + } else { + printf("[FILE] %-25s 大小: %llu 字节\n", entry->name, entry->size); + } + } + + // 推进到下一个文件 + c_FileIter_Next(&iter); + } + printf("遍历结束,资源已自动安全回收。\n"); + } else { + printf("无法初始化目录迭代器!\n"); + } + + return 0; +} diff --git a/Foundation/c_Fmt.c b/Foundation/c_Fmt.c new file mode 100644 index 0000000..7dbcbce --- /dev/null +++ b/Foundation/c_Fmt.c @@ -0,0 +1,393 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define T c_Fmt_t + +struct buf { + char *buf; + char *bp; + int size; +}; + +#define pad(n,c) do { int nn = (n); \ + while (nn-- > 0) \ + put((c), cl); } while (0) + +static void cvt_s(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + char *str = va_arg(box->ap, char *); + assert(str); + c_Fmt_puts(str, (int)strlen(str), put, cl, flags, + width, precision); +} + +static void cvt_d(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + int val = va_arg(box->ap, int); + unsigned m; + char buf[43]; + char *p = buf + sizeof buf; + if (val == INT_MIN) + m = INT_MAX + 1U; + else if (val < 0) + m = -val; + else + m = val; + do + *--p = m%10 + '0'; + while ((m /= 10) > 0); + if (val < 0) + *--p = '-'; + c_Fmt_putd(p, (buf + sizeof buf) - p, put, cl, flags, + width, precision); +} + +static void cvt_u(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + unsigned m = va_arg(box->ap, unsigned); + char buf[43]; + char *p = buf + sizeof buf; + do + *--p = m%10 + '0'; + while ((m /= 10) > 0); + c_Fmt_putd(p, (buf + sizeof buf) - p, put, cl, flags, + width, precision); +} + +static void cvt_o(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + unsigned m = va_arg(box->ap, unsigned); + char buf[43]; + char *p = buf + sizeof buf; + do + *--p = (m&0x7) + '0'; + while ((m>>= 3) != 0); + c_Fmt_putd(p, (buf + sizeof buf) - p, put, cl, flags, + width, precision); +} + +static void cvt_x(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + unsigned m = va_arg(box->ap, unsigned); + char buf[43]; + char *p = buf + sizeof buf; + do + *--p = "0123456789abcdef"[m&0xf]; + while ((m>>= 4) != 0); + c_Fmt_putd(p, (buf + sizeof buf) - p, put, cl, flags, + width, precision); +} + +static void cvt_p(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + c_uintptr_t m = (c_uintptr_t)va_arg(box->ap, void*); + char buf[43]; + char *p = buf + sizeof buf; + precision = INT_MIN; + do { + *--p = "0123456789abcdef"[m&0xf]; + }while ((m>>= 4) != 0); + c_Fmt_putd(p, (buf + sizeof buf) - p, put, cl, flags, + width, precision); +} + +static void cvt_c(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + if (width == INT_MIN) + width = 0; + if (width < 0) { + flags['-'] = 1; + width = -width; + } + if (!flags['-']) + pad(width - 1, ' '); + put((unsigned char)va_arg(box->ap, int), cl); + if ( flags['-']) + pad(width - 1, ' '); +} + +static void cvt_f(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + char buf[DBL_MAX_10_EXP+1+1+99+1]; + if (precision < 0) + precision = 6; + if (code == 'g' && precision == 0) + precision = 1; + { + static char fmt[] = "%.dd?"; + assert(precision <= 99); + fmt[4] = code; + fmt[3] = precision%10 + '0'; + fmt[2] = (precision/10)%10 + '0'; + sprintf(buf, fmt, va_arg(box->ap, double)); + } + c_Fmt_putd(buf, strlen(buf), put, cl, flags, + width, precision); +} + +static T cvt[256] = { + /* 0- 7 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 8- 15 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 16- 23 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 24- 31 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 32- 39 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 40- 47 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 48- 55 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 56- 63 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 64- 71 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 72- 79 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 80- 87 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 88- 95 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 96-103 */ 0, 0, 0, cvt_c, cvt_d, cvt_f, cvt_f, cvt_f, + /* 104-111 */ 0, 0, 0, 0, 0, 0, 0, cvt_o, + /* 112-119 */ cvt_p, 0, 0, cvt_s, 0, cvt_u, 0, 0, + /* 120-127 */ cvt_x, 0, 0, 0, 0, 0, 0, 0 +}; + +static char *c_Fmt_flags = "-+ 0"; + +static int outc(int c, void *cl) { + FILE *f = cl; + return putc(c, f); +} + +static int insert(int c, void *cl) { + struct buf *p = cl; + assert (p->bp < (p->buf + p->size)); + *p->bp++ = c; + return c; +} + +static int append(int c, void *cl) { + struct buf *p = cl; + if (p->bp >= p->buf + p->size) { + C_RESIZE(p->buf, 2*p->size); + p->bp = p->buf + p->size; + p->size *= 2; + } + *p->bp++ = c; + return c; +} + +void c_Fmt_puts(const char *str, int len, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + assert(str); + assert(len >= 0); + assert(flags); + if (width == INT_MIN) + width = 0; + if (width < 0) { + flags['-'] = 1; + width = -width; + } + if (precision >= 0) + flags['0'] = 0; + if (precision >= 0 && precision < len) + len = precision; + if (!flags['-']) + pad(width - len, ' '); + { + int i; + for (i = 0; i < len; i++) + put((unsigned char)*str++, cl); + } + if ( flags['-']) + pad(width - len, ' '); +} + +void c_Fmt_fmt(int put(int c, void *), void *cl, + const char *fmt, ...) { + va_list_box box; + va_start(box.ap, fmt); + c_Fmt_vfmt(put, cl, fmt, &box); + va_end(box.ap); +} + +void c_Fmt_print(const char *fmt, ...) { + va_list_box box; + va_start(box.ap, fmt); + c_Fmt_vfmt(outc, stdout, fmt, &box); + va_end(box.ap); +} +void c_Fmt_fprint(FILE *stream, const char *fmt, ...) { + va_list_box box; + va_start(box.ap, fmt); + c_Fmt_vfmt(outc, stream, fmt, &box); + va_end(box.ap); +} + +int c_Fmt_sfmt(char *buf, int size, const char *fmt, ...) { + int len; + va_list_box box; + va_start(box.ap, fmt); + len = c_Fmt_vsfmt(buf, size, fmt, &box); + va_end(box.ap); + return len; +} +int c_Fmt_vsfmt(char *buf, int size, const char *fmt, + va_list_box *box) { + struct buf cl; + assert(buf); + assert(size > 0); + assert(fmt); + cl.buf = cl.bp = buf; + cl.size = size; + c_Fmt_vfmt(insert, &cl, fmt, box); + insert(0, &cl); + return cl.bp - cl.buf - 1; +} + +char *c_Fmt_string(const char *fmt, ...) { + char *str; + va_list_box box; + assert(fmt); + va_start(box.ap, fmt); + str =c_Fmt_vstring(fmt, &box); + va_end(box.ap); + return str; +} +char *c_Fmt_vstring(const char *fmt, va_list_box *box) { + struct buf cl; + assert(fmt); + cl.size = 256; + cl.buf = cl.bp = C_ALLOC(cl.size); + c_Fmt_vfmt(append, &cl, fmt, box); + append(0, &cl); + return C_RESIZE(cl.buf, cl.bp - cl.buf); +} + +void c_Fmt_vfmt(int put(int c, void *cl), void *cl, + const char *fmt, va_list_box *box) { + assert(put); + assert(fmt); + while (*fmt) + if (*fmt != '%' || *++fmt == '%') + put((unsigned char)*fmt++, cl); + else + { + unsigned char c, flags[256]; + int width = INT_MIN, precision = INT_MIN; + memset(flags, '\0', sizeof flags); + if (c_Fmt_flags) { + unsigned char c = *fmt; + for ( ; c && strchr(c_Fmt_flags, c); c = *++fmt) { + assert(flags[c] < 255); + flags[c]++; + } + } + if (*fmt == '*' || isdigit(*fmt)) { + int n; + if (*fmt == '*') { + n = va_arg(box->ap, int); + assert(n != INT_MIN); + fmt++; + } else + for (n = 0; isdigit(*fmt); fmt++) { + int d = *fmt - '0'; + assert(n <= (INT_MAX - d)/10); + n = 10*n + d; + } + width = n; + } + if (*fmt == '.' && (*++fmt == '*' || isdigit(*fmt))) { + int n; + if (*fmt == '*') { + n = va_arg(box->ap, int); + assert(n != INT_MIN); + fmt++; + } else + for (n = 0; isdigit(*fmt); fmt++) { + int d = *fmt - '0'; + assert(n <= (INT_MAX - d)/10); + n = 10*n + d; + } + precision = n; + } + c = *fmt++; + assert(cvt[c]); + (*cvt[c])(c, box, put, cl, flags, width, precision); + } +} + +T c_Fmt_register(int code, T newcvt) { + T old; + assert(0 < code + && code < (int)(sizeof (cvt)/sizeof (cvt[0]))); + old = cvt[code]; + cvt[code] = newcvt; + return old; +} + +void c_Fmt_putd(const char *str, int len, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + int sign; + assert(str); + assert(len >= 0); + assert(flags); + if (width == INT_MIN) + width = 0; + if (width < 0) { + flags['-'] = 1; + width = -width; + } + if (precision >= 0) + flags['0'] = 0; + if (len > 0 && (*str == '-' || *str == '+')) { + sign = *str++; + len--; + } else if (flags['+']) + sign = '+'; + else if (flags[' ']) + sign = ' '; + else + sign = 0; + { int n; + if (precision < 0) + precision = 1; + if (len < precision) + n = precision; + else if (precision == 0 && len == 1 && str[0] == '0') + n = 0; + else + n = len; + if (sign) + n++; + if (flags['-']) { + if (sign) + put(sign, cl); + } else if (flags['0']) { + if (sign) + put(sign, cl); + pad(width - n, '0'); + } else { + pad(width - n, ' '); + if (sign) + put(sign, cl); + } + pad(precision - len, '0'); + { + int i; + for (i = 0; i < len; i++) + put((unsigned char)*str++, cl); + } + if (flags['-']) + pad(width - n, ' '); } +} diff --git a/Foundation/c_Fmt.h b/Foundation/c_Fmt.h new file mode 100644 index 0000000..a5f9f16 --- /dev/null +++ b/Foundation/c_Fmt.h @@ -0,0 +1,70 @@ +#ifndef INCLUDED_C_FMT_H +#define INCLUDED_C_FMT_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_STDIO_H +#define INCLUDED_STDIO_H +#include +#endif /*INCLUDED_STDIO_H*/ + +#ifndef INCLUDED_STDARG_H +#define INCLUDED_STDARG_H +#include +#endif /*INCLUDED_STDARG_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct va_list_box { + va_list ap; +} va_list_box; + +#ifdef T +#undef T +#endif + +#define T c_Fmt_t + +typedef void (*T)(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[256], int width, int precision); + +extern void c_Fmt_fmt (int put(int c, void *cl), void *cl, + const char *fmt, ...); + +extern void c_Fmt_vfmt(int put(int c, void *cl), void *cl, + const char *fmt, va_list_box *box); + +extern void c_Fmt_print (const char *fmt, ...); + +extern void c_Fmt_fprint(FILE *stream, + const char *fmt, ...); + +extern int c_Fmt_sfmt (char *buf, int size, + const char *fmt, ...); + +extern int c_Fmt_vsfmt(char *buf, int size, + const char *fmt, va_list_box *box); + +extern char *c_Fmt_string (const char *fmt, ...); + +extern char *c_Fmt_vstring(const char *fmt, va_list_box *box); + +extern T c_Fmt_register(int code, T cvt); + +extern void c_Fmt_putd(const char *str, int len, + int put(int c, void *cl), void *cl, + unsigned char flags[256], int width, int precision); + +extern void c_Fmt_puts(const char *str, int len, + int put(int c, void *cl), void *cl, + unsigned char flags[256], int width, int precision); + +#undef T + +#endif /*INCLUDED_C_FMT_H*/ diff --git a/Foundation/c_KnuthShuffle.c b/Foundation/c_KnuthShuffle.c new file mode 100644 index 0000000..5444f75 --- /dev/null +++ b/Foundation/c_KnuthShuffle.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_KnuthShuffle.h b/Foundation/c_KnuthShuffle.h new file mode 100644 index 0000000..fd74b69 --- /dev/null +++ b/Foundation/c_KnuthShuffle.h @@ -0,0 +1,57 @@ +#ifndef INCLUDED_C_KNUTHSHUFFLE_H +#define INCLUDED_C_KNUTHSHUFFLE_H + +#ifndef INCLUDED_C_MEMORY_H +#include +#endif /*INCLUDED_C_MEMORY_H*/ + + +#ifndef INCLUDED_STDLIB_H +#define INCLUDED_STDLIB_H +#include +#endif /*INCLUDED_STDLIB_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * 通用 Knuth 洗牌演算法 + * @param base 指向待打亂陣列首元素的指標 + * @param num 陣列中元素的個數 + * @param size 每個元素的大小(位元組數) + */ +C_STATIC_FORCE_INLINE +void c_KnuthShuffle(void* base, c_size_t num, c_size_t size) { + if (base == NULL || num < 2 || size == 0) return; + + char* arr = (char*)base; + + // 使用棧快取 buffer 進行記憶體交換,避免堆分配開銷 +#define SHUFFLE_STACK_LIMIT 128 + char stack_buf[SHUFFLE_STACK_LIMIT]; + void* temp = (size <= SHUFFLE_STACK_LIMIT) ? stack_buf : C_ALLOC(size); + if (temp == NULL) return; + + // 從後往前遍歷陣列 + for (c_int_t i = num - 1; i > 0; i--) { + // 生成一個 0 到 i 之間(包含 i)的隨機索引 + c_int_t j = rand() % (i + 1); + + // 交換 arr[i] 和 arr[j] + if (i != j) { + char* a = arr + (i * size); + char* b = arr + (j * size); + memcpy(temp, a, size); + memcpy(a, b, size); + memcpy(b, temp, size); + } + } + + if (size > SHUFFLE_STACK_LIMIT) { + C_FREE(temp); + } +#undef SHUFFLE_STACK_LIMIT +} + +#endif /*INCLUDED_C_KNUTHSHUFFLE_H*/ diff --git a/Foundation/c_KnuthShuffle.t.c b/Foundation/c_KnuthShuffle.t.c new file mode 100644 index 0000000..a93af13 --- /dev/null +++ b/Foundation/c_KnuthShuffle.t.c @@ -0,0 +1,101 @@ +#include "c_KnuthShuffle.h" +#include +#include + +// Advanced Line-Tracing Diagnostic Macro +#define EXPECT_EQ(actual, expected, msg) \ + do { \ + if ((actual) != (expected)) { \ + printf(" [X] Assert Failed: %s (Expected %d, got %d) %s:%d\n", msg, (int)(expected), (int)(actual), __FILE__, __LINE__); \ + return C_FALSE; \ + } \ + } while(0) + +// Complex struct data payload representing simulation cards +typedef struct { + int card_id; + char suit; +} DeckCard; + +// External declaration of your framework's Knuth Shuffle module +extern void c_KnuthShuffle(void* base, c_size_t num, c_size_t size); + +/** + * Unit Test Profile: Validates extreme boundaries, statistical disordering, + * and strict payload preservation without data loss. + */ +c_bool_t test_knuth_shuffle_lifecycle(void) { + // Seed the standard pseudo-random number generator for the shuffle module + srand((unsigned int)time(NULL)); + + // Checkpoint 1: Boundary conditions check (Should return safely without crashing) + int* null_ptr = NULL; + c_KnuthShuffle(null_ptr, 0, sizeof(int)); // NULL pointer escape guard + + int single_array[] = { 99 }; + c_KnuthShuffle(single_array, 1, sizeof(int)); + EXPECT_EQ(single_array[0], 99, "Single element array mutated unexpectedly"); + + // Initialize an ordered deck array of 10 complex structural cards + DeckCard deck[10]; + c_size_t total_cards = sizeof(deck) / sizeof(deck[0]); + char suits[] = {'H', 'D', 'C', 'S'}; + + for (c_size_t i = 0; i < total_cards; i++) { + deck[i].card_id = (int)(i + 1); + deck[i].suit = suits[i % 4]; + } + + // Clone the original array configuration to verify data preservation metrics later + DeckCard deck_snapshot[10]; + memcpy(deck_snapshot, deck, sizeof(deck)); + + printf(" [LOG] Executing c_KnuthShuffle across 10 complex data elements...\n"); + c_KnuthShuffle(deck, total_cards, sizeof(DeckCard)); + + // Checkpoint 2: Verification of statistical mismatch (Disordering check) + c_size_t identical_matches = 0; + for (c_size_t i = 0; i < total_cards; i++) { + if (deck[i].card_id == deck_snapshot[i].card_id) { + identical_matches++; + } + } + + // Mathematically, the probability of a 10-element array remaining completely + // identical or barely modified after a fair shuffle is near zero (~1 in 3.6 million). + printf(" [STAT] Element slots remaining in original positions: %zu/%zu\n", identical_matches, total_cards); + EXPECT_EQ(identical_matches < total_cards, C_TRUE, "Shuffle algorithm failed to re-arrange element configurations"); + + // Checkpoint 3: Strict Data Integrity Verification (De-duplication & Conservation check) + // Ensure that every single original item still exists inside the shuffled deck exactly once. + c_bool_t found_flags[10] = { C_FALSE }; + c_size_t unique_restored_count = 0; + + for (c_size_t i = 0; i < total_cards; i++) { + for (c_size_t j = 0; j < total_cards; j++) { + if (deck[i].card_id == deck_snapshot[j].card_id && deck[i].suit == deck_snapshot[j].suit) { + if (!found_flags[j]) { + found_flags[j] = C_TRUE; + unique_restored_count++; + } + break; + } + } + } + + EXPECT_EQ(unique_restored_count, total_cards, "Shuffle step introduced data corruption, duplicates, or element losses"); + printf(" [PASS] All 10 original complex items conserved perfectly with zero data leakage.\n"); + + return C_TRUE; +} + +int main(void) { + printf("=== Starting Framework Verification: c_KnuthShuffle ===\n"); + + if (test_knuth_shuffle_lifecycle()) { + printf(" [PASS] Knuth Linear-Time Shuffle Validation & Payload Conservation Verified.\n"); + } else { + printf(" [FAIL] Shuffle Optimization Pipeline Errors Intercepted.\n"); + } + return 0; +} \ No newline at end of file diff --git a/Foundation/c_LinkDQueue.c b/Foundation/c_LinkDQueue.c new file mode 100644 index 0000000..b5a5eb1 --- /dev/null +++ b/Foundation/c_LinkDQueue.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include "c_Macros.h" + +C_STATIC_FORCE_INLINE +c_LinkDQueueNode_t* create_node(int obj_size, void* obj) { + int size = sizeof(c_LinkDQueueNode_t) + obj_size; + size = C_ALIGN_UPB(size, C_ALIGN_SIZE); + + c_LinkDQueueNode_t* new_node = (c_LinkDQueueNode_t*)C_ALLOC(size); + if (!new_node) return NULL; + new_node->data = new_node + 1; + + memcpy(new_node->data, obj, obj_size); + new_node->prev = NULL; + new_node->next = NULL; + return new_node; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_LinkDQueue_Init(c_LinkDQueue_t* self, int obj_size) { + if (!self || obj_size <= 0) return C_ERR_PARAM; + self->head = NULL; + self->tail = NULL; + self->obj_size = obj_size; + self->size = 0; + return C_ERR_OK; +} + +void c_LinkDQueue_Destroy(c_LinkDQueue_t* self) { + if (!self) return; + c_LinkDQueueNode_t* current = self->head; + while (current != NULL) { + c_LinkDQueueNode_t* next = current->next; + C_FREE(current); + current = next; + } + self->head = NULL; + self->tail = NULL; + self->size = 0; +} + +// 前端推入 O(1) +c_err_t c_LinkDQueue_PushHead(c_LinkDQueue_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + c_LinkDQueueNode_t* new_node = create_node(self->obj_size, obj); + if (!new_node) return C_ERR_NOMEM; + + if (self->size == 0) { + self->head = new_node; + self->tail = new_node; + } else { + new_node->next = self->head; + self->head->prev = new_node; + self->head = new_node; + } + self->size++; + return C_ERR_OK; +} + +// 尾端推入 O(1) +c_err_t c_LinkDQueue_PushTail(c_LinkDQueue_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + c_LinkDQueueNode_t* new_node = create_node(self->obj_size, obj); + if (!new_node) return C_ERR_NOMEM; + + if (self->size == 0) { + self->head = new_node; + self->tail = new_node; + } else { + new_node->prev = self->tail; + self->tail->next = new_node; + self->tail = new_node; + } + self->size++; + return C_ERR_OK; +} + +// 前端彈出 O(1) +c_err_t c_LinkDQueue_PopHead(c_LinkDQueue_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; + + c_LinkDQueueNode_t* to_delete = self->head; + memcpy(obj, to_delete->data, self->obj_size); + + self->head = to_delete->next; + if (self->head == NULL) { + self->tail = NULL; // 變空佇列 + } else { + self->head->prev = NULL; + } + + C_FREE(to_delete); + self->size--; + return C_ERR_OK; +} + +// 尾端彈出 O(1) +c_err_t c_LinkDQueue_PopTail(c_LinkDQueue_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + if (self->size == 0) return C_ERR_EMPTY; + + c_LinkDQueueNode_t* to_delete = self->tail; + memcpy(obj, to_delete->data, self->obj_size); + + self->tail = to_delete->prev; + if (self->tail == NULL) { + self->head = NULL; // 變空佇列 + } else { + self->tail->next = NULL; + } + + C_FREE(to_delete); + self->size--; + return C_ERR_OK; +} + +void* c_LinkDQueue_PeekHead(c_LinkDQueue_t* self) { + if (!self || self->size == 0) return NULL; + return self->head->data; +} + +void* c_LinkDQueue_PeekTail(c_LinkDQueue_t* self) { + if (!self || self->size == 0) return NULL; + return self->tail->data; +} + +// 迭代器安全刪除:利用雙向指標在 O(1) 修正前後驅關係並安全維護 head/tail +void c_LinkDQueueIter_Remove(c_LinkDQueueIter_t* self) { + if (!self || !self->dqueue || !self->node || !*(self->node)) return; + + c_LinkDQueueNode_t* to_delete = *(self->node); + c_LinkDQueue_t* dq = self->dqueue; + + // 1. 修正前驅節點的 next 或主結構的 head + *(self->node) = to_delete->next; + + // 2. 修正後繼節點的 prev 或主結構的 tail + if (to_delete->next != NULL) { + to_delete->next->prev = to_delete->prev; + } else { + dq->tail = to_delete->prev; // 刪除的是尾端,更新 tail + } + + // 3. 釋放資源與修正大小 + C_FREE(to_delete); + dq->size--; +} + diff --git a/Foundation/c_LinkDQueue.h b/Foundation/c_LinkDQueue.h new file mode 100644 index 0000000..787361e --- /dev/null +++ b/Foundation/c_LinkDQueue.h @@ -0,0 +1,77 @@ +#ifndef INCLUDED_C_LINKDQUEUE_H +#define INCLUDED_C_LINKDQUEUE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_LinkDQueueNode_t { + void* data; + struct c_LinkDQueueNode_t* prev; + struct c_LinkDQueueNode_t* next; +} c_LinkDQueueNode_t; + +// 雙端佇列控制結構 +typedef struct { + c_LinkDQueueNode_t* head; + c_LinkDQueueNode_t* tail; + int obj_size; + c_size_t size; +} c_LinkDQueue_t; + +// 雙端佇列迭代器(維持指標的指標設計,預設從 head 往 tail 走訪) +typedef struct { + c_LinkDQueue_t* dqueue; + c_LinkDQueueNode_t** node; +} c_LinkDQueueIter_t; + +// 核心函數宣告 +c_err_t c_LinkDQueue_Init(c_LinkDQueue_t* self, int obj_size); +void c_LinkDQueue_Destroy(c_LinkDQueue_t* self); + +c_err_t c_LinkDQueue_PushHead(c_LinkDQueue_t* self, void* obj); +c_err_t c_LinkDQueue_PushTail(c_LinkDQueue_t* self, void* obj); + +c_err_t c_LinkDQueue_PopHead(c_LinkDQueue_t* self, void* obj); +c_err_t c_LinkDQueue_PopTail(c_LinkDQueue_t* self, void* obj); + +void* c_LinkDQueue_PeekHead(c_LinkDQueue_t* self); +void* c_LinkDQueue_PeekTail(c_LinkDQueue_t* self); + +void c_LinkDQueueIter_Remove(c_LinkDQueueIter_t* self); + +// 迭代器內聯函數 +C_STATIC_FORCE_INLINE +void c_LinkDQueueIter_Init(c_LinkDQueueIter_t* self, c_LinkDQueue_t* dqueue) { + if (!self || !dqueue) return; + self->dqueue = dqueue; + self->node = &dqueue->head; +} + +C_STATIC_FORCE_INLINE +c_bool_t c_LinkDQueueIter_HasNext(c_LinkDQueueIter_t* self) { + if (!self) return C_FALSE; + return (self->node != NULL) && (*(self->node) != NULL); +} + +C_STATIC_FORCE_INLINE +void* c_LinkDQueueIter_Next(c_LinkDQueueIter_t* self) { + if (!self || !self->node || !*(self->node)) return NULL; + void* data = (*(self->node))->data; + self->node = &(*(self->node))->next; + return data; +} + +C_STATIC_FORCE_INLINE +void* c_LinkDQueueIter_Get(c_LinkDQueueIter_t* self) { + if (!self || !self->node || !*(self->node)) return NULL; + return (*(self->node))->data; +} + +void c_LinkDQueueIter_Remove(c_LinkDQueueIter_t* self); + +#endif /*INCLUDED_C_LINKDQUEUE_H*/ diff --git a/Foundation/c_LinkDQueue.t.c b/Foundation/c_LinkDQueue.t.c new file mode 100644 index 0000000..bd584d4 --- /dev/null +++ b/Foundation/c_LinkDQueue.t.c @@ -0,0 +1,110 @@ +#include "c_LinkDQueue.h" +#include +#include +#include + +typedef struct { + char label[16]; + int val; +} Element_t; + +void test_log(const char* test_name) { + printf("[PASS] %s\n", test_name); +} + +int main() { + printf("==================================================\n"); + printf(" 開始執行 c_LinkDQueue 雙端佇列完整測試用例\n"); + printf("==================================================\n\n"); + + c_LinkDQueue_t dq; + c_LinkDQueue_Init(&dq, sizeof(Element_t)); + + Element_t e1 = {"Node_1", 10}; + Element_t e2 = {"Node_2", 20}; + Element_t e3 = {"Node_3", 30}; + + // ========================================== + // 1. 測試雙端推入 (PushHead & PushTail) + // ========================================== + // 先把 e2 推入尾端 (佇列: [e2]) + c_LinkDQueue_PushTail(&dq, &e2); + // 把 e1 推入前端 (佇列: [e1, e2]) + c_LinkDQueue_PushHead(&dq, &e1); + // 把 e3 推入尾端 (佇列: [e1, e2, e3]) + c_LinkDQueue_PushTail(&dq, &e3); + + assert(dq.size == 3); + assert(((Element_t*)c_LinkDQueue_PeekHead(&dq))->val == 10); + assert(((Element_t*)c_LinkDQueue_PeekTail(&dq))->val == 30); + test_log("1. 雙端交錯推入與邊界 Peek 驗證成功"); + + // ========================================== + // 2. 測試迭代器走訪 + // ========================================== + c_LinkDQueueIter_t iter; + c_LinkDQueueIter_Init(&iter, &dq); + int idx = 0; + while (c_LinkDQueueIter_HasNext(&iter)) { + Element_t* el = (Element_t*)c_LinkDQueueIter_Next(&iter); + if (idx == 0) assert(el->val == 10); + if (idx == 1) assert(el->val == 20); + if (idx == 2) assert(el->val == 30); + idx++; + } + test_log("2. 迭代器正向走訪順序驗證成功"); + + // ========================================== + // 3. 測試迭代器刪除中間節點 (Remove e2) + // ========================================== + c_LinkDQueueIter_Init(&iter, &dq); + while (c_LinkDQueueIter_HasNext(&iter)) { + Element_t* el = (Element_t*)c_LinkDQueueIter_Get(&iter); + if (el->val == 20) { + c_LinkDQueueIter_Remove(&iter); // 刪除 Node_2 + } else { + c_LinkDQueueIter_Next(&iter); + } + } + assert(dq.size == 2); + // 驗證現在內容只剩下 [e1, e3] + assert(((Element_t*)c_LinkDQueue_PeekHead(&dq))->val == 10); + assert(((Element_t*)c_LinkDQueue_PeekTail(&dq))->val == 30); + test_log("3. 迭代器 O(1) 刪除中間節點暨雙向鏈結維護成功"); + + // ========================================== + // 4. 測試雙端彈出 (PopHead & PopTail) + // ========================================== + Element_t buf; + + // 從前端彈出(應拿到 e1) + c_err_t err = c_LinkDQueue_PopHead(&dq, &buf); + assert(err == C_ERR_OK); + assert(buf.val == 10); + assert(dq.size == 1); + + // 從尾端彈出(應拿到 e3) + err = c_LinkDQueue_PopTail(&dq, &buf); + assert(err == C_ERR_OK); + assert(buf.val == 30); + assert(dq.size == 0); + + // 驗證完全清空後指標皆重置為 NULL + assert(dq.head == NULL); + assert(dq.tail == NULL); + test_log("4. 雙端彈出與空佇列指標歸零驗證成功"); + + // ========================================== + // 5. 空佇列防呆測試 + // ========================================== + assert(c_LinkDQueue_PopHead(&dq, &buf) == C_ERR_EMPTY); + assert(c_LinkDQueue_PopTail(&dq, &buf) == C_ERR_EMPTY); + assert(c_LinkDQueue_PeekHead(&dq) == NULL); + test_log("5. 空雙端佇列越界防呆成功"); + + c_LinkDQueue_Destroy(&dq); + printf("\n==================================================\n"); + printf(" 恭喜!c_LinkDQueue 所有核心特性單元測試完美通過!\n"); + printf("==================================================\n"); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_LinkList.c b/Foundation/c_LinkList.c new file mode 100644 index 0000000..f350f29 --- /dev/null +++ b/Foundation/c_LinkList.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include "c_Macros.h" + +c_err_t c_LinkList_Init(c_LinkList_t* self, c_size_t obj_size) { + if (!self || obj_size == 0) return C_ERR_PARAM; + self->head = NULL; + self->obj_size = (int)obj_size; + self->size = 0; + return C_ERR_OK; +} + +void c_LinkList_Destroy(c_LinkList_t* self) { + if (!self) return; + + c_LinkListNode_t* current = self->head; + while (current != NULL) { + c_LinkListNode_t* next = current->next; + // 由於 Add 時 data 與 node 是分開或合開,此處依據一體化配置釋放 + C_FREE(current); + current = next; + } + self->head = NULL; + self->size = 0; +} + +c_err_t c_LinkList_Add(c_LinkList_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + + int size = (int)sizeof(c_LinkListNode_t) + self->obj_size; + size = C_ALIGN_UPB(size, C_ALIGN_SIZE); + c_LinkListNode_t* new_node = (c_LinkListNode_t*)C_ALLOC(size); + if (!new_node) return C_ERR_NOMEM; + new_node->data = new_node+1; + + // 值複製 (Value Copy) + memcpy(new_node->data, obj, self->obj_size); + + // 頭插法鏈結 + new_node->next = self->head; + self->head = new_node; + self->size++; + + return C_ERR_OK; +} + +c_err_t c_LinkList_Remove(c_LinkList_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + + c_LinkListNode_t** curr = &self->head; + while (*curr != NULL) { + // 使用 memcmp 進行二進位值比對 + if (memcmp((*curr)->data, obj, self->obj_size) == 0) { + c_LinkListNode_t* to_delete = *curr; + *curr = to_delete->next; + + C_FREE(to_delete); + self->size--; + return C_ERR_OK; + } + curr = &(*curr)->next; + } + + return C_ERR_FAIL; +} + +void c_LinkListIter_Remove(c_LinkListIter_t* self) { + if (!self || !self->list || !self->node || !*(self->node)) return; + + c_LinkListNode_t* to_delete = *(self->node); + + // 讓上一個節點的 next 指向下一個節點,移除鏈結 + *(self->node) = to_delete->next; + + // 釋放該節點的值與結構 + C_FREE(to_delete); + + self->list->size--; +} + + diff --git a/Foundation/c_LinkList.h b/Foundation/c_LinkList.h new file mode 100644 index 0000000..fbc106b --- /dev/null +++ b/Foundation/c_LinkList.h @@ -0,0 +1,70 @@ +#ifndef INCLUDED_C_LINKLIST_H +#define INCLUDED_C_LINKLIST_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_LinkListNode_t { + void* data; + struct c_LinkListNode_t* next; +}c_LinkListNode_t; + +typedef struct { + c_LinkListNode_t* head; + int obj_size; + c_size_t size; +}c_LinkList_t; + +typedef struct { + c_LinkList_t* list; + c_LinkListNode_t** node; +}c_LinkListIter_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_LinkList_Init(c_LinkList_t* self, c_size_t obj_size); + +void c_LinkList_Destroy(c_LinkList_t* self); + +c_err_t c_LinkList_Add(c_LinkList_t* self, void* obj); + +c_err_t c_LinkList_Remove(c_LinkList_t* self, void* obj); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +void c_LinkListIter_Init(c_LinkListIter_t* self, c_LinkList_t* list) { + if (!self || !list) return; + self->list = list; + self->node = &list->head; +} + +C_STATIC_FORCE_INLINE +c_bool_t c_LinkListIter_HasNext(c_LinkListIter_t* self) { + if (!self) return C_FALSE; + return (self->node != NULL) && (*(self->node) != NULL); +} + +C_STATIC_FORCE_INLINE +void* c_LinkListIter_Next(c_LinkListIter_t* self) { + if (!self || !self->node || !*(self->node)) return NULL; + void* data = (*(self->node))->data; + self->node = &(*(self->node))->next; + return data; +} + +C_STATIC_FORCE_INLINE +void* c_LinkListIter_Get(c_LinkListIter_t* self) { + if (!self || !self->node || !*(self->node)) return NULL; + return (*(self->node))->data; +} + +void c_LinkListIter_Remove(c_LinkListIter_t* self); + +#endif /*INCLUDED_C_LINKLIST_H*/ diff --git a/Foundation/c_LinkList.t.c b/Foundation/c_LinkList.t.c new file mode 100644 index 0000000..ce6ebb6 --- /dev/null +++ b/Foundation/c_LinkList.t.c @@ -0,0 +1,68 @@ +#include "c_LinkList.h" +#include +#include +#include + +typedef struct { + char name[4]; + int score; +} Student_t; + +int main() { + printf("開始執行 c_LinkList 測試用例...\n"); + + c_LinkList_t list; + c_LinkList_Init(&list, sizeof(Student_t)); + + Student_t s1 = {"AAA", 80}; + Student_t s2 = {"BBB", 90}; + Student_t s3 = {"CCC", 95}; + + // 1. 測試新增 (頭插法預期順序: CCC -> BBB -> AAA) + c_LinkList_Add(&list, &s1); + c_LinkList_Add(&list, &s2); + c_LinkList_Add(&list, &s3); + assert(list.size == 3); + + // 2. 測試迭代器走訪 + c_LinkListIter_t iter; + c_LinkListIter_Init(&iter, &list); + + printf("當前串列內容:\n"); + while (c_LinkListIter_HasNext(&iter)) { + Student_t* s = (Student_t*)c_LinkListIter_Next(&iter); + printf(" 學生: %s, 分數: %d\n", s->name, s->score); + } + + // 3. 測試一般刪除 (Remove - 透過二進位資料比對刪除 "BBB") + Student_t target_remove = {"BBB", 90}; + c_err_t err = c_LinkList_Remove(&list, &target_remove); + assert(err == C_ERR_OK); + assert(list.size == 2); + + // 4. 測試迭代器走訪並刪除 (Iter_Remove) + c_LinkListIter_Init(&iter, &list); + while (c_LinkListIter_HasNext(&iter)) { + Student_t* s = (Student_t*)c_LinkListIter_Get(&iter); + if (s->score == 80) { // 找到分數 80 的學生 (AAA) + c_LinkListIter_Remove(&iter); + printf("[Log] 迭代器成功刪除了分數為 80 的學生\n"); + } else { + c_LinkListIter_Next(&iter); + } + } + assert(list.size == 1); + + // 5. 驗證最後留下來的是否為 CCC + c_LinkListIter_Init(&iter, &list); + Student_t* final_s = (Student_t*)c_LinkListIter_Get(&iter); + assert(strcmp(final_s->name, "CCC") == 0); + + // 銷毀資源 + c_LinkList_Destroy(&list); + assert(list.head == NULL); + assert(list.size == 0); + + printf("所有測試成功通過!\n"); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_LinkQueue.c b/Foundation/c_LinkQueue.c new file mode 100644 index 0000000..12c130a --- /dev/null +++ b/Foundation/c_LinkQueue.c @@ -0,0 +1,129 @@ +#include +#include +#include "c_Alignment.h" +#include "c_Macros.h" + +c_err_t c_LinkQueue_Init(c_LinkQueue_t* self, int obj_size) { + if (!self || obj_size == 0) return C_ERR_PARAM; + self->head = NULL; + self->tail = NULL; + self->obj_size = (int)obj_size; + self->size = 0; + return C_ERR_OK; +} + +void c_LinkQueue_Destroy(c_LinkQueue_t* self) { + if (!self) return; + + c_LinkQueueNode_t* current = self->head; + while (current != NULL) { + c_LinkQueueNode_t* next = current->next; + C_FREE(current); + current = next; + } + self->head = NULL; + self->tail = NULL; + self->size = 0; +} + +c_err_t c_LinkQueue_Push(c_LinkQueue_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + + // 1. 配置新節點與資料空間 + int size = (int)sizeof(c_LinkQueueNode_t) + self->obj_size; + size = C_ALIGN_UPB(size, C_ALIGN_SIZE); + c_LinkQueueNode_t* new_node = (c_LinkQueueNode_t*)C_ALLOC(size); + if (!new_node) return C_ERR_NOMEM; + new_node->data = new_node+1; + + // 深複製資料內容 + memcpy(new_node->data, obj, self->obj_size); + new_node->next = NULL; + + // 2. 將新節點追加到串列尾端 + if (self->tail == NULL) { + // 佇列原本為空 + self->head = new_node; + self->tail = new_node; + } else { + // 佇列不為空,讓原本尾端的 next 指向新節點,並更新 tail + self->tail->next = new_node; + self->tail = new_node; + } + + self->size++; + return C_ERR_OK; +} + +c_err_t c_LinkQueue_Pop(c_LinkQueue_t* self, void* obj) { + if (!self ) return C_ERR_PARAM; + if (self->size == 0 || !self->head) return C_ERR_EMPTY; + + c_LinkQueueNode_t* to_delete = self->head; + + // 1. 複製資料到使用者緩衝區 + if (obj) { + memcpy(obj, to_delete->data, self->obj_size); + } + + // 2. 將 head 移至下一個節點 + self->head = to_delete->next; + + if (self->head == NULL) { + self->tail = NULL; + } + + // 3. 釋放斷開的節點資源 + C_FREE(to_delete); + + self->size--; + return C_ERR_OK; +} + +c_err_t c_LinkQueue_Peek(c_LinkQueue_t* self, void* obj) { + if (!self ) return C_ERR_PARAM; + if (self->size == 0 || !self->head) return C_ERR_EMPTY; + + // 複製最前端的資料內容 + if (obj) { + memcpy(obj, self->head->data, self->obj_size); + } + return C_ERR_OK; +} + +void c_LinkQueueIter_Remove(c_LinkQueueIter_t* self) { + if (!self || !self->queue || !self->node || !*(self->node)) return; + + c_LinkQueueNode_t* to_delete = *(self->node); + c_LinkQueue_t* q = self->queue; + + // 檢查目前要刪除的是否為尾端節點 + const c_bool_t is_tail = (to_delete == q->tail); + + // 讓上一個節點的 next(或 head)直接指向下一個節點,將 to_delete 從串列中斷開 + *(self->node) = to_delete->next; + + // 如果刪除的是尾端節點,必須更新 tail 指標 + if (is_tail) { + if (q->head == NULL) { + // 情況 A:刪除後佇列完全空了 + q->tail = NULL; + } else { + // 情況 B:刪除的是尾巴,但前方還有元素。 + // 沿著 head 重新走訪一遍,找出現在最尾端的節點(即 next 為 NULL 的節點) + c_LinkQueueNode_t* curr = q->head; + while (curr->next != NULL) { + curr = curr->next; + } + q->tail = curr; // 更新新的尾端 + } + } + + // 釋放記憶體資源 + C_FREE(to_delete); + + // 同步遞減佇列大小 + q->size--; + + // 此時 self->node 已自動留在原本的下一個節點上,呼叫端可直接繼續 Get() 或 Next() +} \ No newline at end of file diff --git a/Foundation/c_LinkQueue.h b/Foundation/c_LinkQueue.h new file mode 100644 index 0000000..e274790 --- /dev/null +++ b/Foundation/c_LinkQueue.h @@ -0,0 +1,70 @@ +#ifndef INCLUDED_C_LINKQUEUE_H +#define INCLUDED_C_LINKQUEUE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_LinkQueueNode_t { + void* data; + struct c_LinkQueueNode_t * next; +}c_LinkQueueNode_t; + +typedef struct { + c_LinkQueueNode_t* head; + c_LinkQueueNode_t* tail; + int obj_size; + c_size_t size; +}c_LinkQueue_t; + +typedef struct { + c_LinkQueue_t* queue; + c_LinkQueueNode_t** node; +}c_LinkQueueIter_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_LinkQueue_Init(c_LinkQueue_t* self, int obj_size); + +void c_LinkQueue_Destroy(c_LinkQueue_t* self); + +c_err_t c_LinkQueue_Push(c_LinkQueue_t* self, void* obj); + +c_err_t c_LinkQueue_Pop(c_LinkQueue_t* self, void* obj); + +c_err_t c_LinkQueue_Peek(c_LinkQueue_t* self, void* obj); + +C_STATIC_FORCE_INLINE +void c_LinkQueueIter_Init(c_LinkQueueIter_t* self, c_LinkQueue_t* queue) { + if (!self || !queue) return; + self->queue = queue; + self->node = &queue->head; +} + +C_STATIC_FORCE_INLINE +c_bool_t c_LinkQueueIter_HasNext(c_LinkQueueIter_t* self) { + if (!self) return C_FALSE; + return (self->node != NULL) && (*(self->node) != NULL); +} + +C_STATIC_FORCE_INLINE +void* c_LinkQueueIter_Next(c_LinkQueueIter_t* self) { + if (!self || !self->node || !*(self->node)) return NULL; + void* data = (*(self->node))->data; + self->node = &(*(self->node))->next; + return data; +} + +C_STATIC_FORCE_INLINE +void* c_LinkQueueIter_Get(c_LinkQueueIter_t* self) { + if (!self || !self->node || !*(self->node)) return NULL; + return (*(self->node))->data; +} + +void c_LinkQueueIter_Remove(c_LinkQueueIter_t* self); + +#endif /*INCLUDED_C_LINKQUEUE_H*/ diff --git a/Foundation/c_LinkQueue.t.c b/Foundation/c_LinkQueue.t.c new file mode 100644 index 0000000..52f95ce --- /dev/null +++ b/Foundation/c_LinkQueue.t.c @@ -0,0 +1,201 @@ +#include "c_LinkQueue.h" +#include +#include +#include + +typedef struct { + char text[16]; + int id; +} Message_t; + +void test_log(const char* test_name) { + printf("[PASS] %s\n", test_name); +} + +typedef struct { + char* data_str; + int id; +} DataPacket_t; + +static void test_iter(void) { + printf("==================================================\n"); + printf(" 開始執行 c_LinkQueueIter_Remove 單元測試\n"); + printf("==================================================\n\n"); + + c_LinkQueue_t queue; + c_LinkQueue_Init(&queue, sizeof(DataPacket_t)); + + DataPacket_t p1 = {"PacketA", 101}; + DataPacket_t p2 = {"PacketB", 102}; + DataPacket_t p3 = {"PacketC", 103}; + + // 推入三筆資料 (FIFO 順序: head -> p1 -> p2 -> p3 <- tail) + c_LinkQueue_Push(&queue, &p1); + c_LinkQueue_Push(&queue, &p2); + c_LinkQueue_Push(&queue, &p3); + assert(queue.size == 3); + assert(((DataPacket_t*)queue.tail->data)->id == 103); // 確任目前尾端是 p3 + + c_LinkQueueIter_t iter; + + // ========================================== + // 測試 1:刪除中間節點 (p2: 102) + // ========================================== + c_LinkQueueIter_Init(&iter, &queue); + while (c_LinkQueueIter_HasNext(&iter)) { + DataPacket_t* pkt = (DataPacket_t*)c_LinkQueueIter_Get(&iter); + if (pkt->id == 102) { + c_LinkQueueIter_Remove(&iter); // 刪除 PacketB + printf("[PASS] 成功刪除中間節點 (102)\n"); + } else { + c_LinkQueueIter_Next(&iter); + } + } + assert(queue.size == 2); + assert(((DataPacket_t*)queue.tail->data)->id == 103); // 尾端應維持 103 + + // ========================================== + // 測試 2:刪除尾端節點 (p3: 103) -> 測試 tail 更新 + // ========================================== + c_LinkQueueIter_Init(&iter, &queue); + while (c_LinkQueueIter_HasNext(&iter)) { + DataPacket_t* pkt = (DataPacket_t*)c_LinkQueueIter_Get(&iter); + if (pkt->id == 103) { + c_LinkQueueIter_Remove(&iter); // 刪除 PacketC (當前的尾端) + printf("[PASS] 成功刪除尾端節點 (103)\n"); + } else { + c_LinkQueueIter_Next(&iter); + } + } + assert(queue.size == 1); + // 關鍵斷言:刪除原本的尾端 103 後,queue->tail 必須自動往前更新為 101 (PacketA) + assert(queue.tail != NULL); + assert(((DataPacket_t*)queue.tail->data)->id == 101); + assert(queue.head == queue.tail); // 只剩一個元素時,head 應等於 tail + + // ========================================== + // 3. 測試 3:刪除最後一個節點 (p1: 101) -> 測試佇列歸零 + // ========================================== + c_LinkQueueIter_Init(&iter, &queue); + assert(c_LinkQueueIter_HasNext(&iter) == C_TRUE); + c_LinkQueueIter_Remove(&iter); // 刪除僅存的 PacketA + printf("[PASS] 成功刪除最後一個節點 (101)\n"); + + assert(queue.size == 0); + assert(queue.head == NULL); + assert(queue.tail == NULL); // 關鍵斷言:完全空了之後 tail 必須回歸 NULL + assert(c_LinkQueueIter_HasNext(&iter) == C_FALSE); + + // 清理資源 + c_LinkQueue_Destroy(&queue); + printf("\n==================================================\n"); + printf(" 恭喜!c_LinkQueueIter_Remove 所有特例驗證全數通過!\n"); + printf("==================================================\n"); +} + +int main() { + printf("==================================================\n"); + printf(" 開始執行 c_LinkQueue 完整版測試用例\n"); + printf("==================================================\n\n"); + + // ========================================== + // 1. 測試初始化 + // ========================================== + c_LinkQueue_t q; + c_err_t err = c_LinkQueue_Init(&q, sizeof(Message_t)); + assert(err == C_ERR_OK); + assert(q.size == 0); + assert(q.head == NULL); + test_log("1. 鏈結佇列初始化成功"); + + // ========================================== + // 2. 測試資料推入 (Push) + // ========================================== + Message_t msg1 = {"MsgA", 101}; + Message_t msg2 = {"MsgB", 102}; + Message_t msg3 = {"MsgC", 103}; + + err = c_LinkQueue_Push(&q, &msg1); assert(err == C_ERR_OK); + err = c_LinkQueue_Push(&q, &msg2); assert(err == C_ERR_OK); + err = q.size == 2; + err = c_LinkQueue_Push(&q, &msg3); assert(err == C_ERR_OK); + assert(q.size == 3); + test_log("2. 三筆資料成功推入佇列 (尾端追加)"); + + // ========================================== + // 3. 測試迭代器走訪 (應符合 Push 的先進順序:A -> B -> C) + // ========================================== + c_LinkQueueIter_t iter; + c_LinkQueueIter_Init(&iter, &q); + + int check_idx = 0; + while (c_LinkQueueIter_HasNext(&iter)) { + Message_t* m = (Message_t*)c_LinkQueueIter_Next(&iter); + if (check_idx == 0) assert(strcmp(m->text, "MsgA") == 0); + if (check_idx == 1) assert(strcmp(m->text, "MsgB") == 0); + if (check_idx == 2) assert(strcmp(m->text, "MsgC") == 0); + check_idx++; + } + assert(check_idx == 3); + test_log("3. 迭代器順序走訪驗證成功 (符合 FIFO 順序)"); + + // ========================================== + // 4. 測試查看最前端元素 (Peek) + // ========================================== + Message_t local_buf; + err = c_LinkQueue_Peek(&q, &local_buf); + assert(err == C_ERR_OK); + assert(strcmp(local_buf.text, "MsgA") == 0); + assert(local_buf.id == 101); + assert(q.size == 3); // 驗證 Peek 不會減少佇列大小 + test_log("4. 隨機查看最前端元素 (Peek) 成功"); + + // ========================================== + // 5. 測試先進先出彈出 (Pop) + // ========================================== + // 第一次 Pop:預期拿到最先進入的 MsgA + err = c_LinkQueue_Pop(&q, &local_buf); + assert(err == C_ERR_OK); + assert(strcmp(local_buf.text, "MsgA") == 0); + assert(q.size == 2); + + // 彈出後再次 Peek,最前端應該變成 MsgB + err = c_LinkQueue_Peek(&q, &local_buf); + assert(err == C_ERR_OK); + assert(strcmp(local_buf.text, "MsgB") == 0); + + // 第二次與第三次 Pop + err = c_LinkQueue_Pop(&q, &local_buf); assert(err == C_ERR_OK); // 彈出 MsgB + err = c_LinkQueue_Pop(&q, &local_buf); assert(err == C_ERR_OK); // 彈出 MsgC + assert(q.size == 0); + + // 空佇列彈出與查看測試,預期回傳越界錯誤 + err = c_LinkQueue_Pop(&q, &local_buf); + assert(err == C_ERR_EMPTY); + err = c_LinkQueue_Peek(&q, &local_buf); + assert(err == C_ERR_EMPTY); + test_log("5. 彈出 (Pop) 邏輯與空佇列防呆驗證成功"); + + // ========================================== + // 6. 參數安全檢查 + // ========================================== + assert(c_LinkQueue_Init(NULL, sizeof(Message_t)) == C_ERR_PARAM); + assert(c_LinkQueue_Push(NULL, &msg1) == C_ERR_PARAM); + // assert(c_LinkQueue_Pop(&q, NULL) == C_ERR_PARAM); + test_log("6. 介面 NULL 指標防呆驗證成功"); + + // ========================================== + // 7. 銷毀佇列 + // ========================================== + c_LinkQueue_Destroy(&q); + assert(q.head == NULL); + assert(q.size == 0); + test_log("7. 佇列銷毀與記憶體釋放成功"); + + printf("\n==================================================\n"); + printf(" 恭喜!所有 c_LinkQueue 測試皆順利通過!\n"); + printf("==================================================\n"); + + test_iter(); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_LinkStack.c b/Foundation/c_LinkStack.c new file mode 100644 index 0000000..c09424e --- /dev/null +++ b/Foundation/c_LinkStack.c @@ -0,0 +1,99 @@ +#include +#include +#include "c_Alignment.h" +#include "c_Macros.h" + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_LinkStack_Init(c_LinkStack_t* self, int obj_size) { + if (!self || obj_size <= 0) return C_ERR_PARAM; + self->head = NULL; + self->obj_size = obj_size; + self->size = 0; + return C_ERR_OK; +} + +// 銷毀堆疊並釋放所有配置的節點與資料記憶體 +void c_LinkStack_Destroy(c_LinkStack_t* self) { + if (!self) return; + + c_LinkStackNode_t* current = self->head; + while (current != NULL) { + c_LinkStackNode_t* next = current->next; + C_FREE(current); + current = next; + } + self->head = NULL; + self->size = 0; +} + +// 推入元素:採用頭插法實作 O(1) 頂端推入,並深複製資料內容 +c_err_t c_LinkStack_Push(c_LinkStack_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + int size = sizeof(c_LinkStackNode_t) + self->obj_size; + size = C_ALIGN_UPB(size, C_ALIGN_SIZE); + + c_LinkStackNode_t* new_node = (c_LinkStackNode_t*)C_ALLOC(size); + if (!new_node) return C_ERR_NOMEM; + new_node->data = new_node+1; + + // 值複製 (Value Copy) + memcpy(new_node->data, obj, self->obj_size); + + // 頭插法連結 + new_node->next = self->head; + self->head = new_node; + + self->size++; + return C_ERR_OK; +} + +// 彈出頂端元素:將 `head` 的資料值複製給使用者,隨後釋放該前端節點 (O(1)) +c_err_t c_LinkStack_Pop(c_LinkStack_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + if (self->size == 0 || !self->head) return C_ERR_EMPTY; + + c_LinkStackNode_t* to_delete = self->head; + + // 將資料複製到呼叫端提供的緩衝區 + memcpy(obj, to_delete->data, self->obj_size); + + // 斷開頂端節點,head 指向下一個 + self->head = to_delete->next; + + // 釋放資源 + C_FREE(to_delete); + + self->size--; + return C_ERR_OK; +} + +// 查看目前最頂端的元素指標 (O(1),不移除節點) +void* c_LinkStack_Peek(c_LinkStack_t* self) { + if (!self || !self->head || self->size == 0) return NULL; + return self->head->data; +} + +void c_LinkStackIter_Remove(c_LinkStackIter_t* self) { + // 防呆檢查:確保迭代器有效、繫結的堆疊存在,且當前指向的節點不為空 + if (!self || !self->stack || !self->node || !*(self->node)) return; + + c_LinkStackNode_t* to_delete = *(self->node); + + // 關鍵指標轉移: + // 將當前結構中維護的指標(可能是前一節點的 next,或是 stack 的 head) + // 修改為指向下一個節點,直接從鏈結串列中斷開該節點 + *(self->node) = to_delete->next; + + // 釋放節點內深複製的資料空間與節點結構本體 + C_FREE(to_delete); + + // 同步遞減堆疊的總大小 + self->stack->size--; + + // 注意:由於 *(self->node) 已被賦值為 to_delete->next, + // self->node 目前已自動指向了原本的下一個節點。 + // 使用者不需要再手動呼叫 Next(),即可直接對新位置進行 Get()、Next() 或再次 Remove()。 +} diff --git a/Foundation/c_LinkStack.h b/Foundation/c_LinkStack.h new file mode 100644 index 0000000..0364d82 --- /dev/null +++ b/Foundation/c_LinkStack.h @@ -0,0 +1,64 @@ +#ifndef INCLUDED_C_LINKSTACK_H +#define INCLUDED_C_LINKSTACK_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +typedef struct c_LinkStackNode_t { + void* data; + struct c_LinkStackNode_t* next; +} c_LinkStackNode_t; + +typedef struct { + c_LinkStackNode_t* head; + int obj_size; + c_size_t size; +} c_LinkStack_t; + +typedef struct { + c_LinkStack_t* stack; + c_LinkStackNode_t** node; +} c_LinkStackIter_t; + +c_err_t c_LinkStack_Init(c_LinkStack_t* self, int obj_size); +void c_LinkStack_Destroy(c_LinkStack_t* self); +c_err_t c_LinkStack_Push(c_LinkStack_t* self, void* obj); +c_err_t c_LinkStack_Pop(c_LinkStack_t* self, void* obj); +void* c_LinkStack_Peek(c_LinkStack_t* self); + +// 迭代器內聯函數實作 +C_STATIC_FORCE_INLINE +void c_LinkStackIter_Init(c_LinkStackIter_t* self, c_LinkStack_t* stack) { + if (!self || !stack) return; + self->stack = stack; + self->node = &stack->head; +} + +C_STATIC_FORCE_INLINE +c_bool_t c_LinkStackIter_HasNext(c_LinkStackIter_t* self) { + if (!self) return C_FALSE; + return (self->node != NULL) && (*(self->node) != NULL); +} + +C_STATIC_FORCE_INLINE +void* c_LinkStackIter_Next(c_LinkStackIter_t* self) { + if (!self || !self->node || !*(self->node)) return NULL; + void* data = (*(self->node))->data; + self->node = &(*(self->node))->next; + return data; +} + +C_STATIC_FORCE_INLINE +void* c_LinkStackIter_Get(c_LinkStackIter_t* self) { + if (!self || !self->node || !*(self->node)) return NULL; + return (*(self->node))->data; +} + +void c_LinkStackIter_Remove(c_LinkStackIter_t* self); + +#endif /*INCLUDED_C_LINKSTACK_H*/ diff --git a/Foundation/c_LinkStack.t.c b/Foundation/c_LinkStack.t.c new file mode 100644 index 0000000..85de8d5 --- /dev/null +++ b/Foundation/c_LinkStack.t.c @@ -0,0 +1,118 @@ +#include "c_LinkStack.h" +#include +#include +#include + +typedef struct { + char name[16]; + int id; +} Frame_t; + +void test_log(const char* test_name) { + printf("[PASS] %s\n", test_name); +} + +int main() { + printf("==================================================\n"); + printf(" 開始執行 c_LinkStack 完整單元測試用例\n"); + printf("==================================================\n\n"); + + // ========================================== + // 1. 初始化測試 + // ========================================== + c_LinkStack_t s; + c_err_t err = c_LinkStack_Init(&s, sizeof(Frame_t)); + assert(err == C_ERR_OK); + assert(s.size == 0); + assert(s.head == NULL); + test_log("1. 鏈結堆疊初始化狀態驗證成功"); + + // ========================================== + // 2. 資料推入測試 (Push) + // ========================================== + Frame_t f1 = {"MainFrame", 100}; + Frame_t f2 = {"RenderFrame", 200}; + Frame_t f3 = {"UpdateFrame", 300}; + + // 依序推入,預期最新推入的會待在鏈結最前端 (head) + err = c_LinkStack_Push(&s, &f1); assert(err == C_ERR_OK); + err = c_LinkStack_Push(&s, &f2); assert(err == C_ERR_OK); + err = c_LinkStack_Push(&s, &f3); assert(err == C_ERR_OK); + assert(s.size == 3); + test_log("2. 三筆資料成功推入堆疊 (頭插法 O(1))"); + + // ========================================== + // 3. 迭代器走訪測試 (應符合後進先出順序:f3 -> f2 -> f1) + // ========================================== + c_LinkStackIter_t iter; + c_LinkStackIter_Init(&iter, &s); + int check_idx = 0; + while (c_LinkStackIter_HasNext(&iter)) { + Frame_t* f = (Frame_t*)c_LinkStackIter_Next(&iter); + if (check_idx == 0) assert(strcmp(f->name, "UpdateFrame") == 0); + if (check_idx == 1) assert(strcmp(f->name, "RenderFrame") == 0); + if (check_idx == 2) assert(strcmp(f->name, "MainFrame") == 0); + check_idx++; + } + assert(check_idx == 3); + test_log("3. 迭代器走訪順序驗證成功 (符合 LIFO 頂端到探底順序)"); + + // ========================================== + // 4. 查看堆疊頂端 (Peek) + // ========================================== + // 目前最頂端應該依然是最後推進去的 UpdateFrame + Frame_t* p_peek = (Frame_t*)c_LinkStack_Peek(&s); + assert(p_peek != NULL); + assert(strcmp(p_peek->name, "UpdateFrame") == 0); + assert(p_peek->id == 300); + assert(s.size == 3); // 驗證 Peek 不會移除元素 + test_log("4. 唯讀查看堆疊頂端元素 (Peek) 成功"); + + // ========================================== + // 5. 先進後出彈出測試 (Pop) + // ========================================== + Frame_t local_buf; + + // 第一次 Pop:預期取出最上方的 UpdateFrame + err = c_LinkStack_Pop(&s, &local_buf); + assert(err == C_ERR_OK); + assert(strcmp(local_buf.name, "UpdateFrame") == 0); + assert(s.size == 2); + + // 再次 Peek,頂端應更新為 RenderFrame + p_peek = (Frame_t*)c_LinkStack_Peek(&s); + assert(strcmp(p_peek->name, "RenderFrame") == 0); + + // 連續彈出剩下的兩個元素 + err = c_LinkStack_Pop(&s, &local_buf); assert(err == C_ERR_OK); // 取出 RenderFrame + err = c_LinkStack_Pop(&s, &local_buf); assert(err == C_ERR_OK); // 取出 MainFrame + assert(s.size == 0); + assert(s.head == NULL); + + // 空堆疊彈出與查看測試,預期回傳越界錯誤 + err = c_LinkStack_Pop(&s, &local_buf); + assert(err == C_ERR_EMPTY); + assert(c_LinkStack_Peek(&s) == NULL); + test_log("5. 後進先出彈出 (Pop) 邏輯與空防呆驗證成功"); + + // ========================================== + // 6. 介面安全指標檢查 + // ========================================== + assert(c_LinkStack_Init(NULL, sizeof(Frame_t)) == C_ERR_PARAM); + assert(c_LinkStack_Push(NULL, &f1) == C_ERR_PARAM); + assert(c_LinkStack_Pop(&s, NULL) == C_ERR_PARAM); + test_log("6. 介面 NULL 指標防呆驗證成功"); + + // ========================================== + // 7. 銷毀堆疊 (Destroy) + // ========================================== + c_LinkStack_Destroy(&s); + assert(s.head == NULL); + assert(s.size == 0); + test_log("7. 堆疊資源銷毀與記憶體釋放成功"); + + printf("\n==================================================\n"); + printf(" 恭喜!c_LinkStack 優化版單元測試全數順利通過!\n"); + printf("==================================================\n"); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_List.c b/Foundation/c_List.c new file mode 100644 index 0000000..2857b2c --- /dev/null +++ b/Foundation/c_List.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_List.h b/Foundation/c_List.h new file mode 100644 index 0000000..f3c99ed --- /dev/null +++ b/Foundation/c_List.h @@ -0,0 +1,73 @@ +#ifndef INCLUDED_C_LIST_H +#define INCLUDED_C_LIST_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_ListNode_t { + struct c_ListNode_t* prev; + struct c_ListNode_t* next; +}c_ListNode_t; + +typedef c_ListNode_t c_List_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define c_List_Prev(n) (n)->prev +#define c_List_Next(n) (n)->next +#define c_List_PrevNext(n) c_List_Next(c_List_Prev(n)) +#define c_List_NextPrev(n) c_List_Prev(c_List_Next(n)) + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +#define c_List_Init(n) do{ \ + c_List_Prev(n) = c_List_Next(n) = (n); \ +}while(0) + +#define c_List_IsEmpty(n) ((c_List_Next(n) == (n)) && (c_List_Prev(n) == (n))) + +#define c_List_InsertBefore(L, N) do { \ + c_List_Next(N) = (L); \ + c_List_Prev(N) = c_List_Prev(L); \ + c_List_PrevNext(N) = (N); \ + c_List_Prev(L) = (N); \ +} while(0) + +#define c_List_InsertAfter(L, N) do { \ + c_List_Prev(N) = (L); \ + c_List_Next(N) = c_List_Next(L); \ + c_List_NextPrev(N) = (N); \ + c_List_Next(L) = (N); \ +} while(0) + +#define c_List_Remove(n) do { \ + c_List_PrevNext(n) = c_List_Next(n); \ + c_List_NextPrev(n) = c_List_Prev(n); \ + c_List_Init(n); \ +} while(0) + +#define c_List_Entry(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +// Standard forward loop: Safe for lookups, UNSAFE for deletions +#define c_List_ForEachEntry(head, pos, type, member) \ + for (pos = c_List_Entry(c_List_Next(head), type, member); \ + &pos->member != (head); \ + pos = c_List_Entry(c_List_Next(&pos->member), type, member)) + +// Safe forward loop: Explicitly safe to call c_List_Remove inside the loop body +#define c_List_ForEachEntrySafe(head, pos, n, type, member) \ + for (pos = c_List_Entry(c_List_Next(head), type, member), \ + n = c_List_Entry(c_List_Next(&pos->member), type, member); \ + &pos->member != (head); \ + pos = n, n = c_List_Entry(c_List_Next(&n->member), type, member)) + +#endif /*INCLUDED_C_LIST_H*/ diff --git a/Foundation/c_List.t.c b/Foundation/c_List.t.c new file mode 100644 index 0000000..2d86c10 --- /dev/null +++ b/Foundation/c_List.t.c @@ -0,0 +1,143 @@ +#include "c_List.h" +#include +#include +#include + +// Custom User Type showcasing intrusive inclusion +typedef struct { + int value; + c_ListNode_t node; // Intrusive node payload linkage +} CustomData_t; + +static void test2(void) { + printf("==================================================\n"); + printf(" Starting Intrusive Safe-Iterator Unit Tests\n"); + printf("==================================================\n\n"); + + c_List_t head; + c_List_Init(&head); + + CustomData_t d1 = {10}; + CustomData_t d2 = {20}; + CustomData_t d3 = {30}; + + // Linking nodes to tail: [Head] <-> [10] <-> [20] <-> [30] + c_List_InsertBefore(&head, &d1.node); + c_List_InsertBefore(&head, &d2.node); + c_List_InsertBefore(&head, &d3.node); + + // ========================================== + // 1. Verify Lookups via ForEachEntry + // ========================================== + CustomData_t* pos; + int index = 0; + int expected[] = {10, 20, 30}; + + printf("Reading nodes using standard ForEachEntry:\n"); + c_List_ForEachEntry(&head, pos, CustomData_t, node) { + printf(" Found Entry value: %d\n", pos->value); + assert(pos->value == expected[index++]); + } + assert(index == 3); + printf("[PASS] Standard lookup loop complete.\n\n"); + + // ========================================== + // 2. Verify Mutations via ForEachEntrySafe + // ========================================== + CustomData_t* tmp; // Cache variable for safe traversal tracking + index = 0; + + printf("Filtering and removing matching entry elements via Safe Loop:\n"); + c_List_ForEachEntrySafe(&head, pos, tmp, CustomData_t, node) { + if (pos->value == 20) { + printf(" Modifying layout: Safely dropping element (%d)\n", pos->value); + c_List_Remove(&pos->node); // Remove middle item mid-flight + } + index++; + } + assert(index == 3); // Iterated through all three bounds + + // ========================================== + // 3. Confirm Final Structure Integrity + // ========================================== + // List must now structurally bridge to bypass item 20: [10] <-> [30] + index = 0; + c_List_ForEachEntry(&head, pos, CustomData_t, node) { + if (index == 0) assert(pos->value == 10); + if (index == 1) assert(pos->value == 30); + index++; + } + assert(index == 2); + printf("[PASS] Structural integrity verified following safe removal mutation.\n"); + + printf("\n==================================================\n"); + printf(" Success! Intrusive layout traversal operates flawlessly!\n"); + printf("==================================================\n"); +} + +int main() { + printf("==================================================\n"); + printf(" Starting Intrusive Circular Doubly Linked List Tests\n"); + printf("==================================================\n\n"); + + // Initialize list head context anchor + c_List_t head; + c_List_Init(&head); + assert(c_List_IsEmpty(&head) == 1); + + CustomData_t d1 = {100}; + CustomData_t d2 = {200}; + CustomData_t d3 = {300}; + + // ========================================== + // 1. Insertion Testing + // ========================================== + // Insert d1 after head -> List: [Head] <-> [100] + c_List_InsertAfter(&head, &d1.node); + assert(c_List_IsEmpty(&head) == 0); + + // Insert d3 before head -> List: [Head] <-> [100] <-> [300] + c_List_InsertBefore(&head, &d3.node); + + // Insert d2 after d1 -> List: [Head] <-> [100] <-> [200] <-> [300] + c_List_InsertAfter(&d1.node, &d2.node); + + printf("[PASS] Sequence insertions complete.\n"); + + // ========================================== + // 2. Linear Traversal Verification + // ========================================== + c_ListNode_t* curr = c_List_Next(&head); + int expected_values[] = {100, 200, 300}; + int idx = 0; + + while (curr != &head) { + CustomData_t* entry = c_List_Entry(curr, CustomData_t, node); + assert(entry->value == expected_values[idx]); + curr = c_List_Next(curr); + idx++; + } + assert(idx == 3); + printf("[PASS] Intrusive forward iterator matching sequential expectations.\n"); + + // ========================================== + // 3. Deletion and Mutation Verification + // ========================================== + // Target and drop middle node (200) + c_List_Remove(&d2.node); + + // Structure must bridge gaps smoothly: [100] <-> [300] + assert(c_List_Next(&d1.node) == &d3.node); + assert(c_List_Prev(&d3.node) == &d1.node); + + // Eliminated isolated node must be self-referencing via macro assignment + assert(c_List_IsEmpty(&d2.node) == 1); + printf("[PASS] Drop operation and isolated reference initialization verified.\n"); + + printf("\n==================================================\n"); + printf(" Success! Intrusive macro mutations match layout specs!\n"); + printf("==================================================\n"); + + test2(); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_LockQueue.c b/Foundation/c_LockQueue.c new file mode 100644 index 0000000..90ab27e --- /dev/null +++ b/Foundation/c_LockQueue.c @@ -0,0 +1,324 @@ +#include + +#if defined(PLATFORM_POSIX) +#include +#endif + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_LockQueue_Init(c_LockQueue_t* queue, c_size_t capacity) { + if (!queue || capacity==0) return C_ERR_PARAM; + queue->data = (void**)malloc(sizeof(void*) * capacity); + if (!queue->data) { + return C_ERR_NOMEM; + } + queue->capacity = capacity; + queue->write_idx = 0; + queue->read_idx = 0; + queue->size = 0; + queue->is_shutdown = C_FALSE; + + if ((c_Mutex_Init(&queue->lock)!=C_ERR_OK) || + (c_Cond_Init(&queue->not_full)!=C_ERR_OK) || + (c_Cond_Init(&queue->not_empty)!=C_ERR_OK)) + { + if (queue->data) { + free(queue->data); + queue->data = NULL; + } + } + return C_ERR_OK; +} +void c_LockQueue_Destroy(c_LockQueue_t* queue) { + if (!queue) return; + + c_LockQueue_Shutdown(queue); + + c_Mutex_Lock(&queue->lock); + c_Cond_Destroy(&queue->not_full); + c_Cond_Destroy(&queue->not_empty); + if (queue->data) { + free(queue->data); + queue->data = NULL; + } + c_Mutex_UnLock(&queue->lock); + + // 销毁跨平台锁 + c_Mutex_Destroy(&queue->lock); +} + +void c_LockQueue_Shutdown(c_LockQueue_t* queue) { + if (!queue) return; + + c_Mutex_Lock(&queue->lock); + queue->is_shutdown = C_TRUE; + + // 广播唤醒所有正在阻塞的生产者和消费者,让他们通过 is_shutdown 状态感知并安全退出 + c_Cond_Broadcast(&queue->not_full); + c_Cond_Broadcast(&queue->not_empty); + c_Mutex_UnLock(&queue->lock); +} + +c_err_t c_LockQueue_Push(c_LockQueue_t* queue, void* data) { + if (!queue) return C_ERR_PARAM; + + c_Mutex_Lock(&queue->lock); + + // 1. 经典工业级设计:使用 while 循环检查条件,完美防御虚假唤醒 + while (queue->size == queue->capacity && !queue->is_shutdown) { + c_Cond_Wait(&queue->not_full, &queue->lock); + } + + // 2. 如果队列中途被关闭,直接拒绝写入并返回 + if (queue->is_shutdown) { + c_Mutex_UnLock(&queue->lock); + return C_ERR_FAIL; + } + + // 3. 循环数组插入数据 + queue->data[queue->write_idx] = data; + queue->write_idx = (queue->write_idx + 1) % queue->capacity; + queue->size++; + + // 4. 唤醒可能正在等待数据的消费者 + c_Cond_Signal(&queue->not_empty); + + c_Mutex_UnLock(&queue->lock); + return C_ERR_OK; +} + +c_err_t c_LockQueue_Pop(c_LockQueue_t* queue, void** item) { + if (!queue ) return C_ERR_PARAM; + + c_Mutex_Lock(&queue->lock); + + // 1. 队空且未关闭时,消费者阻塞等待 + while (queue->size == 0 && !queue->is_shutdown) { + c_Cond_Wait(&queue->not_empty, &queue->lock); + } + + // 2. 如果队列已关闭且数据已被清空,优雅退出 + if (queue->is_shutdown && queue->size == 0) { + c_Mutex_UnLock(&queue->lock); + return C_ERR_FAIL; + } + + // 3. 循环数组取出数据 + if (item) { + *item = queue->data[queue->read_idx]; + } + queue->read_idx = (queue->read_idx + 1) % queue->capacity; + queue->size--; + + // 4. 唤醒可能正在等待空间的生产者 + c_Cond_Signal(&queue->not_full); + + c_Mutex_UnLock(&queue->lock); + return C_ERR_OK; +} + +c_size_t c_LockQueue_Size(c_LockQueue_t* queue) { + if (!queue) return 0; + c_Mutex_Lock(&queue->lock); + const c_size_t size = queue->size; + c_Mutex_UnLock(&queue->lock); + return size; +} + +c_bool_t c_LockQueue_IsEmpty(c_LockQueue_t* queue) { + if (!queue) return true; // 安全檢查:無效隊列視為空 + + c_Mutex_Lock(&queue->lock); + const c_bool_t is_empty = (queue->size == 0); + c_Mutex_UnLock(&queue->lock); + + return is_empty; +} + +c_bool_t c_LockQueue_IsFull(c_LockQueue_t* queue) { + if (!queue) return false; // 安全檢查 + + c_Mutex_Lock(&queue->lock); + const c_bool_t is_full = (queue->size == queue->capacity); + c_Mutex_UnLock(&queue->lock); + return is_full; +} + + +c_bool_t c_LockQueue_TimedPop(c_LockQueue_t* queue, void** item, c_uint_t timeout_ms) { + if (!queue || !item) return C_FALSE; + + c_Mutex_Lock(&queue->lock); + + // 1. Calculate the absolute deadline for POSIX or track elapsed time for Windows + c_uint_t remaining_ms = timeout_ms; + +#if defined(PLATFORM_POSIX) + // POSIX timedwait requires an absolute system calendar time deadline + struct timespec deadline; + struct timeval now; + gettimeofday(&now, NULL); + long long total_ns = (long long)now.tv_usec * 1000 + (long long)timeout_ms * 1000000; + deadline.tv_sec = now.tv_sec + total_ns / 1000000000LL; + deadline.tv_nsec = total_ns % 1000000000LL; +#elif defined(PLATFORM_WINDOWS) + // Windows tracks relative intervals natively via GetTickCount/GetTickCount64 + ULONGLONG start_tick = GetTickCount64(); +#endif + + // 2. Loop to defend against Spurious Wakeups + while (queue->size == 0 && !queue->is_shutdown) { + if (remaining_ms == 0) { + // Out of time before cond wait or remaining time became zero + c_Mutex_UnLock(&queue->lock); + return C_FALSE; + } + + // 3. Atomically release the lock and sleep until signaled or timed out +#if defined(PLATFORM_WINDOWS) + // SleepConditionVariableCS handles relative timeout natively + BOOL wait_success = SleepConditionVariableCS(&queue->not_empty.handle, &queue->lock.handle, remaining_ms); + + if (!wait_success) { + if (GetLastError() == ERROR_TIMEOUT) { + c_Mutex_UnLock(&queue->lock); + return C_FALSE; // Dynamic Windows timeout hit + } + } + + // Recalculate remaining time in case of spurious wakeups + ULONGLONG elapsed = GetTickCount64() - start_tick; + if (elapsed >= timeout_ms) { + remaining_ms = 0; + } else { + remaining_ms = timeout_ms - (c_uint_t)elapsed; + } +#elif defined(PLATFORM_POSIX) + // pthread_cond_timedwait takes the exact calculated deadline + int wait_result = pthread_cond_timedwait(&queue->not_empty.handle, &queue->lock.handle, &deadline); + + if (wait_result != 0) { + // POSIX returns ETIMEDOUT (usually 110) if time limit expires + c_Mutex_UnLock(&queue->lock); + return C_FALSE; + } + + // Re-verify remaining time using current clock just to be precise + gettimeofday(&now, NULL); + long long current_ms = (long long)now.tv_sec * 1000 + now.tv_usec / 1000; + long long deadline_ms = (long long)deadline.tv_sec * 1000 + deadline.tv_nsec / 1000000; + if (current_ms >= deadline_ms) { + remaining_ms = 0; + } else { + remaining_ms = (c_uint_t)(deadline_ms - current_ms); + } +#endif + } + + // 4. Handle exit criteria if queue shut down during wait + if (queue->is_shutdown && queue->size == 0) { + c_Mutex_UnLock(&queue->lock); + return C_FALSE; + } + + // 5. Securely pop data from circular buffer + *item = queue->data[queue->read_idx]; + queue->read_idx = (queue->read_idx + 1) % queue->capacity; + queue->size--; + + // 6. Signal blocked producers that space has cleared up + c_Cond_Signal(&queue->not_full); + + c_Mutex_UnLock(&queue->lock); + return C_TRUE; +} + +c_bool_t c_LockQueue_TimedPush(c_LockQueue_t* queue, void* item, c_uint_t timeout_ms) { + if (!queue || !item) return C_FALSE; + + c_Mutex_Lock(&queue->lock); + + // 1. Calculate the absolute deadline for POSIX or track elapsed time for Windows + c_uint_t remaining_ms = timeout_ms; + +#if defined(PLATFORM_POSIX) + // POSIX timedwait requires an absolute wall-clock calendar deadline + struct timespec deadline; + struct timeval now; + gettimeofday(&now, NULL); + long long total_ns = (long long)now.tv_usec * 1000 + (long long)timeout_ms * 1000000; + deadline.tv_sec = now.tv_sec + total_ns / 1000000000LL; + deadline.tv_nsec = total_ns % 1000000000LL; +#elif defined(PLATFORM_WINDOWS) + // Windows tracks relative intervals natively via clock ticks + ULONGLONG start_tick = GetTickCount64(); +#endif + + // 2. Loop to defend against Spurious Wakeups while the queue is full + while (queue->size == queue->capacity && !queue->is_shutdown) { + if (remaining_ms == 0) { + // Out of time before cond wait or remaining time ticked down to zero + c_Mutex_UnLock(&queue->lock); + return C_FALSE; + } + + // 3. Atomically release the lock and sleep until signaled or timed out +#if defined(PLATFORM_WINDOWS) + // SleepConditionVariableCS handles relative timeout natively + BOOL wait_success = SleepConditionVariableCS(&queue->not_full.handle, &queue->lock.handle, remaining_ms); + + if (!wait_success) { + if (GetLastError() == ERROR_TIMEOUT) { + c_Mutex_UnLock(&queue->lock); + return C_FALSE; // Dynamic Windows timeout expired + } + } + + // Recalculate remaining time in case of a spurious wakeup + ULONGLONG elapsed = GetTickCount64() - start_tick; + if (elapsed >= timeout_ms) { + remaining_ms = 0; + } else { + remaining_ms = timeout_ms - (c_uint_t)elapsed; + } +#elif defined(PLATFORM_POSIX) + // pthread_cond_timedwait takes the absolute calculated deadline + int wait_result = pthread_cond_timedwait(&queue->not_full.handle, &queue->lock.handle, &deadline); + + if (wait_result != 0) { + // POSIX returns ETIMEDOUT (110) if time limit expires + c_Mutex_UnLock(&queue->lock); + return C_FALSE; + } + + // Re-verify remaining time using current clock to stay precise + gettimeofday(&now, NULL); + long long current_ms = (long long)now.tv_sec * 1000 + now.tv_usec / 1000; + long long deadline_ms = (long long)deadline.tv_sec * 1000 + deadline.tv_nsec / 1000000; + if (current_ms >= deadline_ms) { + remaining_ms = 0; + } else { + remaining_ms = (c_uint_t)(deadline_ms - current_ms); + } +#endif + } + + // 4. Handle exit criteria if queue shut down during wait + if (queue->is_shutdown) { + c_Mutex_UnLock(&queue->lock); + return C_FALSE; + } + + // 5. Securely push data into the circular buffer + queue->data[queue->write_idx] = item; + queue->write_idx = (queue->write_idx + 1) % queue->capacity; + queue->size++; + + // 6. Signal blocked consumers that data is ready + c_Cond_Signal(&queue->not_empty); + + c_Mutex_UnLock(&queue->lock); + return C_TRUE; +} + diff --git a/Foundation/c_LockQueue.h b/Foundation/c_LockQueue.h new file mode 100644 index 0000000..a3b6da1 --- /dev/null +++ b/Foundation/c_LockQueue.h @@ -0,0 +1,49 @@ +#ifndef INCLUDED_C_LOCKQUEUE_H +#define INCLUDED_C_LOCKQUEUE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_MUTEX_H +#include +#endif /*INCLUDED_C_MUTEX_H*/ + +#ifndef INCLUDED_C_COND_H +#include +#endif /*INCLUDED_C_COND_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + void** data; + c_size_t capacity; + c_size_t write_idx; + c_size_t read_idx; + c_size_t size; + c_bool_t is_shutdown; + + c_Mutex_t lock; + c_Cond_t not_full; + c_Cond_t not_empty; +}c_LockQueue_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_LockQueue_Init(c_LockQueue_t* queue, c_size_t capacity); +void c_LockQueue_Destroy(c_LockQueue_t* queue); +void c_LockQueue_Shutdown(c_LockQueue_t* queue); + +c_err_t c_LockQueue_Push(c_LockQueue_t* queue, void* data); +c_err_t c_LockQueue_Pop(c_LockQueue_t* queue, void** data); +c_bool_t c_LockQueue_TimedPop(c_LockQueue_t* queue, void** item, c_uint_t timeout_ms); +c_bool_t c_LockQueue_TimedPush(c_LockQueue_t* queue, void* item, c_uint_t timeout_ms); +c_size_t c_LockQueue_Size(c_LockQueue_t* queue); + +c_bool_t c_LockQueue_IsEmpty(c_LockQueue_t* queue); +c_bool_t c_LockQueue_IsFull(c_LockQueue_t* queue); + +#endif /*INCLUDED_C_LOCKQUEUE_H*/ diff --git a/Foundation/c_Matrix.c b/Foundation/c_Matrix.c new file mode 100644 index 0000000..79c362a --- /dev/null +++ b/Foundation/c_Matrix.c @@ -0,0 +1,493 @@ +#include +#include + +// 初始化矩阵(由外部传入结构体指针 self,内部只分配绑定的 c_Array 内存) +c_err_t c_Matrix_Init(c_Matrix_t* self, c_size_t rows, c_size_t cols, c_size_t element_size) { + if (!self) return C_ERR_PARAM; + if (rows == 0 || cols == 0 || element_size == 0) return C_ERR_PARAM; + + // 分配底层封装的一维泛型数组 + self->array = c_Array_New(rows * cols, element_size); + if (!self->array) { + return C_ERR_NOMEM; + } + + self->rows = rows; + self->cols = cols; + return C_SUCCESS; +} + +// 销毁矩阵(释放内部资源,不释放 self 本身,由外部决定 self 释放方式) +void c_Matrix_Destroy(c_Matrix_t* self) { + if (self) { + if (self->array) { + c_Array_Delete(&self->array); + self->array = NULL; + } + self->rows = 0; + self->cols = 0; + } +} + +// 获取单个元素字节大小 +c_size_t c_Matrix_ElementSize(c_Matrix_t* self) { + if (!self || !self->array) return 0; + return c_Array_Size(self->array); +} + +// 获取矩阵元素(通过二级指针 value 返回该元素的内部指针,并返回状态码) +c_err_t c_Matrix_Get(c_Matrix_t* self, c_size_t row, c_size_t col, void** value) { + if (!self || !value) return C_ERR_PARAM; + if (row >= self->rows || col >= self->cols) return C_ERR_PARAM; + + // 计算一维索引:index = row * cols + col + const c_size_t index = row * self->cols + col; + + c_err_t err = c_Array_Get(self->array, index, value); + if (err!=C_ERR_OK) return err; + + return C_SUCCESS; +} + +// 写入矩阵元素 +c_err_t c_Matrix_Put(c_Matrix_t* self, c_size_t row, c_size_t col, void* value) { + if (!self || !value) return C_ERR_PARAM; + if (row >= self->rows || col >= self->cols) return C_ERR_PARAM; + + const c_size_t index = row * self->cols + col; + return c_Array_Put(self->array, index, value); +} + +// 矩阵转置:dst 必须是已经初始化好的大小为 (src->cols x src->rows) 的矩阵 +c_err_t c_Matrix_TransposeTo(c_Matrix_t* src, c_Matrix_t* dst) { + if (!src || !dst) return C_ERR_PARAM; + if (src->cols != dst->rows || src->rows != dst->cols) return C_ERR_PARAM; + + for (c_size_t i = 0; i < src->rows; i++) { + for (c_size_t j = 0; j < src->cols; j++) { + void* temp = NULL; + c_Matrix_Get(src, i, j, (void**)&temp); + c_Matrix_Put(dst, j, i, temp); // 行列互换写入 + } + } + + return C_SUCCESS; +} + +// 矩阵乘法:C = A * B (以 float 为例) +c_err_t c_Matrix_Multiply(const c_Matrix_t* A, const c_Matrix_t* B, c_Matrix_t* C, void (*multiply)(const void* a, const void* b, void** c)) { + // 1. 基础健壮性检查 + if (!A || !B || !C || !multiply) return C_ERR_PARAM; + + // 2. 矩阵乘法维度匹配检查 (A的列数必须等于B的行数,C的尺寸必须是 A.rows x B.cols) + if (A->cols != B->rows || C->rows != A->rows || C->cols != B->cols) { + return C_ERR_PARAM; + } + + const c_size_t elem_size = c_Matrix_ElementSize((c_Matrix_t*)A); + + // 4. 三重循环计算矩阵乘法 + for (c_size_t i = 0; i < A->rows; i++) { + for (c_size_t j = 0; j < B->cols; j++) { + + // 在计算当前 C[i][j] 的点积前,必须先获取 C[i][j] 现有的物理指针 + void* c_item_ptr = NULL; + c_Matrix_Get(C, i, j, &c_item_ptr); + + // 将当前位置的数据清零(初始化累加器) + memset(c_item_ptr, 0, elem_size); + + for (c_size_t k = 0; k < A->cols; k++) { + void *a_item_ptr = NULL; + void *b_item_ptr = NULL; + + // 安全获取 A[i][k] 和 B[k][j] + c_Matrix_Get((c_Matrix_t*)A, i, k, &a_item_ptr); + c_Matrix_Get((c_Matrix_t*)B, k, j, &b_item_ptr); + + // 核心:调用用户自定义的单元素乘法 + // 指针传参解释: + // &prod_res 是一个二级指针 (void**),回调函数内部会将计算结果写入到 prod_res 指向的缓冲区 + void* ctx_ptr = c_item_ptr; + multiply(a_item_ptr, b_item_ptr, (void**)&ctx_ptr); + + // 执行累加:这里需要对通用泛型执行加法 + // 由于 C 语言泛型限制,我们在这里进行一维数组层面的二进制或特定类型累加。 + // 工业级做法通常会将“加法”也作为回调,或者假设前几个字节可加。 + // 针对最常见的数值类型,我们可以根据用户传进来的回调结果,在当前位置累加: + // 为使设计更严谨,通常让 multiply 内部直接实现:*(Type*)c_item_ptr += *(Type*)a * *(Type*)b + // 如果你的 multiply 语义是:*c = a * b,则需要外部进行特定类型的累加,例如以 float 为例: + // *(float*)c_item_ptr += *(float*)prod_res; + } + } + } + + return C_SUCCESS; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// 辅助函数:根据一维索引,计算转置后该元素应当去往的新一维索引 +// 公式:新行 = 旧列,新列 = 旧行 -> new_index = (old_index % cols) * rows + (old_index / cols) +C_STATIC_FORCE_INLINE +c_size_t get_transposed_source_index(c_size_t curr_index, c_size_t old_rows, c_size_t old_cols) { + return (curr_index % old_rows) * old_cols + (curr_index / old_rows); +} + +c_err_t c_Matrix_InPlaceTranspose(c_Matrix_t* self) { + if (!self || !self->array) return C_ERR_PARAM; + + c_size_t rows = self->rows; + c_size_t cols = self->cols; + c_size_t elem_size = c_Matrix_ElementSize(self); + + if (rows <= 1 && cols <= 1) { + return C_SUCCESS; + } + + // -------------------------------------------------------------------------------- + // 场景 A:方阵原地转置(不变,原本就是正确的) + // -------------------------------------------------------------------------------- + if (rows == cols) { +#define SWAP_BUF_SIZE 256 + uint8_t swap_buf[SWAP_BUF_SIZE]; + uint8_t* temp = swap_buf; + if (elem_size > SWAP_BUF_SIZE) { + temp = (uint8_t*)C_ALLOC(elem_size); + if (!temp) return C_ERR_NOMEM; + } + for (c_size_t i = 0; i < rows; i++) { + for (c_size_t j = i + 1; j < cols; j++) { + void *cell_a = NULL, *cell_b = NULL; + c_Matrix_Get(self, i, j, &cell_a); + c_Matrix_Get(self, j, i, &cell_b); + memcpy(temp, cell_a, elem_size); + memcpy(cell_a, cell_b, elem_size); + memcpy(cell_b, temp, elem_size); + } + } + if (elem_size > SWAP_BUF_SIZE) C_FREE(temp); +#undef SWAP_BUF_SIZE + return C_SUCCESS; + } + + // -------------------------------------------------------------------------------- + // 场景 B:非方阵原地转置(已完全修复环路拉取逻辑) + // -------------------------------------------------------------------------------- + c_size_t total_elements = rows * cols; + + c_size_t bitmask_size = (total_elements + 7) / 8; + uint8_t* visited = (uint8_t*)C_CALLOC(bitmask_size, sizeof(uint8_t)); + if (!visited) return C_ERR_NOMEM; + + uint8_t* cycle_buf = (uint8_t*)C_ALLOC(elem_size); + if (!cycle_buf) { + C_FREE(visited); + return C_ERR_NOMEM; + } + + void* base_data = NULL; + c_err_t array_err = c_Array_Get(self->array, 0, &base_data); + if (array_err != C_SUCCESS || !base_data) { + C_FREE(cycle_buf); + C_FREE(visited); + return array_err; + } + + for (c_size_t i = 0; i < total_elements; i++) { + if (visited[i / 8] & (1 << (i % 8))) { + continue; + } + + c_size_t curr_idx = i; + // 使用修正后的逆向映射函数 + c_size_t next_idx = get_transposed_source_index(curr_idx, rows, cols); + + if (next_idx == curr_idx) { + visited[curr_idx / 8] |= (1 << (curr_idx % 8)); + continue; + } + + // 暂存当前起点元素 + memcpy(cycle_buf, (char*)base_data + (curr_idx * elem_size), elem_size); + + // 沿着逆向数据源环路追溯拉取 + while (next_idx != i) { + void* src = (char*)base_data + (next_idx * elem_size); + void* dst = (char*)base_data + (curr_idx * elem_size); + + memcpy(dst, src, elem_size); // 正确地拉取源数据 + visited[curr_idx / 8] |= (1 << (curr_idx % 8)); + + curr_idx = next_idx; + next_idx = get_transposed_source_index(curr_idx, rows, cols); + } + + // 闭合环路 + void* dst = (char*)base_data + (curr_idx * elem_size); + memcpy(dst, cycle_buf, elem_size); + visited[curr_idx / 8] |= (1 << (curr_idx % 8)); + } + + C_FREE(cycle_buf); + C_FREE(visited); + + // 修改元数据宽高 + self->rows = cols; + self->cols = rows; + + return C_SUCCESS; +} + +c_err_t c_Matrix_Determinant(const c_Matrix_t* self, const c_MatrixOps_t* ops, void* out_det) { + if (!self || !ops || !out_det) return C_ERR_PARAM; + if (self->rows != self->cols) return C_ERR_PARAM; + + c_size_t n = self->rows; + c_size_t elem_size = c_Matrix_ElementSize((c_Matrix_t*)self); + + if (n == 1) { + void* cell = NULL; + c_Matrix_Get((c_Matrix_t*)self, 0, 0, &cell); + memcpy(out_det, cell, elem_size); + return C_SUCCESS; + } + + c_Matrix_t temp_mat; + c_err_t err = c_Matrix_Init(&temp_mat, n, n, elem_size); + if (err != C_SUCCESS) return err; + + void* src_base = NULL; + void* dst_base = NULL; + c_Array_Get(self->array, 0, &src_base); + c_Array_Get(temp_mat.array, 0, &dst_base); + memcpy(dst_base, src_base, n * n * elem_size); + + ops->one(out_det); + int sign = 1; + + // 分配真正独立的单元素计算缓冲区 + void* pivot_val = C_ALLOC(elem_size); + void* factor = C_ALLOC(elem_size); + void* temp_val = C_ALLOC(elem_size); + + if (!pivot_val || !factor || !temp_val) { + C_FREE(pivot_val); + C_FREE(factor); + C_FREE(temp_val); + return C_ERR_NOMEM; + } + + for (c_size_t i = 0; i < n; i++) { + // --- 部分主元选择 --- + c_size_t pivot_row = i; + void* max_cell = NULL; + c_Matrix_Get(&temp_mat, i, i, &max_cell); + + for (c_size_t k = i + 1; k < n; k++) { + void* check_cell = NULL; + c_Matrix_Get(&temp_mat, k, i, &check_cell); + if (ops->compare_abs(check_cell, max_cell) > 0) { + max_cell = check_cell; + pivot_row = k; + } + } + + if (ops->is_zero(max_cell)) { + ops->zero(out_det); + goto _cleanup; + } + + if (pivot_row != i) { + for (c_size_t j = i; j < n; j++) { + void *cell_a = NULL, *cell_b = NULL; + c_Matrix_Get(&temp_mat, i, j, &cell_a); + c_Matrix_Get(&temp_mat, pivot_row, j, &cell_b); + + memcpy(temp_val, cell_a, elem_size); + memcpy(cell_a, cell_b, elem_size); + memcpy(cell_b, temp_val, elem_size); + } + sign = -sign; + } + + // --- 🛠️ 核心修复点:安全获取当前主元的数据 --- + void* internal_pivot_ptr = NULL; + c_Matrix_Get(&temp_mat, i, i, &internal_pivot_ptr); // 此时被覆盖的是临时的 internal_pivot_ptr + memcpy(pivot_val, internal_pivot_ptr, elem_size); // 将真实数据拷贝到我们的 C_ALLOC 缓冲区中,保持 pivot_val 的指针值不变 + + // --- 消元 --- + for (c_size_t k = i + 1; k < n; k++) { + void* current_col_cell = NULL; + c_Matrix_Get(&temp_mat, k, i, ¤t_col_cell); + + if (ops->is_zero(current_col_cell)) continue; + + ops->div(current_col_cell, pivot_val, factor); + + for (c_size_t j = i; j < n; j++) { + void *row_i_cell = NULL, *row_k_cell = NULL; + c_Matrix_Get(&temp_mat, i, j, &row_i_cell); + c_Matrix_Get(&temp_mat, k, j, &row_k_cell); + + ops->mul(row_i_cell, factor, temp_val); + ops->sub(row_k_cell, temp_val, row_k_cell); + } + } + + // 累乘对角线元素 + ops->mul(out_det, pivot_val, out_det); + } + + if (sign == -1) { + ops->zero(temp_val); + ops->sub(temp_val, out_det, out_det); + } + +_cleanup: + // 此时的指针地址完美保持初始 C_ALLOC 状态,可以安全 C_FREE! + C_FREE(pivot_val); + C_FREE(factor); + C_FREE(temp_val); + c_Matrix_Destroy(&temp_mat); + return C_SUCCESS; +} + +c_err_t c_Matrix_Solve(const c_Matrix_t* A, const c_Matrix_t* b, c_Matrix_t* x, const c_MatrixOps_t* ops) { + // 1. 基础健壮性检查 + if (!A || !b || !x || !ops) return C_ERR_PARAM; + if (A->rows != A->cols) return C_ERR_PARAM; // A 必须是方阵 + if (b->rows != A->rows || b->cols != 1) return C_ERR_PARAM; // b 必须是 Nx1 + if (x->rows != A->rows || x->cols != 1) return C_ERR_PARAM; // x 必须是 Nx1 + + c_size_t n = A->rows; + c_size_t elem_size = c_Matrix_ElementSize((c_Matrix_t*)A); + + // 2. 初始化 Nx(N+1) 的增广矩阵 + c_Matrix_t aug; + c_err_t err = c_Matrix_Init(&aug, n, n + 1, elem_size); + if (err != C_SUCCESS) return err; + + // 填充增广矩阵:前 n 列拷贝 A,第 n+1 列拷贝 b + for (c_size_t i = 0; i < n; i++) { + for (c_size_t j = 0; j < n; j++) { + void* cell_A = NULL; + c_Matrix_Get((c_Matrix_t*)A, i, j, &cell_A); + c_Matrix_Put(&aug, i, j, cell_A); + } + void* cell_b = NULL; + c_Matrix_Get((c_Matrix_t*)b, i, 0, &cell_b); + c_Matrix_Put(&aug, i, n, cell_b); // 最后一列 + } + + // 分配独立的计算缓冲区,严格保护指针地址不被 Get 覆盖 + void* pivot_val = C_ALLOC(elem_size); + void* factor = C_ALLOC(elem_size); + void* temp_val = C_ALLOC(elem_size); + void* sum_val = C_ALLOC(elem_size); + + if (!pivot_val || !factor || !temp_val || !sum_val) { + C_FREE(pivot_val); + C_FREE(factor); + C_FREE(temp_val); + C_FREE(sum_val); + return C_ERR_NOMEM; + } + + // 3. 高斯消元主循环(化为上三角矩阵) + for (c_size_t i = 0; i < n; i++) { + // --- 3.1 部分主元选择(选绝对值最大的行交换上来,提高数值稳定性) --- + c_size_t pivot_row = i; + void* max_cell = NULL; + c_Matrix_Get(&aug, i, i, &max_cell); + + for (c_size_t k = i + 1; k < n; k++) { + void* check_cell = NULL; + c_Matrix_Get(&aug, k, i, &check_cell); + if (ops->compare_abs(check_cell, max_cell) > 0) { + max_cell = check_cell; + pivot_row = k; + } + } + + // 如果主元极度接近 0,说明矩阵奇异(无解或无数解) + if (ops->is_zero(max_cell)) { + err = C_ERR_SINGULAR; + goto _cleanup; + } + + // 行交换 + if (pivot_row != i) { + for (c_size_t j = i; j <= n; j++) { + void *cell_a = NULL, *cell_b = NULL; + c_Matrix_Get(&aug, i, j, &cell_a); + c_Matrix_Get(&aug, pivot_row, j, &cell_b); + + memcpy(temp_val, cell_a, elem_size); + memcpy(cell_a, cell_b, elem_size); + memcpy(cell_b, temp_val, elem_size); + } + } + + // --- 3.2 保护性安全读取当前行的主元数据 --- + void* internal_pivot_ptr = NULL; + c_Matrix_Get(&aug, i, i, &internal_pivot_ptr); + memcpy(pivot_val, internal_pivot_ptr, elem_size); // 复制数据,保证 pivot_val 的 C_ALLOC 指针不被踩坏 + + // --- 3.3 消元过程 --- + for (c_size_t k = i + 1; k < n; k++) { + void* current_col_cell = NULL; + c_Matrix_Get(&aug, k, i, ¤t_col_cell); + + if (ops->is_zero(current_col_cell)) continue; + + // factor = aug[k][i] / pivot_val + ops->div(current_col_cell, pivot_val, factor); + + for (c_size_t j = i; j <= n; j++) { + void *row_i_cell = NULL, *row_k_cell = NULL; + c_Matrix_Get(&aug, i, j, &row_i_cell); + c_Matrix_Get(&aug, k, j, &row_k_cell); + + ops->mul(row_i_cell, factor, temp_val); // temp = aug[i][j] * factor + ops->sub(row_k_cell, temp_val, row_k_cell); // aug[k][j] -= temp + } + } + } + + // 4. 回代法求解(Back Substitution) + // 公式:x[i] = (aug[i][n] - sum(aug[i][j] * x[j])) / aug[i][i] + for (int i = (int)n - 1; i >= 0; i--) { + ops->zero(sum_val); // sum = 0 + + for (c_size_t j = (c_size_t)i + 1; j < n; j++) { + void *aug_cell = NULL, *x_cell = NULL; + c_Matrix_Get(&aug, (c_size_t)i, j, &aug_cell); + c_Matrix_Get(x, j, 0, &x_cell); + + ops->mul(aug_cell, x_cell, temp_val); // temp = aug[i][j] * x[j] + ops->add(sum_val, temp_val, sum_val); // sum += temp + } + + void *aug_b_cell = NULL, *aug_diag_cell = NULL; + c_Matrix_Get(&aug, (c_size_t)i, n, &aug_b_cell); // 最后一列的常数项 + c_Matrix_Get(&aug, (c_size_t)i, (c_size_t)i, &aug_diag_cell); // 对角线主元 + + ops->sub(aug_b_cell, sum_val, temp_val); // temp = b_item - sum + + void* x_dest_slot = NULL; + c_Matrix_Get(x, (c_size_t)i, 0, &x_dest_slot); + ops->div(temp_val, aug_diag_cell, x_dest_slot); // x[i] = temp / aug[i][i] + } + + err = C_SUCCESS; + +_cleanup: + C_FREE(pivot_val); + C_FREE(factor); + C_FREE(temp_val); + C_FREE(sum_val); + c_Matrix_Destroy(&aug); + return err; +} diff --git a/Foundation/c_Matrix.h b/Foundation/c_Matrix.h new file mode 100644 index 0000000..64239e1 --- /dev/null +++ b/Foundation/c_Matrix.h @@ -0,0 +1,90 @@ +#ifndef INCLUDED_C_MATRIX_H +#define INCLUDED_C_MATRIX_H + +#ifndef INCLUDED_C_ARRAY_H +#include +#endif /*INCLUDED_C_ARRAY_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// “主元全为0”而触发矩阵奇异错误 +#define C_ERR_SINGULAR C_ERR_FAIL + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_Array_t* array; // 内部封装的一维泛型数组 + c_size_t rows; // 矩阵行数 + c_size_t cols; // 矩阵列数 +}c_Matrix_t; + +// 泛型数学算子包 +typedef struct { + void (*zero)(void* out); // 设为 0 + void (*one)(void* out); // 设为 1 + void (*add)(const void* a, const void* b, void* out); // out = a + b + void (*sub)(const void* a, const void* b, void* out); // out = a - b + void (*mul)(const void* a, const void* b, void* out); // out = a * b + void (*div)(const void* a, const void* b, void* out); // out = a / b + int (*is_zero)(const void* a); // 判断是否为 0 (或接近 0) + int (*compare_abs)(const void* a, const void* b); // 绝对值比较:|a| > |b| 返回 1, 否则返回 0 +} c_MatrixOps_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_Matrix_Init(c_Matrix_t* self, c_size_t rows, c_size_t cols, c_size_t element_size); + +void c_Matrix_Destroy(c_Matrix_t* self); + +c_size_t c_Matrix_ElementSize(c_Matrix_t* self); + +c_err_t c_Matrix_Get(c_Matrix_t* self, c_size_t row, c_size_t col, void** value); + +c_err_t c_Matrix_Put(c_Matrix_t* self, c_size_t row, c_size_t col, void* value); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* + +float 运算 举例 + void float_matmul_callback(const void* a, const void* b, void** c) { + float val_a = *(const float*)a; + float val_b = *(const float*)b; + + // 1. 拿到外部矩阵 C[i][j] 的真实内存地址 + float* dest_cell = *(float**)c; + + // 2. 严格执行乘加(+=) + *dest_cell += val_a * val_b; + } + */ + +c_err_t c_Matrix_Multiply(const c_Matrix_t* A, const c_Matrix_t* B, c_Matrix_t* C, void (*multiply)(const void* a, const void* b, void** c)) ; + +c_err_t c_Matrix_TransposeTo(c_Matrix_t* src, c_Matrix_t* dst); + +c_err_t c_Matrix_InPlaceTranspose(c_Matrix_t* self); + +/** + * @brief 计算方阵的行列式 + * @param self 矩阵指针 + * @param ops 用户注册的泛型数学算子包 + * @param out_det 存储计算结果的内存指针(外部分配) + * @return c_err_t 成功返回 C_SUCCESS,非方阵返回 C_ERR_PARAM + */ +c_err_t c_Matrix_Determinant(const c_Matrix_t* self, const c_MatrixOps_t* ops, void* out_det); + +/** + * @brief 使用高斯消元法求解线性方程组 Ax = b + * @param A NxN 的系数矩阵指针 + * @param b Nx1 的常数项向量矩阵指针 + * @param x Nx1 的解向量矩阵指针(外部分配好内存) + * @param ops 用户注册的泛型数学算子包 + * @return c_err_t 成功返回 C_SUCCESS,维度不匹配返回 C_ERR_PARAM,矩阵奇异返回 C_ERR_SINGULAR + */ +c_err_t c_Matrix_Solve(const c_Matrix_t* A, const c_Matrix_t* b, c_Matrix_t* x, const c_MatrixOps_t* ops); + +#endif /*INCLUDED_C_MATRIX_H*/ diff --git a/Foundation/c_Mutex.c b/Foundation/c_Mutex.c new file mode 100644 index 0000000..40b3099 --- /dev/null +++ b/Foundation/c_Mutex.c @@ -0,0 +1,63 @@ +#include + +c_err_t c_Mutex_Init(c_Mutex_t* mutex) { + if (!mutex) return C_ERR_FAIL; + +#if defined(PLATFORM_WINDOWS) + // InitializeCriticalSection 不会失败,但 InitializeCriticalSectionAndSpinCount 可能会 + // 工业级推荐直接使用此 API,性能优秀 + InitializeCriticalSection(&mutex->handle); + mutex->is_initialized = C_TRUE; + return C_ERR_OK; +#elif defined(PLATFORM_POSIX) + // POSIX 默认是 PTHREAD_MUTEX_DEFAULT (不可重入锁) + if (pthread_mutex_init(&mutex->handle, NULL) == 0) { + mutex->is_initialized = C_TRUE; + return C_ERR_OK; + } + return C_ERR_FAIL; +#endif +} + +void c_Mutex_Destroy(c_Mutex_t* mutex) { + if (!mutex || !mutex->is_initialized) return; + +#if defined(PLATFORM_WINDOWS) + DeleteCriticalSection(&mutex->handle); +#elif defined(PLATFORM_POSIX) + pthread_mutex_destroy(&mutex->handle); +#endif + mutex->is_initialized = C_FALSE; +} + + +void c_Mutex_Lock(c_Mutex_t* mutex) { + if (!mutex || !mutex->is_initialized) return; +#if defined(PLATFORM_WINDOWS) + EnterCriticalSection(&mutex->handle); +#elif defined(PLATFORM_POSIX) + pthread_mutex_lock(&mutex->handle); +#endif +} + +c_bool_t c_Mutex_TryLock(c_Mutex_t* mutex) { + if (!mutex || !mutex->is_initialized) return C_FALSE; + +#if defined(PLATFORM_WINDOWS) + // TryEnterCriticalSection 返回非 0 表示成功 + return (TryEnterCriticalSection(&mutex->handle) != 0)?C_TRUE:C_FALSE; +#elif defined(PLATFORM_POSIX) + // pthread_mutex_trylock 返回 0 表示成功 + return (pthread_mutex_trylock(&mutex->handle) == 0)?C_TRUE:C_FALSE; +#endif +} + + +void c_Mutex_UnLock(c_Mutex_t* mutex) { + if (!mutex || !mutex->is_initialized) return; +#if defined(PLATFORM_WINDOWS) + LeaveCriticalSection(&mutex->handle); +#elif defined(PLATFORM_POSIX) + pthread_mutex_unlock(&mutex->handle); +#endif +} \ No newline at end of file diff --git a/Foundation/c_Mutex.h b/Foundation/c_Mutex.h new file mode 100644 index 0000000..dd88484 --- /dev/null +++ b/Foundation/c_Mutex.h @@ -0,0 +1,43 @@ +#ifndef INCLUDED_C_MUTEX_H +#define INCLUDED_C_MUTEX_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#if defined(_WIN32) || defined(_WIN64) + #define PLATFORM_WINDOWS 1 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include +#else + #define PLATFORM_POSIX 1 + #include +#endif + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { +#if defined(PLATFORM_WINDOWS) + CRITICAL_SECTION handle; +#elif defined(PLATFORM_POSIX) + pthread_mutex_t handle; +#endif + c_bool_t is_initialized; +} c_Mutex_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_Mutex_Init(c_Mutex_t* mutex); +void c_Mutex_Destroy(c_Mutex_t* mutex); +void c_Mutex_Lock(c_Mutex_t* mutex); +c_bool_t c_Mutex_TryLock(c_Mutex_t* mutex); +void c_Mutex_UnLock(c_Mutex_t* mutex); + +#endif /*INCLUDED_C_MUTEX_H*/ diff --git a/Foundation/c_PtrArrayBag.c b/Foundation/c_PtrArrayBag.c new file mode 100644 index 0000000..0347b2f --- /dev/null +++ b/Foundation/c_PtrArrayBag.c @@ -0,0 +1,64 @@ +#include +#include + +#define DEFAULT_INIT_CAPACITY 4 + +c_err_t c_PtrArrayBag_Init(c_PtrArrayBag_t* self, c_size_t capacity) { + if (!self) return C_ERR_PARAM; + self->capacity = (capacity > 0) ? capacity : DEFAULT_INIT_CAPACITY; + self->size = 0; + self->array = (void**)C_ALLOC(self->capacity * sizeof(void*)); + if (!self->array) { + self->capacity = 0; + return C_ERR_NOMEM; + } + + return C_ERR_OK; +} + +void c_PtrArrayBag_Destroy(c_PtrArrayBag_t* self) { + if (!self) return; + C_FREE(self->array); + self->capacity = 0; + self->size = 0; +} + +c_err_t c_PtrArrayBag_Add(c_PtrArrayBag_t* self, void* item) { + if (!self || !self->array) return C_ERR_PARAM; + + // Handle dynamic resizing (doubling capacity) + if (self->size >= self->capacity) { + const c_size_t new_capacity = self->capacity << 1; + void** new_array = (void**)C_REALLOC(self->array, new_capacity * sizeof(void*)); + if (!new_array) { + return C_ERR_NOMEM; + } + self->array = new_array; + self->capacity = new_capacity; + } + + self->array[self->size++] = item; + return C_ERR_OK; +} + +void* c_PtrArrayBag_Get(c_PtrArrayBag_t* self, c_size_t index) { + if (!self || !self->array || index >= self->size) { + return NULL; + } + return self->array[index]; +} + +c_err_t c_PtrArrayBag_Remove(c_PtrArrayBag_t* self, c_size_t index) { + if (!self || !self->array) return C_ERR_PARAM; + if (index >= self->size) return C_ERR_OUTOFBOUND; + + // Shift elements left to fill the gap + if (index < self->size - 1) { + const c_size_t elements_to_move = self->size - index - 1; + memmove(&self->array[index], &self->array[index + 1], elements_to_move * sizeof(void*)); + } + + self->size--; + self->array[self->size] = NULL; // Optional clear for safety + return C_ERR_OK; +} diff --git a/Foundation/c_PtrArrayBag.h b/Foundation/c_PtrArrayBag.h new file mode 100644 index 0000000..8b1550f --- /dev/null +++ b/Foundation/c_PtrArrayBag.h @@ -0,0 +1,27 @@ +#ifndef INCLUDED_C_PTRARRAYBAG_H +#define INCLUDED_C_PTRARRAYBAG_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + void** array; + c_size_t capacity; + c_size_t size; +}c_PtrArrayBag_t; + +c_err_t c_PtrArrayBag_Init(c_PtrArrayBag_t* self, c_size_t capacity); + +void c_PtrArrayBag_Destroy(c_PtrArrayBag_t* self); + +c_err_t c_PtrArrayBag_Add(c_PtrArrayBag_t* self, void* item); + +void* c_PtrArrayBag_Get(c_PtrArrayBag_t* self, c_size_t index); + +c_err_t c_PtrArrayBag_Remove(c_PtrArrayBag_t* self, c_size_t index); + +#endif /*INCLUDED_C_PTRARRAYBAG_H*/ diff --git a/Foundation/c_PtrArrayBag.t.c b/Foundation/c_PtrArrayBag.t.c new file mode 100644 index 0000000..b90960b --- /dev/null +++ b/Foundation/c_PtrArrayBag.t.c @@ -0,0 +1,98 @@ +#include "c_PtrArrayBag.h" +#include +#include +#include + +// 輔助測試函數:印出測試進度 +void test_log(const char* test_name) { + printf("[PASS] %s\n", test_name); +} + +int main(int argc, char** argv){ + + printf("開始執行 c_PtrArrayBag 測試用例...\n\n"); + + // 模擬一些測試資料 + int val1 = 100; + int val2 = 200; + int val3 = 300; + int val4 = 400; + + // ========================================== + // 1. 測試初始化 (Init) + // ========================================== + c_PtrArrayBag_t bag; + c_err_t err = c_PtrArrayBag_Init(&bag, 2); // 故意設小容量測試動態擴容 + assert(err == C_ERR_OK); + assert(bag.capacity == 2); + assert(bag.size == 0); + assert(bag.array != NULL); + test_log("初始化測試"); + + // ========================================== + // 2. 測試新增元素與基本讀取 (Add & Get) + // ========================================== + err = c_PtrArrayBag_Add(&bag, &val1); + assert(err == C_ERR_OK); + assert(bag.size == 1); + assert(*(int*)c_PtrArrayBag_Get(&bag, 0) == 100); + + err = c_PtrArrayBag_Add(&bag, &val2); + assert(err == C_ERR_OK); + assert(bag.size == 2); + assert(*(int*)c_PtrArrayBag_Get(&bag, 1) == 200); + test_log("基本新增與讀取測試"); + + // ========================================== + // 3. 測試動態擴容 (Dynamic Resizing) + // ========================================== + // 目前 size = 2, capacity = 2。再加第 3 個元素應該要觸發容量翻倍 + err = c_PtrArrayBag_Add(&bag, &val3); + assert(err == C_ERR_OK); + assert(bag.size == 3); + assert(bag.capacity == 4); // 2 * 2 = 4 + assert(*(int*)c_PtrArrayBag_Get(&bag, 2) == 300); + test_log("自動擴容測試"); + + // ========================================== + // 4. 測試邊界與無效引數 (Edge Cases) + // ========================================== + // 讀取超出範圍的索引應該回傳 NULL + assert(c_PtrArrayBag_Get(&bag, 99) == NULL); + + // 傳入 NULL 結構指標應該要防呆 + assert(c_PtrArrayBag_Init(NULL, 10) == C_ERR_PARAM); + assert(c_PtrArrayBag_Add(NULL, &val4) == C_ERR_PARAM); + assert(c_PtrArrayBag_Remove(NULL, 0) == C_ERR_PARAM); + test_log("邊界防呆測試"); + + // ========================================== + // 5. 測試刪除元素與平移 (Remove) + // ========================================== + // 目前狀態: [100, 200, 300],刪除索引 1 (200) + // 預期結果: [100, 300],後方元素向前平移,保持順序 + err = c_PtrArrayBag_Remove(&bag, 1); + assert(err == C_ERR_OK); + assert(bag.size == 2); + + // 驗證原本索引 2 的 300 是否變成索引 1 + assert(*(int*)c_PtrArrayBag_Get(&bag, 0) == 100); + assert(*(int*)c_PtrArrayBag_Get(&bag, 1) == 300); + + // 嘗試刪除不存在的索引 + err = c_PtrArrayBag_Remove(&bag, 5); + assert(err == C_ERR_OUTOFBOUND); + test_log("刪除與元素平移測試"); + + // ========================================== + // 6. 測試銷毀與記憶體釋放 (Destroy) + // ========================================== + c_PtrArrayBag_Destroy(&bag); + assert(bag.array == NULL); + assert(bag.size == 0); + assert(bag.capacity == 0); + test_log("銷毀測試"); + + printf("\n恭喜!所有測試用例皆順利通過 (All Tests Passed)!\n"); + return 0; +} diff --git a/Foundation/c_PtrLinkBag.c b/Foundation/c_PtrLinkBag.c new file mode 100644 index 0000000..4283864 --- /dev/null +++ b/Foundation/c_PtrLinkBag.c @@ -0,0 +1,65 @@ +#include +#include + +c_err_t c_PtrLinkBag_Init(c_PtrLinkBag_t* self) { + if (!self) return C_ERR_PARAM; + self->head= NULL; + return C_ERR_OK; +} + +void c_PtrLinkBag_Destroy(c_PtrLinkBag_t* self) { + if (!self) return; + c_PtrLinkBagNode_t* p = self->head; + while (p) { + c_PtrLinkBagNode_t* q = p->next; + C_FREE(p); + p = q; + } + self->head = NULL; +} + +c_err_t c_PtrLinkBag_Add(c_PtrLinkBag_t* self, void* item) { + if (!self) return C_ERR_PARAM; + c_PtrLinkBagNode_t* p; + C_NEW(p); + if (!p) { + return C_ERR_NOMEM; + } + p->ptr = item; + p->next = self->head; + self->head = p; + return C_ERR_OK; +} + +c_err_t c_PtrLinkBag_Remove(c_PtrLinkBag_t* self, const void* item) { + if (!self) return C_ERR_PARAM; + c_PtrLinkBagNode_t** curr = &self->head; + while (*curr != NULL) { + if ((*curr)->ptr == item) { + c_PtrLinkBagNode_t* entry = *curr; + *curr = entry->next; + C_FREE(entry); + return C_ERR_OK; + } + curr = &(*curr)->next; + } + + return C_ERR_NOTFOUND; +} + +void c_PtrLinkBagIter_Remove(c_PtrLinkBagIter_t* self) { + // 防呆檢查:確保迭代器有效,且當前指向的節點不為空 + if (!self || !self->node || !*(self->node)) return; + + c_PtrLinkBagNode_t* to_delete = *(self->node); + + // 將當前結構中維護的指標(可能是上一節點的 next,或是 bag 的 head) + // 修改為指向下一個節點,直接從鏈結串列中斷開 + *(self->node) = to_delete->next; + + // 釋放記憶體 + C_FREE(to_delete); + + // 注意:此時 self->node 自動更新指向了原本的下一個節點 + // 使用者不需要再呼叫 Next(),即可直接對新節點進行 Get() 或再次 Remove() +} diff --git a/Foundation/c_PtrLinkBag.h b/Foundation/c_PtrLinkBag.h new file mode 100644 index 0000000..1bfaf45 --- /dev/null +++ b/Foundation/c_PtrLinkBag.h @@ -0,0 +1,62 @@ +#ifndef INCLUDED_C_PTRLINKBAG_H +#define INCLUDED_C_PTRLINKBAG_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct c_PtrLinkBagNode_t { + void* ptr; + struct c_PtrLinkBagNode_t* next; +}c_PtrLinkBagNode_t; + +typedef struct { + c_PtrLinkBagNode_t* head; +}c_PtrLinkBag_t; + +typedef struct { + c_PtrLinkBag_t* bag; + c_PtrLinkBagNode_t** node; +}c_PtrLinkBagIter_t; + +c_err_t c_PtrLinkBag_Init(c_PtrLinkBag_t* self); + +void c_PtrLinkBag_Destroy(c_PtrLinkBag_t* self); + +c_err_t c_PtrLinkBag_Add(c_PtrLinkBag_t* self, void* item); + +c_err_t c_PtrLinkBag_Remove(c_PtrLinkBag_t* self, const void* item); + +C_STATIC_FORCE_INLINE +void c_PtrLinkBagIter_Init(c_PtrLinkBagIter_t* self, c_PtrLinkBag_t* bag) { + if (!self || !bag) return; + self->bag = bag; + self->node = &bag->head; +} + +C_STATIC_FORCE_INLINE +c_bool_t c_PtrLinkBagIter_HasNext(c_PtrLinkBagIter_t* self) { + if (!self) return C_FALSE; + return (self->node!=NULL) && (*(self->node)!=NULL); +} + +C_STATIC_FORCE_INLINE +void* c_PtrLinkBagIter_Next(c_PtrLinkBagIter_t* self) { + if (!self || !self->node) return NULL; + void* ptr = (*(self->node))->ptr; + self->node = &(*(self->node))->next; + return ptr; +} + +C_STATIC_FORCE_INLINE +void* c_PtrLinkBagIter_Get(c_PtrLinkBagIter_t* self) { + if (!self || !self->node) return NULL; + return (*(self->node))->ptr; +} + +void c_PtrLinkBagIter_Remove(c_PtrLinkBagIter_t* self); + +#endif /*INCLUDED_C_PTRLINKBAG_H*/ diff --git a/Foundation/c_PtrLinkBag.t.c b/Foundation/c_PtrLinkBag.t.c new file mode 100644 index 0000000..b24d1be --- /dev/null +++ b/Foundation/c_PtrLinkBag.t.c @@ -0,0 +1,61 @@ +#include "c_PtrLinkBag.h" +#include +#include +#include + +int main(int argc, char** argv){ + printf("開始執行 c_PtrLinkBag 測試...\n"); + + c_PtrLinkBag_t bag; + c_PtrLinkBag_Init(&bag); + + int v1 = 10, v2 = 20, v3 = 30; + + // 1. 測試新增 (使用頭插法,順序會是 30 -> 20 -> 10) + c_PtrLinkBag_Add(&bag, &v1); + c_PtrLinkBag_Add(&bag, &v2); + c_PtrLinkBag_Add(&bag, &v3); + + // 2. 測試走訪 + c_PtrLinkBagIter_t iter; + c_PtrLinkBagIter_Init(&iter, &bag); + + printf("目前鏈結串列內容: "); + while (c_PtrLinkBagIter_HasNext(&iter)) { + int* val = (int*)c_PtrLinkBagIter_Next(&iter); + printf("%d ", *val); + } + printf("\n"); + + // 3. 測試在迭代過程中刪除特定元素 (例如刪除 20) + c_PtrLinkBagIter_Init(&iter, &bag); + while (c_PtrLinkBagIter_HasNext(&iter)) { + int* val = (int*)c_PtrLinkBagIter_Get(&iter); + if (*val == 20) { + c_PtrLinkBagIter_Remove(&iter); // 刪除 20,iter->node 自動指向 10 + printf("[Log] 迭代器成功刪除了 20\n"); + } else { + c_PtrLinkBagIter_Next(&iter); // 沒刪除時才手動前進 + } + } + + // 4. 驗證刪除後的背包內容 (預期只剩 30 -> 10) + c_PtrLinkBagIter_Init(&iter, &bag); + assert(*(int*)c_PtrLinkBagIter_Next(&iter) == 30); + assert(*(int*)c_PtrLinkBagIter_Next(&iter) == 10); + assert(c_PtrLinkBagIter_HasNext(&iter) == C_FALSE); + + // 5. 測試一般刪除 (Remove) + c_err_t err = c_PtrLinkBag_Remove(&bag, &v3); + assert(err == C_ERR_OK); + + // 檢查是不是只剩 10 + c_PtrLinkBagIter_Init(&iter, &bag); + assert(*(int*)c_PtrLinkBagIter_Get(&iter) == 10); + + // 清除記憶體 + c_PtrLinkBag_Destroy(&bag); + printf("所有測試成功通過!\n"); + + return 0; +} diff --git a/Foundation/c_RBTree.c b/Foundation/c_RBTree.c new file mode 100644 index 0000000..db25597 --- /dev/null +++ b/Foundation/c_RBTree.c @@ -0,0 +1,321 @@ +#include +#include +#include "c_Macros.h" + +// Internal Helper: Allocates structural payload node properties +C_STATIC_FORCE_INLINE +c_RBTreeNode_t* create_node(c_RBTree_t* self, void* obj) { + int size = (int)sizeof(c_RBTreeNode_t) + self->obj_size; + size = C_ALIGN_UPB(size, C_ALIGN_SIZE); + + c_RBTreeNode_t* node = (c_RBTreeNode_t*)C_ALLOC(size); + if (!node) return NULL; + node->data = node + 1; + + memcpy(node->data, obj, self->obj_size); + node->left = self->nil; + node->right = self->nil; + node->parent = self->nil; + node->color = C_RBTREE_RED; + return node; +} + + +static void destroy_recursive(c_RBTree_t* self, c_RBTreeNode_t* node) { + if (node == self->nil || node == NULL) return; + destroy_recursive(self, node->left); + destroy_recursive(self, node->right); + C_FREE(node); +} + + +// Tree Rotation Helpers +C_STATIC_FORCE_INLINE +void left_rotate(c_RBTree_t* self, c_RBTreeNode_t* x) { + c_RBTreeNode_t* y = x->right; + x->right = y->left; + if (y->left != self->nil) y->left->parent = x; + y->parent = x->parent; + if (x->parent == self->nil) self->root = y; + else if (x == x->parent->left) x->parent->left = y; + else x->parent->right = y; + y->left = x; + x->parent = y; +} + +C_STATIC_FORCE_INLINE +void right_rotate(c_RBTree_t* self, c_RBTreeNode_t* y) { + c_RBTreeNode_t* x = y->left; + y->left = x->right; + if (x->right != self->nil) x->right->parent = y; + x->parent = y->parent; + if (y->parent == self->nil) self->root = x; + else if (y == y->parent->right) y->parent->right = x; + else y->parent->left = x; + x->right = y; + y->parent = x; +} + +// Balance adjustments post standard insert passes +static void insert_fixup(c_RBTree_t* self, c_RBTreeNode_t* z) { + while (z->parent->color == C_RBTREE_RED) { + if (z->parent == z->parent->parent->left) { + c_RBTreeNode_t* y = z->parent->parent->right; + if (y->color == C_RBTREE_RED) { + z->parent->color = C_RBTREE_BLACK; + y->color = C_RBTREE_BLACK; + z->parent->parent->color = C_RBTREE_RED; + z = z->parent->parent; + } else { + if (z == z->parent->right) { + z = z->parent; + left_rotate(self, z); + } + z->parent->color = C_RBTREE_BLACK; + z->parent->parent->color = C_RBTREE_RED; + right_rotate(self, z->parent->parent); + } + } else { + c_RBTreeNode_t* y = z->parent->parent->left; + if (y->color == C_RBTREE_RED) { + z->parent->color = C_RBTREE_BLACK; + y->color = C_RBTREE_BLACK; + z->parent->parent->color = C_RBTREE_RED; + z = z->parent->parent; + } else { + if (z == z->parent->left) { + z = z->parent; + right_rotate(self, z); + } + z->parent->color = C_RBTREE_BLACK; + z->parent->parent->color = C_RBTREE_RED; + left_rotate(self, z->parent->parent); + } + } + } + self->root->color = C_RBTREE_BLACK; +} + + +static void inorder_recursive(c_RBTree_t* self, c_RBTreeNode_t* node, c_RBTree_Visit_f visit, void* cl) { + if (node == self->nil || node == NULL) return; + inorder_recursive(self, node->left, visit, cl); + visit(node->data, cl); + inorder_recursive(self, node->right, visit, cl); +} + +C_STATIC_FORCE_INLINE +void rb_transplant(c_RBTree_t* self, c_RBTreeNode_t* u, c_RBTreeNode_t* v) { + if (u->parent == self->nil) { + self->root = v; + } else if (u == u->parent->left) { + u->parent->left = v; + } else { + u->parent->right = v; + } + v->parent = u->parent; +} + +// 內部輔助函數:尋找子樹中的最小節點(用於刪除時尋找後繼節點) +C_STATIC_FORCE_INLINE +c_RBTreeNode_t* rb_tree_minimum(c_RBTree_t* self, c_RBTreeNode_t* node) { + while (node->left != self->nil) { + node = node->left; + } + return node; +} + +static void remove_fixup(c_RBTree_t* self, c_RBTreeNode_t* x) { + while (x != self->root && x->color == C_RBTREE_BLACK) { + if (x == x->parent->left) { + c_RBTreeNode_t* w = x->parent->right; // x 的兄弟節點 + + // 狀況 1:兄弟節點 w 是紅色 + if (w->color == C_RBTREE_RED) { + w->color = C_RBTREE_BLACK; + x->parent->color = C_RBTREE_RED; + left_rotate(self, x->parent); + w = x->parent->right; + } + + // 狀況 2:兄弟節點 w 是黑色,且其兩個子節點也都是黑色 + if (w->left->color == C_RBTREE_BLACK && w->right->color == C_RBTREE_BLACK) { + w->color = C_RBTREE_RED; + x = x->parent; + } else { + // 狀況 3:兄弟節點 w 是黑色,w 的右子是黑色,左子是紅色 + if (w->right->color == C_RBTREE_BLACK) { + w->left->color = C_RBTREE_BLACK; + w->color = C_RBTREE_RED; + right_rotate(self, w); + w = x->parent->right; + } + // 狀況 4:兄弟節點 w 是黑色,且 w 的右子是紅色 + w->color = x->parent->color; + x->parent->color = C_RBTREE_BLACK; + w->right->color = C_RBTREE_BLACK; + left_rotate(self, x->parent); + x = self->root; // 結束循環 + } + } else { + // 對稱狀況:x 是其父節點的右子 + c_RBTreeNode_t* w = x->parent->left; + if (w->color == C_RBTREE_RED) { + w->color = C_RBTREE_BLACK; + x->parent->color = C_RBTREE_RED; + right_rotate(self, x->parent); + w = x->parent->left; + } + if (w->right->color == C_RBTREE_BLACK && w->left->color == C_RBTREE_BLACK) { + w->color = C_RBTREE_RED; + x = x->parent; + } else { + if (w->left->color == C_RBTREE_BLACK) { + w->right->color = C_RBTREE_BLACK; + w->color = C_RBTREE_RED; + left_rotate(self, w); + w = x->parent->left; + } + w->color = x->parent->color; + x->parent->color = C_RBTREE_BLACK; + w->left->color = C_RBTREE_BLACK; + right_rotate(self, x->parent); + x = self->root; + } + } + } + x->color = C_RBTREE_BLACK; +} + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_RBTree_Init(c_RBTree_t* self, int obj_size, c_RBTree_Compare_f compare) { + if (!self || obj_size <= 0 || !compare) return C_ERR_PARAM; + + // Allocate an explicit shared Sentinel NIL node boundary properties + self->nil = (c_RBTreeNode_t*)C_ALLOC(sizeof(c_RBTreeNode_t)); + if (!self->nil) return C_ERR_NOMEM; + self->nil->data = NULL; + self->nil->color = C_RBTREE_BLACK; + self->nil->left = NULL; + self->nil->right = NULL; + self->nil->parent = NULL; + + self->root = self->nil; + self->obj_size = obj_size; + self->size = 0; + self->compare = compare; + return C_ERR_OK; +} + +void c_RBTree_Destroy(c_RBTree_t* self) { + if (!self) return; + destroy_recursive(self, self->root); + C_FREE(self->nil); + self->root = NULL; + self->size = 0; +} + + +c_err_t c_RBTree_Insert(c_RBTree_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + + c_RBTreeNode_t* y = self->nil; + c_RBTreeNode_t* x = self->root; + int cmp = 0; + + while (x != self->nil) { + y = x; + cmp = self->compare(obj, x->data); + if (cmp == 0) return C_ERR_ALREADY_EXISTS; // Unique constraints protection + else if (cmp < 0) x = x->left; + else x = x->right; + } + + c_RBTreeNode_t* z = create_node(self, obj); + if (!z) return C_ERR_NOMEM; + z->parent = y; + + if (y == self->nil) self->root = z; + else if (self->compare(z->data, y->data) < 0) y->left = z; + else y->right = z; + + insert_fixup(self, z); + self->size++; + return C_ERR_OK; +} + +void* c_RBTree_Find(c_RBTree_t* self, const void* key_target) { + if (!self || !key_target) return NULL; + c_RBTreeNode_t* x = self->root; + while (x != self->nil) { + int cmp = self->compare(key_target, x->data); + if (cmp == 0) return x->data; + x = (cmp < 0) ? x->left : x->right; + } + return NULL; +} + +void c_RBTree_InOrder(c_RBTree_t* self, c_RBTree_Visit_f visit, void* cl) { + if (!self || !visit) return; + inorder_recursive(self, self->root, visit, cl); +} + +c_err_t c_RBTree_Remove(c_RBTree_t* self, void* obj) { + if (!self || !obj) return C_ERR_PARAM; + + // 1. 先尋找目標節點是否存在 + c_RBTreeNode_t* z = self->root; + while (z != self->nil) { + int cmp = self->compare(obj, z->data); + if (cmp == 0) break; + z = (cmp < 0) ? z->left : z->right; + } + + if (z == self->nil) return C_ERR_NOTFOUND; // 節點不存在 + + c_RBTreeNode_t* y = z; + c_RBTreeNode_t* x; + c_RBTreeColor_t y_original_color = y->color; + + // 2. 執行標準二元搜尋樹刪除與移植 + if (z->left == self->nil) { + x = z->right; + rb_transplant(self, z, z->right); + } else if (z->right == self->nil) { + x = z->left; + rb_transplant(self, z, z->left); + } else { + // z 有兩個子節點,尋找其右子樹的最小節點作為後繼者 y + y = rb_tree_minimum(self, z->right); + y_original_color = y->color; + x = y->right; + + if (y->parent == z) { + x->parent = y; // 如果 y 剛好是 z 的直接右子,建立與 nil 的 parent 關係 + } else { + rb_transplant(self, y, y->right); + y->right = z->right; + y->right->parent = y; + } + + rb_transplant(self, z, y); + y->left = z->left; + y->left->parent = y; + y->color = z->color; + } + + // 釋放被刪除節點的記憶體 + C_FREE(z); + self->size--; + + // 3. 如果失去的節點顏色是黑色,會破壞黑高平衡,必須呼叫修復狀態機 + if (y_original_color == C_RBTREE_BLACK) { + remove_fixup(self, x); + } + + return C_ERR_OK; +} diff --git a/Foundation/c_RBTree.h b/Foundation/c_RBTree.h new file mode 100644 index 0000000..b45a5dd --- /dev/null +++ b/Foundation/c_RBTree.h @@ -0,0 +1,48 @@ +#ifndef INCLUDED_C_RBTREE_H +#define INCLUDED_C_RBTREE_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef enum { + C_RBTREE_RED, + C_RBTREE_BLACK +} c_RBTreeColor_t; + +typedef struct c_RBTreeNode_t { + void* data; + c_RBTreeColor_t color; + struct c_RBTreeNode_t* left; + struct c_RBTreeNode_t* right; + struct c_RBTreeNode_t* parent; +} c_RBTreeNode_t; + +// User Comparison Callback Signature: Returns <0 if a < b, 0 if a == b, >0 if a > b +typedef int (*c_RBTree_Compare_f)(const void* a, const void* b); + +// User Visitor Callback Signature for Traversals +typedef void (*c_RBTree_Visit_f)(void* data, void* cl); + +typedef struct { + c_RBTreeNode_t* root; + c_RBTreeNode_t* nil; // Sentinal node representing leaf nodes to simplify rotation math + int obj_size; + c_size_t size; + c_RBTree_Compare_f compare; +} c_RBTree_t; + +c_err_t c_RBTree_Init(c_RBTree_t* self, int obj_size, c_RBTree_Compare_f compare); +void c_RBTree_Destroy(c_RBTree_t* self); + +c_err_t c_RBTree_Insert(c_RBTree_t* self, void* obj); +c_err_t c_RBTree_Remove(c_RBTree_t* self, void* obj); +void* c_RBTree_Find(c_RBTree_t* self, const void* key_target); + +void c_RBTree_InOrder(c_RBTree_t* self, c_RBTree_Visit_f visit, void* cl); + + +#endif /*INCLUDED_C_RBTREE_H*/ diff --git a/Foundation/c_RBTree.t.c b/Foundation/c_RBTree.t.c new file mode 100644 index 0000000..1a4f1f0 --- /dev/null +++ b/Foundation/c_RBTree.t.c @@ -0,0 +1,92 @@ +#include "c_RBTree.h" +#include +#include +#include + +typedef struct { + int id; + char name[32]; +} Task_t; + +// 比較回呼函數 +int compare_tasks(const void* a, const void* b) { + return (((Task_t*)a)->id - ((Task_t*)b)->id); +} + +// 走訪時驗證遞增順序 +int g_last_id = -1; +void visit_verify(void* data, void* cl) { + Task_t* t = (Task_t*)data; + assert(t->id > g_last_id); // 必須嚴格遞增 + g_last_id = t->id; +} + +void test_log(const char* name) { + printf("[PASS] %s\n", name); +} + +int main() { + printf("==================================================\n"); + printf(" 開始執行 c_RBTree (含 O(log N) Remove) 最終整合測試\n"); + printf("==================================================\n\n"); + + c_RBTree_t tree; + c_RBTree_Init(&tree, sizeof(Task_t), compare_tasks); + + Task_t tasks[] = { + {40, "Task_40"}, {20, "Task_20"}, {60, "Task_60"}, + {10, "Task_10"}, {30, "Task_30"}, {50, "Task_50"}, {70, "Task_70"} + }; + + // 1. 批次推入資料 + for (int i = 0; i < 7; i++) { + c_RBTree_Insert(&tree, &tasks[i]); + } + assert(tree.size == 7); + test_log("1. 批次推入 7 筆資料成功"); + + // ========================================== + // 2. 測試刪除葉子節點 (Task 10) + // ========================================== + Task_t target = {10, ""}; + c_err_t err = c_RBTree_Remove(&tree, &target); + assert(err == C_ERR_OK); + assert(tree.size == 6); + assert(c_RBTree_Find(&tree, &target) == NULL); // 預期找不到 + + // 驗證刪除後的結構順序性 + g_last_id = -1; + c_RBTree_InOrder(&tree, visit_verify, NULL); + test_log("2. 成功刪除葉子節點 (10) 且中序走訪維持平衡有序"); + + // ========================================== + // 3. 測試刪除擁有多個子節點的核心根節點 (Task 40) + // ========================================== + target.id = 40; + err = c_RBTree_Remove(&tree, &target); + assert(err == C_ERR_OK); + assert(tree.size == 5); + assert(c_RBTree_Find(&tree, &target) == NULL); + + // 再次驗證刪除後的結構順序性 + g_last_id = -1; + c_RBTree_InOrder(&tree, visit_verify, NULL); + test_log("3. 成功刪除雙子核心節點 (40) 且平衡移植狀態正確"); + + // ========================================== + // 4. 刪除不存在的鍵值與無效參數防呆 + // ========================================== + target.id = 999; + assert(c_RBTree_Remove(&tree, &target) == C_ERR_NOTFOUND); + assert(c_RBTree_Remove(NULL, &target) == C_ERR_PARAM); + test_log("4. 刪除越界與無效引數防呆驗證成功"); + + c_RBTree_Destroy(&tree); + test_log("5. 紅黑樹資源徹底銷毀成功"); + + printf("\n==================================================\n"); + printf(" 恭喜!包含 O(log N) Remove 在內的所有紅黑樹單元測試完美通過!\n"); + printf("==================================================\n"); + + return 0; +} \ No newline at end of file diff --git a/Foundation/c_SmartPtr.c b/Foundation/c_SmartPtr.c new file mode 100644 index 0000000..5b446b4 --- /dev/null +++ b/Foundation/c_SmartPtr.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_SmartPtr.h b/Foundation/c_SmartPtr.h new file mode 100644 index 0000000..c14f069 --- /dev/null +++ b/Foundation/c_SmartPtr.h @@ -0,0 +1,115 @@ +#ifndef INCLUDED_C_SMARTPTR_H +#define INCLUDED_C_SMARTPTR_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_MEMORY_H +#include +#endif /*INCLUDED_C_MEMORY_H*/ + +#ifndef INCLUDED_C_ATOMIC_H +#include +#endif /*INCLUDED_C_ATOMIC_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +// 釋放資源的函數指標類型 +typedef void (*c_SmartPtrFreeFn_t)(void* ptr, void* args); + +// 智慧指標結構體 +typedef struct { + void* ptr; + c_SmartPtrFreeFn_t free_fn; + void* args; + c_atomic_int_t* ref_count; +} c_SmartPtr_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +c_SmartPtr_t c_SmartPtr_Make(void* ptr, const c_SmartPtrFreeFn_t free_fn, void* args) { + c_SmartPtr_t sptr = { .ptr = ptr, .free_fn = free_fn, .args = args, .ref_count = NULL }; + + if (ptr == NULL) return sptr; + + C_NEW(sptr.ref_count); + if (sptr.ref_count != NULL) { + c_atomic_init_int(sptr.ref_count, 1); + } + return sptr; +} + +C_STATIC_FORCE_INLINE +c_err_t c_SmartPtr_Init(c_SmartPtr_t* self, void* ptr, const c_SmartPtrFreeFn_t free_fn, void* args) { + if (ptr == NULL) return C_ERR_PARAM; + + self->ptr = ptr; + self->free_fn = free_fn; + self->args = args; + self->ref_count = NULL; + + C_NEW(self->ref_count); + if (self->ref_count == NULL) { + return C_ERR_NOMEM; + } + + c_atomic_init_int(self->ref_count, 1); + return C_ERR_OK; +} + +C_STATIC_FORCE_INLINE +void c_SmartPtr_Destroy(c_SmartPtr_t* self) { + if (!self || !self->ref_count) return; + + if (C_ATOMIC_FETCH_SUB(self->ref_count, 1) == 1) { + if (self->free_fn && self->ptr) { + self->free_fn(self->ptr, self->args); + } + C_FREE(self->ref_count); + } + + memset(self, 0, sizeof(c_SmartPtr_t)); +} + +C_STATIC_FORCE_INLINE +c_err_t c_SmartPtr_Copy(c_SmartPtr_t* dest, const c_SmartPtr_t* src) { + if (!dest || !src || dest == src) return C_ERR_PARAM; + if (!src->ptr || !src->ref_count) return C_ERR_PARAM; + + c_SmartPtr_Destroy(dest); + + dest->ptr = src->ptr; + dest->free_fn = src->free_fn; + dest->args = src->args; + dest->ref_count = src->ref_count; + + C_ATOMIC_FETCH_ADD(dest->ref_count, 1); + return C_SUCCESS; +} + +C_STATIC_FORCE_INLINE +c_err_t c_SmartPtr_Move(c_SmartPtr_t* dest, c_SmartPtr_t* src) { + if (!dest || !src || dest == src) return C_ERR_PARAM; + + c_SmartPtr_Destroy(dest); + *dest = *src; + memset(src, 0, sizeof(c_SmartPtr_t)); + + return C_SUCCESS; +} + +C_STATIC_FORCE_INLINE +int c_SmartPtr_UseCount(const c_SmartPtr_t* self) { + if (!self || !self->ref_count) return 0; + return (int)C_ATOMIC_LOAD(self->ref_count); +} + + + +#endif /*INCLUDED_C_SMARTPTR_H*/ diff --git a/Foundation/c_SmartPtr.t.c b/Foundation/c_SmartPtr.t.c new file mode 100644 index 0000000..af339b4 --- /dev/null +++ b/Foundation/c_SmartPtr.t.c @@ -0,0 +1,57 @@ +#include "c_SmartPtr.h" +#include +#include + +// 自訂釋放函數:負責關閉檔案 +void close_file_callback(void* ptr, void* args) { + FILE* fp = (FILE*)ptr; + char* filename = (char*)args; + + if (fp) { + printf("[SmartPtr] 引用計數歸零,自動關閉檔案: %s\n", filename); + fclose(fp); + } +} + +int main() { + printf("--- 1. 建立資源 (Open File) ---\n"); + char* my_file = "test.txt"; + FILE* fp = fopen(my_file, "w"); + if (!fp) return 1; + + // 寫入一些測試資料 + fprintf(fp, "Hello Smart Pointer in C!"); + fflush(fp); // 確保資料進硬碟,但先不關閉檔案 + + // 使用智慧指標接管檔案控制權 + c_SmartPtr_t sptr_a = c_SmartPtr_Make(fp, close_file_callback, my_file); + printf("sptr_a 建立完成,目前引用計數: %d\n", c_SmartPtr_UseCount(&sptr_a)); + + // 建立另外兩個空白的智慧指標容器 + c_SmartPtr_t sptr_b = {0}; + c_SmartPtr_t sptr_c = {0}; + + printf("\n--- 2. 測試 Copy 語義 (共享檔案控制權) ---\n"); + c_SmartPtr_Copy(&sptr_b, &sptr_a); + printf("A 的計數: %d, B 的計數: %d\n", c_SmartPtr_UseCount(&sptr_a), c_SmartPtr_UseCount(&sptr_b)); + + printf("\n--- 3. 測試 Move 語義 (B 所有權轉移給 C) ---\n"); + c_SmartPtr_Move(&sptr_c, &sptr_b); + printf("Move 後 -> B 計數: %d (已空), C 計數: %d\n", c_SmartPtr_UseCount(&sptr_b), c_SmartPtr_UseCount(&sptr_c)); + + printf("\n--- 4. 開始依序銷毀指標物件 ---\n"); + printf("銷毀 sptr_a...\n"); + c_SmartPtr_Destroy(&sptr_a); // 計數 2 -> 1 + printf("sptr_a 銷毀後,C 的計數: %d\n", c_SmartPtr_UseCount(&sptr_c)); + + printf("銷毀 sptr_b (本身已空,無影響)...\n"); + c_SmartPtr_Destroy(&sptr_b); + + printf("銷毀 sptr_c...\n"); + // 計數 1 -> 0,自動觸發 close_file_callback 關閉檔案! + c_SmartPtr_Destroy(&sptr_c); + + printf("\n程式結束,所有資源安全回收。\n"); + return 0; +} + diff --git a/Foundation/c_SmartPtrVector.c b/Foundation/c_SmartPtrVector.c new file mode 100644 index 0000000..a55ec21 --- /dev/null +++ b/Foundation/c_SmartPtrVector.c @@ -0,0 +1,152 @@ +#include + +#define DEFAULT_INITIAL_CAPACITY 4 +#define DEFAULT_GROW_FACTOR 2 + +c_err_t c_SmartPtrVector_Init(c_SmartPtrVector_t* vector, c_size_t capacity) { + if (!vector) return C_ERR_PARAM; + if (capacity>0) { + vector->array = C_CALLOC(capacity, sizeof(c_SmartPtr_t)); + if (!vector->array) { + return C_ERR_NOMEM; + } + }else { + vector->array = NULL; + } + vector->capacity = capacity; + vector->size = 0; + return C_SUCCESS; +} + +void c_SmartPtrVector_Destroy(c_SmartPtrVector_t* vector) { + if (!vector || !vector->array) return; + for (c_size_t i=0; isize; i++) { + c_SmartPtr_Destroy(&vector->array[i]); + } + C_FREE(vector->array); + vector->capacity = 0; + vector->size = 0; +} + +c_err_t c_SmartPtrVector_PushBack(c_SmartPtrVector_t* vector, const c_SmartPtr_t* ptr) { + if (!vector || !ptr) return C_ERR_PARAM; + + // 檢查是否需要自動擴容 + if (vector->size >= vector->capacity) { + c_size_t new_capacity = (vector->capacity == 0) ? DEFAULT_INITIAL_CAPACITY : (vector->capacity * DEFAULT_GROW_FACTOR); + c_SmartPtr_t* new_array = (c_SmartPtr_t*)C_CALLOC(new_capacity, sizeof(c_SmartPtr_t)); + if (!new_array) { + return C_ERR_NOMEM; + } + + for (c_size_t i = 0; i < vector->size; i++) { + c_SmartPtr_Move(&new_array[i], &vector->array[i]); + } + + C_FREE(vector->array); + vector->array = new_array; + vector->capacity = new_capacity; + } + + // 執行 Copy 語義:在 Vector 內部建立一個新引用,底層引用計數 (ref_count) 自動 +1 + c_SmartPtr_Copy(&vector->array[vector->size], ptr); + vector->size++; + + return C_SUCCESS; +} + +c_err_t c_SmartPtrVector_Remove(c_SmartPtrVector_t* self, c_size_t index) { + if (!self || index >= self->size) return C_ERR_PARAM; + + // 1. 銷毀該 index 的智慧指標(若引用計數歸零,會在此處自動觸發資源 free 回收) + c_SmartPtr_Destroy(&self->array[index]); + + // 2. 將後續的所有智慧指標往前遞補 + // 這裡同樣必須使用 Move 語義進行「所有權搬移」,保持計數器數值不動 + for (c_size_t i = index; i < self->size - 1; i++) { + c_SmartPtr_Move(&self->array[i], &self->array[i + 1]); + } + + // 3. 將多出來的最後一個格子完全抹零抹乾淨,防止殘留野指標 + memset(&self->array[self->size - 1], 0, sizeof(c_SmartPtr_t)); + self->size--; + + return C_SUCCESS; +} + +c_err_t c_SmartPtrVector_PopBack(c_SmartPtrVector_t* vector, c_SmartPtr_t* ptr) { + if (!vector || !ptr) { + return C_ERR_PARAM; + } + + if (vector->size == 0) { + return C_ERR_EMPTY; + } + + c_SmartPtr_Move(ptr, &vector->array[vector->size - 1]); + vector->size--; + + return C_SUCCESS; +} + +c_err_t c_SmartPtrVector_Insert(c_SmartPtrVector_t* vector, c_size_t index, const c_SmartPtr_t* ptr) { + if (!vector || !ptr || index > vector->size) { + return C_ERR_PARAM; + } + + // 1. 檢查是否需要擴容 + if (vector->size >= vector->capacity) { + c_size_t new_capacity = (vector->capacity == 0) ? DEFAULT_INITIAL_CAPACITY : (vector->capacity * DEFAULT_GROW_FACTOR); + c_SmartPtr_t* new_array = (c_SmartPtr_t*)C_CALLOC(new_capacity, sizeof(c_SmartPtr_t)); + if (!new_array) { + return C_ERR_NOMEM; + } + + // 擴容搬移:使用 Move 語義,不變動計數 + for (c_size_t i = 0; i < vector->size; i++) { + c_SmartPtr_Move(&new_array[i], &vector->array[i]); + } + + C_FREE(vector->array); + vector->array = new_array; + vector->capacity = new_capacity; + } + + // 2. 將插入點之後的元素「從後往前」依序向後挪動一格 + // 關鍵:必須使用 c_SmartPtr_move 以確保在挪動過程中引用計數不發生任何不必要的增減與震盪 + for (c_size_t i = vector->size; i > index; i--) { + c_SmartPtr_Move(&vector->array[i], &vector->array[i - 1]); + } + + // 3. 在空出來的 index 位置執行 Copy 語義,正式納入 Vector 管理(引用計數 +1) + c_SmartPtr_Copy(&vector->array[index], ptr); + vector->size++; + + return C_SUCCESS; +} + +c_err_t c_SmartPtrVector_RemoveAndTake(c_SmartPtrVector_t* self, c_size_t index, c_SmartPtr_t* ptr) { + // 防禦性檢查:確保 self、輸出指標 ptr 有效,且 index 沒有越界 + if (!self || !ptr || index >= self->size) { + return C_ERR_PARAM; + } + + // 1. 執行 Move 語義:將指定 index 處的所有權完美轉移給外面的 ptr + // 這會自動安全釋放 ptr 原本持有的舊資源,並將 self->array[index] 掏空抹零。 + // 底層引用計數(ref_count)數值完全保持不動,沒有任何多餘開銷。 + c_SmartPtr_Move(ptr, &self->array[index]); + + // 2. 將 index 之後的所有智慧指標往前遞補一格 + // 因為 self->array[index] 剛才已經被 move 抹零了, + // 這裡的迴圈會把 index + 1 的內容 move 過去,依此類推。 + for (c_size_t i = index; i < self->size - 1; i++) { + c_SmartPtr_Move(&self->array[i], &self->array[i + 1]); + } + + // 3. 將最後一個多出來的殘留格子完全抹乾淨,防止產生野指標,並更新大小 + memset(&self->array[self->size - 1], 0, sizeof(c_SmartPtr_t)); + self->size--; + + return C_SUCCESS; +} + diff --git a/Foundation/c_SmartPtrVector.h b/Foundation/c_SmartPtrVector.h new file mode 100644 index 0000000..1083be9 --- /dev/null +++ b/Foundation/c_SmartPtrVector.h @@ -0,0 +1,49 @@ +#ifndef INCLUDED_C_SMARTPTRVECTOR_H +#define INCLUDED_C_SMARTPTRVECTOR_H + +#ifndef INCLUDED_C_SMARTPTR_H +#include +#endif /*INCLUDED_C_SMARTPTR_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + c_SmartPtr_t* array; + c_size_t capacity; + c_size_t size; +}c_SmartPtrVector_t; + +#define C_SMART_PTR_VECTOR_INITIALIZER {NULL, 0, 0} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +c_SmartPtrVector_t c_SmartPtrVector_Create(void) { + return (c_SmartPtrVector_t)C_SMART_PTR_VECTOR_INITIALIZER; +} + +C_STATIC_FORCE_INLINE +const c_SmartPtr_t* c_SmartPtrVector_Get(const c_SmartPtrVector_t* self, c_size_t index) { + if (!self || index >= self->size) { + return NULL; + } + return &self->array[index]; +} + +c_err_t c_SmartPtrVector_Init(c_SmartPtrVector_t* vector, c_size_t capacity); + +void c_SmartPtrVector_Destroy(c_SmartPtrVector_t* vector); + +c_err_t c_SmartPtrVector_PushBack(c_SmartPtrVector_t* vector, const c_SmartPtr_t* ptr); + +c_err_t c_SmartPtrVector_PopBack(c_SmartPtrVector_t* vector, c_SmartPtr_t* ptr); + +c_err_t c_SmartPtrVector_Remove(c_SmartPtrVector_t* self, c_size_t index); + +c_err_t c_SmartPtrVector_Insert(c_SmartPtrVector_t* vector, c_size_t index, const c_SmartPtr_t* ptr); + +c_err_t c_SmartPtrVector_RemoveAndTake(c_SmartPtrVector_t* self, c_size_t index, c_SmartPtr_t* ptr); + +#endif /*INCLUDED_C_SMARTPTRVECTOR_H*/ diff --git a/Foundation/c_Stopwatch.c b/Foundation/c_Stopwatch.c new file mode 100644 index 0000000..85f2b9f --- /dev/null +++ b/Foundation/c_Stopwatch.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_Stopwatch.h b/Foundation/c_Stopwatch.h new file mode 100644 index 0000000..6751e1c --- /dev/null +++ b/Foundation/c_Stopwatch.h @@ -0,0 +1,179 @@ +#ifndef INCLUDED_C_STOPWATCH_H +#define INCLUDED_C_STOPWATCH_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +#if defined(_WIN32) || defined(_WIN64) + #include + typedef LARGE_INTEGER c_TimePoint_t; +#else +#include +#include +typedef struct timespec c_TimePoint_t; +#endif + +typedef struct { + c_TimePoint_t start_time; + double elapsed_milliseconds; + c_bool_t is_running; +} c_Stopwatch_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * Internal private helper: Captures the system's hardware clock monotonic timestamp. + */ +C_STATIC_FORCE_INLINE +void c_Stopwatch_GetTimePoint(c_TimePoint_t* tp) { +#if defined(_WIN32) || defined(_WIN64) + QueryPerformanceCounter(tp); +#else + // Using CLOCK_MONOTONIC to guarantee safety against system time changes/NTP adjustments + clock_gettime(CLOCK_MONOTONIC, tp); +#endif +} + +/** + * Internal private helper: Computes the difference in seconds between two points. + */ +C_STATIC_FORCE_INLINE +double c_Stopwatch_ComputeDiffInS(const c_TimePoint_t* start, const c_TimePoint_t* end) { +#if defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + return (double)(end->QuadPart - start->HighPart) / (double)freq.QuadPart; +#else + double start_sec = (double)start->tv_sec + (double)start->tv_nsec / 1e9; + double end_sec = (double)end->tv_sec + (double)end->tv_nsec / 1e9; + return end_sec - start_sec; +#endif +} + +/** + * Calculate the delta time in milliseconds between two distinct time points. + * Time Complexity: O(1) | Auxiliary Space: O(1) + * @param start Pointer to the starting time point timestamp. + * @param end Pointer to the ending time point timestamp. + * @return The double precision scalar difference value in milliseconds, + * or -1.0 if any parameter pointer is NULL. + */ +C_STATIC_FORCE_INLINE +double c_Stopwatch_ComputeDiffInMS(const c_TimePoint_t* start, const c_TimePoint_t* end) { + if (start == NULL || end == NULL) return -1.0; + +#if defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + // Convert to seconds first, then scale up to milliseconds + double seconds = (double)(end->QuadPart - start->QuadPart) / (double)freq.QuadPart; + return seconds * 1000.0; +#else + double start_ms = ((double)start->tv_sec * 1000.0) + ((double)start->tv_nsec / 1e6); + double end_ms = ((double)end->tv_sec * 1000.0) + ((double)end->tv_nsec / 1e6); + return end_ms - start_ms; +#endif +} + +/** + * Initialize the Stopwatch. Registers parameters to default states cleanly. + */ +C_STATIC_FORCE_INLINE +c_err_t c_Stopwatch_Init(c_Stopwatch_t* sw) { + if (sw == NULL) return C_ERR_PARAM; + + sw->elapsed_milliseconds = 0.0; + sw->is_running = C_FALSE; + memset(&sw->start_time, 0, sizeof(c_TimePoint_t)); + + return C_ERR_OK; +} + +/** + * Start or resume tracking time. + */ +C_STATIC_FORCE_INLINE +c_err_t c_Stopwatch_Start(c_Stopwatch_t* sw) { + if (sw == NULL) return C_ERR_PARAM; + if (sw->is_running) return C_ERR_OK; // Safe skip if already processing + + c_Stopwatch_GetTimePoint(&sw->start_time); + sw->is_running = C_TRUE; + + return C_ERR_OK; +} + +/** + * Stop tracking time and cache the elapsed segment into the accumulator. + */ +C_STATIC_FORCE_INLINE +c_err_t c_Stopwatch_Stop(c_Stopwatch_t* sw) { + if (sw == NULL) return C_ERR_PARAM; + if (!sw->is_running) return C_ERR_OK; + + c_TimePoint_t end_time; + c_Stopwatch_GetTimePoint(&end_time); + + // Add the delta directly to the millisecond buffer field + sw->elapsed_milliseconds += c_Stopwatch_ComputeDiffInMS(&sw->start_time, &end_time); + sw->is_running = C_FALSE; + + return C_ERR_OK; +} + +/** + * Soft Reset: Blasts elapsed counts down to zero while preserving the active running state. + */ +C_STATIC_FORCE_INLINE +c_err_t c_Stopwatch_Clear(c_Stopwatch_t* sw) { + if (sw == NULL) return C_ERR_PARAM; + + sw->elapsed_milliseconds = 0.0; + if (sw->is_running) { + c_Stopwatch_GetTimePoint(&sw->start_time); // Re-anchor start time mark to prevent jumps + } + + return C_ERR_OK; +} + + +/** + * Utility helper: Extract elapsed timing down to millisecond intervals. + */ +C_STATIC_FORCE_INLINE +double c_Stopwatch_GetElapsedMilliseconds(const c_Stopwatch_t* sw) { + if (sw == NULL) return 0.0; + if (!sw->is_running) return sw->elapsed_milliseconds; // Pure O(1) cache read + + c_TimePoint_t active_tick; + c_Stopwatch_GetTimePoint(&active_tick); + + // Dynamically add current active delta segment to the base accumulator + return sw->elapsed_milliseconds + c_Stopwatch_ComputeDiffInMS(&sw->start_time, &active_tick); +} + + +/** + * Extract total measured elapsed time in seconds up to this exact moment. + * Works perfectly whether the stopwatch is running or stopped (Lap peeking feature). + * Time Complexity: O(1) | Auxiliary Space: O(1) in-place + * @param sw Pointer to the constant stopwatch instance context. + * @return The double precision scalar elapsed time value in seconds, + * or 0.0 if the stopwatch instance handle is NULL. + */ +C_STATIC_FORCE_INLINE +double c_Stopwatch_GetElapsedSeconds(const c_Stopwatch_t* sw) { + if (sw == NULL) return 0.0; + + // Leverage the existing GetElapsedMilliseconds API and scale it down to second precision. + // This maintains perfect abstraction layer unity without duplicating clock read conditions. + return c_Stopwatch_GetElapsedMilliseconds(sw) / 1000.0; +} + +#endif /*INCLUDED_C_STOPWATCH_H*/ diff --git a/Foundation/c_Stopwatch.t.c b/Foundation/c_Stopwatch.t.c new file mode 100644 index 0000000..69c0f77 --- /dev/null +++ b/Foundation/c_Stopwatch.t.c @@ -0,0 +1,132 @@ +#include "c_Stopwatch.h" +#include +#include + +#include +#include +#include +#include + +// Your updated line tracing diagnostic macro +#define EXPECT_EQ(actual, expected, msg) \ + do { \ + if ((actual) != (expected)) { \ + printf(" [X] Assert Failed: %s (Expected %d, got %d) %s:%d\n", msg, (int)(expected), (int)(actual), __FILE__, __LINE__); \ + return C_FALSE; \ + } \ + } while(0) + +// Helper macro for double comparisons with floating-point tolerance +#define EXPECT_NEAR(actual, expected, tolerance, msg) \ + do { \ + if (fabs((actual) - (expected)) > (tolerance)) { \ + printf(" [X] Assert Failed: %s (Expected %f, got %f) %s:%d\n", msg, (double)(expected), (double)(actual), __FILE__, __LINE__); \ + return C_FALSE; \ + } \ + } while(0) + +// External references to previous core modules +extern void c_Stopwatch_GetTimePoint(c_TimePoint_t* tp); + +/** + * Mocks active OS-level processing delays using portable nanosleep/Sleep interfaces. + */ +static void c_Framework_MockExecutionDelayMs(int ms) { +#if defined(_WIN32) || defined(_WIN64) + Sleep(ms); +#else + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000L; + nanosleep(&ts, NULL); +#endif +} + +c_bool_t test_stopwatch_compute_diff_in_ms(void) { + c_TimePoint_t t_start, t_end; + + // Test Case 1: Param Parameter Enforcement Boundary Checks + EXPECT_NEAR(c_Stopwatch_ComputeDiffInMS(NULL, &t_end), -1.0, 1e-6, "NULL start point guard missed"); + EXPECT_NEAR(c_Stopwatch_ComputeDiffInMS(&t_start, NULL), -1.0, 1e-6, "NULL end point guard missed"); + + // Test Case 2: Standard Interval Difference Evaluation + printf(" [LOG] Capturing timestamp benchmarks across a 60ms thread stall scenario...\n"); + c_Stopwatch_GetTimePoint(&t_start); + + c_Framework_MockExecutionDelayMs(60); + + c_Stopwatch_GetTimePoint(&t_end); + + double delta_ms = c_Stopwatch_ComputeDiffInMS(&t_start, &t_end); + + // Allow a flexible tolerance boundary for generic OS context switching variances + EXPECT_EQ(delta_ms >= 55.0, C_TRUE, "Computed millisecond difference fell short of target delay thresholds"); + printf(" [STAT] Clock cycle delta computed: %f ms\n", delta_ms); + + // Test Case 3: Identity Time Check (Difference between identical points must equal 0.0) + EXPECT_NEAR(c_Stopwatch_ComputeDiffInMS(&t_start, &t_start), 0.0, 1e-6, "Identity point calculation returned non-zero value"); + + return C_TRUE; +} + + +c_bool_t test_stopwatch_lifecycle(void) { + c_Stopwatch_t sw; + + // Test Case 1: Param Checking Guards & Initial Conditions + EXPECT_EQ(c_Stopwatch_Init(NULL), C_ERR_PARAM, "NULL stopwatch handler guard missed"); + EXPECT_EQ(c_Stopwatch_Init(&sw), C_ERR_OK, "Stopwatch init initialization failed"); + EXPECT_NEAR(c_Stopwatch_GetElapsedSeconds(&sw), 0.0, 1e-6, "Freshly initialized stopwatch reported non-zero runtime"); + + // Test Case 2: Standard Interval Measurement Tracking Check + printf(" [LOG] Launching Stopwatch profile tracking (100ms thread stall scenario)...\n"); + EXPECT_EQ(c_Stopwatch_Start(&sw), C_ERR_OK, "Stopwatch engine start pass failed"); + + c_Framework_MockExecutionDelayMs(100); + + // Check lap peeking capabilities while running + double lap_peek_ms = c_Stopwatch_GetElapsedMilliseconds(&sw); + EXPECT_EQ(lap_peek_ms >= 90.0, C_TRUE, "Lap peeking reported impossibly low runtime under active intervals"); + + EXPECT_EQ(c_Stopwatch_Stop(&sw), C_ERR_OK, "Stopwatch engine stop pass failed"); + EXPECT_EQ(sw.is_running, C_FALSE, "Stop execution failed to flag active timeline states offline"); + + double final_seconds = c_Stopwatch_GetElapsedSeconds(&sw); + // Allow a wide tolerance for generic OS process scheduling variances + EXPECT_NEAR(final_seconds >= 0.09, C_TRUE, 0.0, "Measured timing fell short of target delay thresholds"); + + // Test Case 3: Accumulated Timing Checks (Resume feature validation) + printf(" [LOG] Resuming stopwatch profile tracking (Additional 50ms delay path)...\n"); + EXPECT_EQ(c_Stopwatch_Start(&sw), C_ERR_OK, "Stopwatch engine resume phase failed"); + + c_Framework_MockExecutionDelayMs(50); + + EXPECT_EQ(c_Stopwatch_Stop(&sw), C_ERR_OK, "Stopwatch subsequent interval termination failed"); + double cumulative_ms = c_Stopwatch_GetElapsedMilliseconds(&sw); + EXPECT_EQ(cumulative_ms >= 140.0, C_TRUE, "Stopwatch failed to accumulate consecutive interval data layers"); + + // Test Case 4: Soft Clear Verification + EXPECT_EQ(c_Stopwatch_Clear(&sw), C_ERR_OK, "Stopwatch clear execution crashed"); + EXPECT_NEAR(c_Stopwatch_GetElapsedSeconds(&sw), 0.0, 1e-6, "Clear protocol left residual timing tracking metrics inside buffer"); + + return C_TRUE; +} + +int main(void) { + printf("=== Starting Framework Verification: c_Stopwatch_ComputeDiffInMS ===\n"); + if (test_stopwatch_compute_diff_in_ms()) { + printf(" [PASS] Standalone Monotonic Delta Millisecond Compute Engine Verified Successfully.\n"); + } else { + printf(" [FAIL] Delta Resolution Pipeline Processing Mismatches Detected.\n"); + } + + + printf("=== Starting Framework Verification: c_Stopwatch ===\n"); + if (test_stopwatch_lifecycle()) { + printf(" [PASS] High-Precision Monotonic Stopwatch Lifecycle Pipelines Verified Successfully.\n"); + } else { + printf(" [FAIL] Stopwatch Engine Processing or State Tracking Mismatches Intercepted.\n"); + } + + return 0; +} diff --git a/Foundation/c_Str.c b/Foundation/c_Str.c new file mode 100644 index 0000000..5ed7d5f --- /dev/null +++ b/Foundation/c_Str.c @@ -0,0 +1,301 @@ +#include +#include +#include +#include + +#define idx(i, len) ((i) <= 0 ? (i) + (len) : (i) - 1) + +#define convert(s, i, j) do { int _len; \ + assert(s); _len = (int)strlen(s); \ + i = idx(i, _len); j = idx(j, _len); \ + if (i > j) { int t = i; i = j; j = t; } \ + assert(i >= 0 && j <= _len); } while (0) + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +char *c_Str_sub(const char *s, int i, int j) { + char *str, *p; + convert(s, i, j); + p = str = C_ALLOC(j - i + 1); + while (i < j) + *p++ = s[i++]; + *p = '\0'; + return str; +} + +char *c_Str_dup(const char *s, int i, int j, int n) { + int k; + char *str, *p; + assert(n >= 0); + convert(s, i, j); + p = str = C_ALLOC(n*(j - i) + 1); + if (j - i > 0) + while (n-- > 0) + for (k = i; k < j; k++) + *p++ = s[k]; + *p = '\0'; + return str; +} + +char *c_Str_reverse(const char *s, int i, int j) { + char *str, *p; + convert(s, i, j); + p = str = C_ALLOC(j - i + 1); + while (j > i) + *p++ = s[--j]; + *p = '\0'; + return str; +} + +char *c_Str_cat(const char *s1, int i1, int j1, + const char *s2, int i2, int j2) { + char *str, *p; + convert(s1, i1, j1); + convert(s2, i2, j2); + p = str = C_ALLOC(j1 - i1 + j2 - i2 + 1); + while (i1 < j1) + *p++ = s1[i1++]; + while (i2 < j2) + *p++ = s2[i2++]; + *p = '\0'; + return str; +} + +char *c_Str_catv(const char *s, ...) { + char *str, *p; + const char *save = s; + int i, j, len = 0; + va_list ap; + va_start(ap, s); + while (s) { + i = va_arg(ap, int); + j = va_arg(ap, int); + convert(s, i, j); + len += j - i; + s = va_arg(ap, const char *); + } + va_end(ap); + p = str = C_ALLOC(len + 1); + s = save; + va_start(ap, s); + while (s) { + i = va_arg(ap, int); + j = va_arg(ap, int); + convert(s, i, j); + while (i < j) + *p++ = s[i++]; + s = va_arg(ap, const char *); + } + va_end(ap); + *p = '\0'; + return str; +} + +char *c_Str_map(const char *s, int i, int j, + const char *from, const char *to) { + static char map[256] = { 0 }; + if (from && to) { + unsigned c; + for (c = 0; c < sizeof map; c++) + map[c] = c; + while (*from && *to) + map[(unsigned char)*from++] = *to++; + assert(*from == 0 && *to == 0); + } else { + assert(from == NULL && to == NULL && s); + assert(map['a']); + } + if (s) { + char *str, *p; + convert(s, i, j); + p = str = C_ALLOC(j - i + 1); + while (i < j) + *p++ = map[(unsigned char)s[i++]]; + *p = '\0'; + return str; + } else + return NULL; +} + +int c_Str_pos(const char *s, int i) { + assert(s); + const int len = (int) strlen(s); + i = idx(i, len); + assert(i >= 0 && i <= len); + return i + 1; +} + +int c_Str_len(const char *s, int i, int j) { + convert(s, i, j); + return j - i; +} + +int c_Str_cmp(const char *s1, int i1, int j1, + const char *s2, int i2, int j2) { + convert(s1, i1, j1); + convert(s2, i2, j2); + s1 += i1; + s2 += i2; + if (j1 - i1 < j2 - i2) { + int cond = strncmp(s1, s2, j1 - i1); + return cond == 0 ? -1 : cond; + } else if (j1 - i1 > j2 - i2) { + const int cond = strncmp(s1, s2, j2 - i2); + return cond == 0 ? +1 : cond; + } else + return strncmp(s1, s2, j1 - i1); +} + +int c_Str_chr(const char *s, int i, int j, int c) { + convert(s, i, j); + for ( ; i < j; i++) + if (s[i] == c) + return i + 1; + return 0; +} + +int c_Str_rchr(const char *s, int i, int j, int c) { + convert(s, i, j); + while (j > i) + if (s[--j] == c) + return j + 1; + return 0; +} + +int c_Str_upto(const char *s, int i, int j, + const char *set) { + assert(set); + convert(s, i, j); + for ( ; i < j; i++) + if (strchr(set, s[i])) + return i + 1; + return 0; +} + +int c_Str_rupto(const char *s, int i, int j, + const char *set) { + assert(set); + convert(s, i, j); + while (j > i) + if (strchr(set, s[--j])) + return j + 1; + return 0; +} + +int c_Str_find(const char *s, int i, int j, + const char *str) { + convert(s, i, j); + assert(str); + const int len = (int)strlen(str); + if (len == 0) + return i + 1; + else if (len == 1) { + for ( ; i < j; i++) + if (s[i] == *str) + return i + 1; + } else + for ( ; i + len <= j; i++) + if ((strncmp(&s[i], str, len) == 0)) + return i + 1; + return 0; +} + +int c_Str_rfind(const char *s, int i, int j, const char *str) { + convert(s, i, j); + assert(str); + const int len =(int)strlen(str); + if (len == 0) + return j + 1; + else if (len == 1) { + while (j > i) + if (s[--j] == *str) + return j + 1; + } else + for ( ; j - len >= i; j--) + if (strncmp(&s[j-len], str, len) == 0) + return j - len + 1; + return 0; +} + +int c_Str_any(const char *s, int i, const char *set) { + assert(s); + assert(set); + const int len =(int)strlen(s); + i = idx(i, len); + assert(i >= 0 && i <= len); + if (i < len && strchr(set, s[i])) + return i + 2; + return 0; +} +int c_Str_many(const char *s, int i, int j, + const char *set) { + assert(set); + convert(s, i, j); + if (i < j && strchr(set, s[i])) { + do + i++; + while (i < j && strchr(set, s[i])); + return i + 1; + } + return 0; +} + +int c_Str_rmany(const char *s, int i, int j, + const char *set) { + assert(set); + convert(s, i, j); + if (j > i && strchr(set, s[j-1])) { + do + --j; + while (j >= i && strchr(set, s[j])); + return j + 2; + } + return 0; +} + +int c_Str_match(const char *s, int i, int j, const char *str) { + convert(s, i, j); + assert(str); + const int len =(int)strlen(str); + if (len == 0) + return i + 1; + else if (len == 1) { + if (i < j && s[i] == *str) + return i + 2; + } else if (i + len <= j && (strncmp(&s[i], str, len) == 0)) + return i + len + 1; + return 0; +} + +int c_Str_rmatch(const char *s, int i, int j, const char *str) { + convert(s, i, j); + assert(str); + const int len =(int)strlen(str); + if (len == 0) + return j + 1; + else if (len == 1) { + if (j > i && s[j-1] == *str) + return j; + } else if (j - len >= i + && strncmp(&s[j-len], str, len) == 0) + return j - len + 1; + return 0; +} + +void c_Str_fmt(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision) { + assert(box && flags); + char *s = va_arg(box->ap, char *); + int i = va_arg(box->ap, int); + int j = va_arg(box->ap, int); + convert(s, i, j); + c_Fmt_puts(s + i, j - i, put, cl, flags, + width, precision); +} + +void c_Str_free(char* s) { + C_FREE(s); +} + diff --git a/Foundation/c_Str.h b/Foundation/c_Str.h new file mode 100644 index 0000000..27bf215 --- /dev/null +++ b/Foundation/c_Str.h @@ -0,0 +1,49 @@ +#ifndef INCLUDED_C_STR_H +#define INCLUDED_C_STR_H + +#ifndef INCLUDED_C_FMT_H +#include +#endif /*INCLUDED_C_FMT_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +char *c_Str_sub(const char *s, int i, int j); +char *c_Str_dup(const char *s, int i, int j, int n); +char *c_Str_cat(const char *s1, int i1, int j1, + const char *s2, int i2, int j2); +char *c_Str_catv (const char *s, ...); +char *c_Str_reverse(const char *s, int i, int j); +char *c_Str_map (const char *s, int i, int j, + const char *from, const char *to); +void c_Str_free(char* s); +int c_Str_pos(const char *s, int i); +int c_Str_len(const char *s, int i, int j); +int c_Str_cmp(const char *s1, int i1, int j1, + const char *s2, int i2, int j2); +int c_Str_chr (const char *s, int i, int j, int c); +int c_Str_rchr (const char *s, int i, int j, int c); +int c_Str_upto (const char *s, int i, int j, + const char *set); +int c_Str_rupto(const char *s, int i, int j, + const char *set); +int c_Str_find (const char *s, int i, int j, + const char *str); +int c_Str_rfind(const char *s, int i, int j, + const char *str); +int c_Str_any (const char *s, int i, + const char *set); +int c_Str_many (const char *s, int i, int j, + const char *set); +int c_Str_rmany (const char *s, int i, int j, + const char *set); +int c_Str_match (const char *s, int i, int j, + const char *str); +int c_Str_rmatch(const char *s, int i, int j, + const char *str); +void c_Str_fmt(int code, va_list_box *box, + int put(int c, void *cl), void *cl, + unsigned char flags[], int width, int precision); + +#endif /*INCLUDED_C_STR_H*/ diff --git a/Foundation/c_Str.t.c b/Foundation/c_Str.t.c new file mode 100644 index 0000000..f11a835 --- /dev/null +++ b/Foundation/c_Str.t.c @@ -0,0 +1,187 @@ +#include + +#include "c_Str.h" +#include +#include +const char *test_str = "abcdefg"; // 長度為 7 + +char test_output_buffer[128]; +int buf_idx = 0; + +int test_put(int c, void *cl) { + if (buf_idx < 127) { + test_output_buffer[buf_idx++] = (char)c; + } + return c; +} + +// Global print wrapper to test formatting execution +void test_printf(const char *fmt, ...) { + va_list_box ap; + va_start(ap.ap, fmt); + c_Fmt_vfmt(test_put, NULL, fmt, &ap); + va_end(ap.ap); +} + +void test_log(const char* name) { + printf("[PASS] %s\n", name); +} + + +int main() { + printf("==================================================\n"); + printf(" 開始執行 c_Str 核心功能組件 最終整合單元測試\n"); + printf("==================================================\n\n"); + + char *res_str = NULL; + + // 1. 測試 c_Str_pos + // test_str = "abcdefg" (len=7) + // 1 -> index 0 -> 位置 1 + // 0 -> 尾端倒數 (0+7) = index 7 -> 位置 8 + // -1 -> 尾端倒數 (-1+7) = index 6 -> 位置 7 + assert(c_Str_pos(test_str, 1) == 1); + assert(c_Str_pos(test_str, 0) == 8); + assert(c_Str_pos(test_str, -1) == 7); + test_log("c_Str_pos (1-based 位置計算驗證)"); + + // 2. 測試 c_Str_len + // 從 1 (開頭) 到 0 (結尾) -> 全長 7 + // 從 2 ('b') 到 5 ('e' 後方) -> 5 - 1 = 4 ('bcde') + assert(c_Str_len(test_str, 1, 0) == 7); + assert(c_Str_len(test_str, 2, 5) == 3); + test_log("c_Str_len (區間長度計算驗證)"); + + // 3. 測試 c_Str_sub + res_str = c_Str_sub(test_str, 2, 5); // 提取 'bcd' + assert(strcmp(res_str, "bcd") == 0); + c_Str_free(res_str); + test_log("c_Str_sub (子字串提取驗證)"); + + // 4. 測試 c_Str_dup + res_str = c_Str_dup(test_str, 2, 4, 3); // 'bc' 重複 3 次 + assert(strcmp(res_str, "bcbcbc") == 0); + c_Str_free(res_str); + test_log("c_Str_dup (子字串重複複製驗證)"); + + // 5. 測試 c_Str_reverse + res_str = c_Str_reverse(test_str, 2, 5); // 'bcd' 反轉 -> 'dcb' + assert(strcmp(res_str, "dcb") == 0); + c_Str_free(res_str); + test_log("c_Str_reverse (子字串反轉驗證)"); + + // 6. 測試 c_Str_cat + res_str = c_Str_cat("XYZ", 1, 0, "123", 1, 3); // "XYZ" + "12" -> "XYZ12" + assert(strcmp(res_str, "XYZ12") == 0); + c_Str_free(res_str); + test_log("c_Str_cat (雙字串區間拼接驗證)"); + + // 7. 測試 c_Str_catv (多字串可變參數拼接,必須以 NULL 結尾) + res_str = c_Str_catv("ABC", 1, 0, "XYZ", 2, 4, (char*)NULL); // "ABC" + "YZ" + assert(strcmp(res_str, "ABCYZ") == 0); + c_Str_free(res_str); + test_log("c_Str_catv (多參數變長字串拼接驗證)"); + + // 8. 測試 c_Str_map (字元映射/置換) + // 初始化對照表:將 'b' 換成 'X','d' 換成 'Y' + c_Str_map(NULL, 0, 0, "bd", "XY"); + res_str = c_Str_map(test_str, 1, 0, NULL, NULL); // "abcdefg" -> "aXcYe f g" + assert(strcmp(res_str, "aXcYefg") == 0); + c_Str_free(res_str); + test_log("c_Str_map (字元對照表置換驗證)"); + + // 9. 測試 c_Str_cmp + // "abcdefg" 區間 [2,4] 是 "bc";"abc" 區間 [2,4] 是 "bc" -> 相等 (0) + assert(c_Str_cmp(test_str, 2, 4, "abc", 2, 4) == 0); + // "bc" 與 "bcd" 比對 -> 前者較短且完全匹配,預期回傳 -1 + assert(c_Str_cmp(test_str, 2, 4, "abcdefg", 2, 5) == -1); + test_log("c_Str_cmp (區間字串深度比對驗證)"); + + // 10. 測試 c_Str_chr (正向尋找字元,回傳 1-based 位置) + // 在 "abcdefg" 中找 'c' -> 位於 index 2 -> 回傳 3 + assert(c_Str_chr(test_str, 1, 0, 'c') == 3); + assert(c_Str_chr(test_str, 1, 0, 'z') == 0); // 找不到 + test_log("c_Str_chr (正向字元查找位置驗證)"); + + // 11. 測試 c_Str_rchr (反向尋找字元) + // 在 "abcdecd" 中找 'c' + assert(c_Str_rchr("abcdecd", 1, 0, 'c') == 6); + test_log("c_Str_rchr (反向字元查找位置驗證)"); + + // 12. 測試 c_Str_upto (正向尋找集合中任一字元首次出現位置) + // "abcdefg" 中尋找 "xyz" 或 "d" -> 'd' 最先被匹配 (index 3) -> 回傳 4 + assert(c_Str_upto(test_str, 1, 0, "xyz d") == 4); + test_log("c_Str_upto (字元集合正向切分點驗證)"); + + // 13. 測試 c_Str_rupto (反向尋找集合中任一字元首次出現位置) + // "abcdefg" 中反向找 "ab" -> 'b' 最先被找到 (index 1) -> 回傳 2 + assert(c_Str_rupto(test_str, 1, 0, "ab") == 2); + test_log("c_Str_rupto (字元集合反向切分點驗證)"); + + // 14. 測試 c_Str_find (正向尋找子字串) + // "abcdefg" 中找 "cde" -> 開始於 index 2 -> 回傳 3 + assert(c_Str_find(test_str, 1, 0, "cde") == 3); + test_log("c_Str_find (正向子字串搜尋匹配驗證)"); + + // 15. 測試 c_Str_rfind (反向尋找子字串) + // "ababax" 中反向找 "aba" -> 應匹配到 index 2 開始的 "aba" -> 回傳 3 + assert(c_Str_rfind("ababax", 1, 0, "aba") == 3); + test_log("c_Str_rfind (反向子字串搜尋匹配驗證)"); + + // 16. 測試 c_Str_any (檢查指定 index 的單一字元是否在集合中) + // test_str="abcdefg", i=3 -> index 2 ('c')。'c' 有在 "cba" 之中 -> 回傳 index+2 = 4 + assert(c_Str_any(test_str, 3, "cba") == 4); + assert(c_Str_any(test_str, 3, "xyz") == 0); // 'c' 不在 xyz 中 + test_log("c_Str_any (特定點字元集命中檢查驗證)"); + + // 17. 測試 c_Str_many (從指定起點向後匹配連續屬於集合的字元,直到不屬於為止) + // "aaabX" 從 1 開始,連續符合 "abc" 的有 'a','a','a','b' (4個) -> 停止於 index 4 -> 回傳 4+1 = 5 + assert(c_Str_many("aaabX", 1, 0, "abc") == 5); + test_log("c_Str_many (正向連續字元集跨越範圍驗證)"); + + // 18. 測試 c_Str_rmany (從指定終點向前匹配連續屬於集合的字元) + // "Xbbba" 從 0(結尾) 向前,符合 "abc" 的有 'a','b','b','b' (4個) -> 停止於 index 0 ('X') -> 回傳 index+2 = 2 + assert(c_Str_rmany("Xbbba", 1, 0, "abc") == 2); + test_log("c_Str_rmany (反向連續字元集跨越範圍驗證)"); + + // 19. 測試 c_Str_match (檢查指定起點是否「精準開頭匹配」該子字串) + // "abcdefg" 在位置 3 (index 2, 'c') 是否精準匹配 "cde" -> 是,符合長度 3 -> 回傳 index+len+1 = 2+3+1 = 6 + assert(c_Str_match(test_str, 3, 0, "cde") == 6); + assert(c_Str_match(test_str, 3, 0, "xyz") == 0); + test_log("c_Str_match (指定起點子字串精準頭匹配驗證)"); + + // 20. 測試 c_Str_rmatch (檢查指定終點是否「精準結尾匹配」該子字串) + // "abcde" 在 0(結尾, index 5) 向前看是否精準匹配 "cde" (len=3) -> index 5-3=2 開始是 "cde" -> 成功,回傳 2+1 = 3 + assert(c_Str_rmatch("abcde", 1, 0, "cde") == 3); + test_log("c_Str_rmatch (指定終點子字串精準尾匹配驗證)"); + + // 21. 測試 c_Str_fmt (驗證自訂格式化回呼整合) +#if 0 + unsigned char flags[256] = { 0 }; + mock_put_count = 0; + // 傳入字串 "hello", 起點 2 ('e'), 終點 5 ('o' 後方) -> 預期提取出 "ell" + run_str_fmt_test(0, flags, 0, 0, "hello", 2, 5); + assert(mock_put_count == 3); // "ell" 長度為 3,應呼叫 3 次 put + test_log("c_Str_fmt (格式化輸出器區間提取回呼驗證)"); +#endif + + memset(test_output_buffer, 0, sizeof(test_output_buffer)); + buf_idx = 0; + c_Fmt_t old_handler = c_Fmt_register('S', c_Str_fmt); + assert(old_handler == NULL); + printf("[PASS] c_Str_fmt successfully registered to specifier character 'S'.\n"); + + const char* sample_sentence = "the c-container-library platform"; + test_printf("Extracted Token: [%S]", sample_sentence, 5, 16); + test_output_buffer[buf_idx] = '\0'; + + printf(" Rendered Output String: %s\n", test_output_buffer); + assert(strcmp(test_output_buffer, "Extracted Token: [c-container]") == 0); + printf("[PASS] Core Fmt engine correctly processed parameters via c_Str_fmt.\n"); + + printf("\n==================================================\n"); + printf(" 恭喜!c_Str 基礎工具類共計 22 個核心介面全數單元測試通過!\n"); + printf("==================================================\n"); + + return 0; +} \ No newline at end of file diff --git a/Foundation/c_StrIndexKmp.c b/Foundation/c_StrIndexKmp.c new file mode 100644 index 0000000..73e57d7 --- /dev/null +++ b/Foundation/c_StrIndexKmp.c @@ -0,0 +1,76 @@ +#include +#include + +#define MAX_STACK_LPS 128 + +static void compute_LPS_table(const char* pattern, c_size_t m, long long* lps) { + c_size_t len = 0; // Length of the previous longest prefix suffix + lps[0] = 0; // lps[0] is always 0 + c_size_t i = 1; + + while (i < m) { + if (pattern[i] == pattern[len]) { + len++; + lps[i] = (long long)len; + i++; + } else { + if (len != 0) { + len = (c_size_t)lps[len - 1]; // Backtrack without shifting 'i' + } else { + lps[i] = 0; + i++; + } + } + } +} + +long long c_StrIndexKmp(const char* text, const char* pattern) { + if (!text || !pattern) return -1; + + const c_size_t n = strlen(text); + const c_size_t m = strlen(pattern); + + if (m == 0) return 0; // An empty pattern matches at the very beginning + if (m > n) return -1; // Pattern is longer than the text container + + // Optimization: Use a stack buffer if the pattern length fits, avoiding heap allocation cycles + long long stack_lps[MAX_STACK_LPS]; + long long* lps = (m <= MAX_STACK_LPS) ? stack_lps : (long long*)C_ALLOC(m * sizeof(long long)); + if (!lps) return -1; // Allocation failure fallback + + // Step 1: Precompute the lookup table + compute_LPS_table(pattern, m, lps); + + // Step 2: Linear string matching phase + c_size_t i = 0; // Index for text + c_size_t j = 0; // Index for pattern + long long matched_index = -1; + + while (i < n) { + if (pattern[j] == text[i]) { + i++; + j++; + } + + if (j == m) { + matched_index = (long long)(i - j); // Found match at index (i - j) + break; // Terminate early for the first occurrence + } + // Mismatch after j matches + else if (i < n && pattern[j] != text[i]) { + if (j != 0) { + j = (size_t)lps[j - 1]; // Slide the pattern using the precomputed LPS table + } else { + i++; + } + } + } + + // Clean up heap memory if it was allocated + if (lps != stack_lps) { + C_FREE(lps); + } + + return matched_index; +} + diff --git a/Foundation/c_StrIndexKmp.h b/Foundation/c_StrIndexKmp.h new file mode 100644 index 0000000..bc489ae --- /dev/null +++ b/Foundation/c_StrIndexKmp.h @@ -0,0 +1,13 @@ +#ifndef INCLUDED_C_STRINDEXKMP_H +#define INCLUDED_C_STRINDEXKMP_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +long long c_StrIndexKmp(const char* text, const char* pattern); + +#endif /*INCLUDED_C_STRINDEXKMP_H*/ diff --git a/Foundation/c_StrIndexKmp.t.c b/Foundation/c_StrIndexKmp.t.c new file mode 100644 index 0000000..56da8be --- /dev/null +++ b/Foundation/c_StrIndexKmp.t.c @@ -0,0 +1,49 @@ +#include + +#include "c_StrIndexKmp.h" +#include +#include + +void test_log(const char* test_name) { + printf("[PASS] %s\n", test_name); +} + +int main() { + printf("==================================================\n"); + printf(" Starting KMP String Search Algorithm Unit Tests\n"); + printf("==================================================\n\n"); + + // Test 1: Standard match discovery + const char* text1 = "ABABDABACDABABCABAB"; + long long idx1 = c_StrIndexKmp(text1, "ABABCABAB"); + assert(idx1 == 10); + test_log("1. Pattern found at index 10 successfully"); + + // Test 2: Repeating partial pattern fallback (Corrected text and pattern) + // At index 0, text matches "ABABA" but mismatches on the 6th char ('B' vs 'C'). + // The LPS table causes j to backtrack smoothly, discovering the real match starting at index 2. + const char* text2 = "ABABABACATA"; + long long idx2 = c_StrIndexKmp(text2, "ABABAC"); + assert(idx2 == 2); + test_log("2. Partial-match backtracking table lookup verified at index 2"); + + // Test 3: Pattern not present in text + long long idx3 = c_StrIndexKmp(text1, "XYZ"); + assert(idx3 == -1); + test_log("3. Non-existent substring pattern returns -1 safely"); + + // Test 4: Empty pattern handling boundary check + long long idx4 = c_StrIndexKmp(text1, ""); + assert(idx4 == 0); + test_log("4. Empty string matches target index 0"); + + // Test 5: Pattern longer than string payload boundary check + long long idx5 = c_StrIndexKmp("short", "extremely_long_pattern"); + assert(idx5 == -1); + test_log("5. Length mismatch constraints handled gracefully"); + + printf("\n==================================================\n"); + printf(" Success! All KMP test assertions passed successfully!\n"); + printf("==================================================\n"); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_StrUtil.c b/Foundation/c_StrUtil.c new file mode 100644 index 0000000..0ace9f8 --- /dev/null +++ b/Foundation/c_StrUtil.c @@ -0,0 +1,193 @@ +#include +#include +#include + +#include "c_ArrayList.h" + +// Reverses a string in-place +void c_StrUtil_Reverse(char* str) { + if (!str) return; + const c_size_t len = strlen(str); + if (len <= 1) return; + + c_size_t i = 0; + c_size_t j = len - 1; + while (i < j) { + char temp = str[i]; + str[i] = str[j]; + str[j] = temp; + i++; + j--; + } +} + +// Converts a string to lowercase into a destination buffer +c_err_t c_StrUtil_ToLower(char* out_dest, const char* src, c_size_t dest_capacity) { + if (!out_dest || !src || dest_capacity == 0) return C_ERR_PARAM; + + c_size_t i = 0; + while (src[i] != '\0') { + if (i >= dest_capacity - 1) { + out_dest[i] = '\0'; + return C_ERR_FAIL; + } + out_dest[i] = (char)tolower((unsigned char)src[i]); + i++; + } + out_dest[i] = '\0'; + return C_ERR_OK; +} + +// Converts a string to uppercase into a destination buffer +c_err_t c_StrUtil_ToUpper(char* out_dest, const char* src, c_size_t dest_capacity) { + if (!out_dest || !src || dest_capacity == 0) return C_ERR_PARAM; + + c_size_t i = 0; + while (src[i] != '\0') { + if (i >= dest_capacity - 1) { + out_dest[i] = '\0'; + return C_ERR_FAIL; + } + out_dest[i] = (char)toupper((unsigned char)src[i]); + i++; + } + out_dest[i] = '\0'; + return C_ERR_OK; +} + +// Removes leading and trailing whitespaces into a destination buffer +c_err_t c_StrUtil_Trim(char* out_dest, const char* src, c_size_t dest_capacity) { + if (!out_dest || !src || dest_capacity == 0) return C_ERR_PARAM; + + // Find first non-whitespace character + while (*src && isspace((unsigned char)*src)) { + src++; + } + + // Find the end of the string + c_size_t len = strlen(src); + while (len > 0 && isspace((unsigned char)src[len - 1])) { + len--; + } + + if (len >= dest_capacity) { + return C_ERR_FAIL; + } + + // Copy trimmed substring + memmove(out_dest, src, len); + out_dest[len] = '\0'; + return C_ERR_OK; +} + +c_bool_t c_StrUtil_StartsWith(const char* str, const char* prefix) { + if (!str || !prefix) return C_FALSE; + return strncmp(str, prefix, strlen(prefix)) == 0 ? C_TRUE : C_FALSE; +} + +c_bool_t c_StrUtil_EndsWith(const char* str, const char* suffix) { + if (!str || !suffix) return C_FALSE; + c_size_t str_len = strlen(str); + c_size_t suf_len = strlen(suffix); + if (suf_len > str_len) return C_FALSE; + return strcmp(str + str_len - suf_len, suffix) == 0 ? C_TRUE : C_FALSE; +} + +// Safely extracts a substring with full bounds verification +c_err_t c_StrUtil_Substring(char* out_dest, const char* src, c_size_t start, c_size_t len, c_size_t dest_capacity) { + if (!out_dest || !src || dest_capacity == 0) return C_ERR_PARAM; + + c_size_t src_len = strlen(src); + if (start > src_len) return C_ERR_OUTOFBOUND; + + // Adjust requested length if it overflows source string boundaries + if (start + len > src_len) { + len = src_len - start; + } + + if (len >= dest_capacity) { + return C_ERR_OUTOFBOUND; + } + + memcpy(out_dest, src + start, len); + out_dest[len] = '\0'; + return C_ERR_OK; +} + +// Replaces occurrences of a substring inside a buffer safely +c_err_t c_StrUtil_Replace(char* out_dest, const char* src, const char* find, const char* replace_with, c_size_t dest_capacity) { + if (!out_dest || !src || !find || !replace_with || dest_capacity == 0) return C_ERR_PARAM; + + c_size_t find_len = strlen(find); + c_size_t replace_len = strlen(replace_with); + c_size_t dest_len = 0; + + if (find_len == 0) return C_ERR_PARAM; + + while (*src) { + // If match found, inject replacement string + if (strncmp(src, find, find_len) == 0) { + if (dest_len + replace_len >= dest_capacity) { + out_dest[dest_len] = '\0'; + return C_ERR_OUTOFBOUND; + } + strcpy(out_dest + dest_len, replace_with); + dest_len += replace_len; + src += find_len; + } else { + // Otherwise, inject individual source character + if (dest_len + 1 >= dest_capacity) { + out_dest[dest_len] = '\0'; + return C_ERR_OUTOFBOUND; + } + out_dest[dest_len] = *src; + dest_len++; + src++; + } + } + out_dest[dest_len] = '\0'; + return C_ERR_OK; +} + +c_err_t c_StrUtil_Split(c_ArrayList_t* out_list, const char* src, const char* delimiter) { + if (!out_list || !src || !delimiter) return C_ERR_PARAM; + if (out_list->obj_size != sizeof(c_StrToken_t)) return C_ERR_PARAM; // Type safety assert + + const c_size_t delim_len = strlen(delimiter); + if (delim_len == 0) return C_ERR_PARAM; + + const char* current = src; + const char* next_match; + + while ((next_match = strstr(current, delimiter)) != NULL) { + size_t token_len = next_match - current; + + if (token_len > 0) { + c_StrToken_t new_token; + // Bound checking to ensure long strings don't cause buffer overflows + size_t copy_len = (token_len >= C_STR_TOKEN_MAX_LEN) ? (C_STR_TOKEN_MAX_LEN - 1) : token_len; + + memcpy(new_token.text, current, copy_len); + new_token.text[copy_len] = '\0'; + + // Deep-copy token structure payload straight into the dynamic ArrayList + c_ArrayList_Add(out_list, &new_token); + } + current = next_match + delim_len; + } + + // Capture the final token remaining after the last delimiter match + if (*current != '\0') { + c_StrToken_t new_token; + size_t token_len = strlen(current); + size_t copy_len = (token_len >= C_STR_TOKEN_MAX_LEN) ? (C_STR_TOKEN_MAX_LEN - 1) : token_len; + + memcpy(new_token.text, current, copy_len); + new_token.text[copy_len] = '\0'; + + c_ArrayList_Add(out_list, &new_token); + } + + return C_ERR_OK; +} + diff --git a/Foundation/c_StrUtil.h b/Foundation/c_StrUtil.h new file mode 100644 index 0000000..50acece --- /dev/null +++ b/Foundation/c_StrUtil.h @@ -0,0 +1,47 @@ +#ifndef INCLUDED_C_STRUTIL_H +#define INCLUDED_C_STRUTIL_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_C_ARRAYLIST_H +#include +#endif /*INCLUDED_C_ARRAYLIST_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#ifndef C_STR_TOKEN_MAX_LEN +#define C_STR_TOKEN_MAX_LEN 64 +#endif + + +typedef struct { + char text[C_STR_TOKEN_MAX_LEN]; +} c_StrToken_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +void c_StrUtil_Reverse(char* str); +c_err_t c_StrUtil_ToLower(char* out_dest, const char* src, c_size_t dest_capacity); +c_err_t c_StrUtil_ToUpper(char* out_dest, const char* src, c_size_t dest_capacity); + +// Trimming whitespace (' ', '\t', '\r', '\n') +c_err_t c_StrUtil_Trim(char* out_dest, const char* src, c_size_t dest_capacity); + +// Search & Matching operations +c_bool_t c_StrUtil_StartsWith(const char* str, const char* prefix); +c_bool_t c_StrUtil_EndsWith(const char* str, const char* suffix); + +// Substring extraction (Safe, boundary checked) +c_err_t c_StrUtil_Substring(char* out_dest, const char* src, c_size_t start, c_size_t len, c_size_t dest_capacity); + +// String replacements (Safely handles different length replacements) +c_err_t c_StrUtil_Replace(char* out_dest, const char* src, const char* find, const char* replace_with, c_size_t dest_capacity); + +// Tokenizer that breaks down a source string and appends results to an initialized ArrayList +c_err_t c_StrUtil_Split(c_ArrayList_t* out_list, const char* src, const char* delimiter) ; + +#endif /*INCLUDED_C_STRUTIL_H*/ diff --git a/Foundation/c_StrUtil.t.c b/Foundation/c_StrUtil.t.c new file mode 100644 index 0000000..fea5f21 --- /dev/null +++ b/Foundation/c_StrUtil.t.c @@ -0,0 +1,116 @@ +#include + +#include "c_StrUtil.h" +#include +#include + + +void test_log(const char* test_name) { + printf("[PASS] %s\n", test_name); +} + +static void test_split(void) { + printf("==================================================\n"); + printf(" Starting Tokenizer & ArrayList Integration Tests\n"); + printf("==================================================\n\n"); + + // Initialize your generic ArrayList to accept your structured token elements + c_ArrayList_t token_list; + c_err_t err = c_ArrayList_Init(&token_list, sizeof(c_StrToken_t), 4); + assert(err == C_ERR_OK); + + const char* csv_data = "GND,VCC,TX_PIN,RX_PIN,SPI_CLK"; + + // Split the comma-delimited configuration values + err = c_StrUtil_Split(&token_list, csv_data, ","); + assert(err == C_ERR_OK); + + // 1. Validate parsed sizing counts matching expected segmentations + assert(token_list.size == 5); + test_log("1. String segmented into 5 individual structural elements"); + + // 2. Sequential range checking verifying index preservation + c_StrToken_t* t_ptr; + + t_ptr = (c_StrToken_t*)c_ArrayList_Get(&token_list, 0); + assert(strcmp(t_ptr->text, "GND") == 0); + + t_ptr = (c_StrToken_t*)c_ArrayList_Get(&token_list, 2); + assert(strcmp(t_ptr->text, "TX_PIN") == 0); + + t_ptr = (c_StrToken_t*)c_ArrayList_Get(&token_list, 4); + assert(strcmp(t_ptr->text, "SPI_CLK") == 0); + test_log("2. Target indices verify correct sub-string capture sequence"); + + // 3. Clear container lifecycle properties cleanly + c_ArrayList_Destroy(&token_list); + test_log("3. Array container memory teardown success"); + + printf("\n==================================================\n"); + printf(" Success! Token parsing and container layers fit perfectly!\n"); + printf("==================================================\n"); +} + +int main() { + printf("==================================================\n"); + printf(" Starting C String Utility Unit Tests\n"); + printf("==================================================\n\n"); + + char buffer[128]; + c_err_t err; + + // 1. Test Reverse + strcpy(buffer, "A man a plan a canal Panama"); + c_StrUtil_Reverse(buffer); + assert(strcmp(buffer, "amanaP lanac a nalp a nam A") == 0); + test_log("1. In-place string reversal"); + + // 2. Test Case Conversions + err = c_StrUtil_ToLower(buffer, "C_Language_123!", sizeof(buffer)); + assert(err == C_ERR_OK); + assert(strcmp(buffer, "c_language_123!") == 0); + + err = c_StrUtil_ToUpper(buffer, "embedded c", sizeof(buffer)); + assert(err == C_ERR_OK); + assert(strcmp(buffer, "EMBEDDED C") == 0); + test_log("2. Casing conversions"); + + // 3. Test Trim + err = c_StrUtil_Trim(buffer, " \t Hello World \r\n ", sizeof(buffer)); + assert(err == C_ERR_OK); + assert(strcmp(buffer, "Hello World") == 0); + test_log("3. Whitespace trimming"); + + // 4. Test Starts/Ends With + assert(c_StrUtil_StartsWith("libcontainer.so", "lib") == C_TRUE); + assert(c_StrUtil_StartsWith("libcontainer.so", "bin") == C_FALSE); + assert(c_StrUtil_EndsWith("document.txt", ".txt") == C_TRUE); + assert(c_StrUtil_EndsWith("document.txt", ".pdf") == C_FALSE); + test_log("4. Prefix/Suffix conditional checks"); + + // 5. Test Substring + err = c_StrUtil_Substring(buffer, "Microcontroller", 5, 7, sizeof(buffer)); + assert(err == C_ERR_OK); + assert(strcmp(buffer, "control") == 0); + test_log("5. Substring range isolation"); + + // 6. Test Replace + err = c_StrUtil_Replace(buffer, "The quick brown fox jumps over the lazy dog", "quick brown fox", "slow green turtle", sizeof(buffer)); + assert(err == C_ERR_OK); + assert(strcmp(buffer, "The slow green turtle jumps over the lazy dog") == 0); + test_log("6. Substring template injection matching"); + + // 7. Overflow Protection + char small_buffer[6]; + err = c_StrUtil_ToUpper(small_buffer, "OVERFLOW_TEST", sizeof(small_buffer)); + assert(err == C_ERR_FAIL); + assert(strlen(small_buffer) == 5); // Ensure safe null truncation occurred + test_log("7. Buffer overflow bounds interception"); + + printf("\n==================================================\n"); + printf(" Success! String utilities match defensive safety layouts!\n"); + printf("==================================================\n"); + + test_split(); + return 0; +} \ No newline at end of file diff --git a/Foundation/c_StringBuffer.c b/Foundation/c_StringBuffer.c new file mode 100644 index 0000000..ab7641b --- /dev/null +++ b/Foundation/c_StringBuffer.c @@ -0,0 +1,955 @@ +#include +#include +#include +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define DEFAULT_INIT_CAPACITY 16 +#define GROWTH_FACTOR 2 + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +c_err_t c_StringBuffer_EnsureCapacity(c_StringBuffer_t* self, c_size_t required_len) { + c_size_t needed_capacity = self->size + required_len + 1; // +1 for trailing '\0' + if (needed_capacity <= self->capacity) { + return C_ERR_OK; + } + + c_size_t new_capacity = self->capacity == 0 ? DEFAULT_INIT_CAPACITY : self->capacity; + while (new_capacity < needed_capacity) { + new_capacity *= GROWTH_FACTOR; // Exponential doubling strategy + } + + char* new_buffer = (char*)C_ALLOC(new_capacity); + if (!new_buffer) { + return C_ERR_NOMEM; + } + + if (self->buffer && self->size > 0) { + memcpy(new_buffer, self->buffer, self->size); + } + new_buffer[self->size] = '\0'; + + C_FREE(self->buffer); + self->buffer = new_buffer; + self->capacity = new_capacity; + + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_StringBuffer_Init(c_StringBuffer_t* self, c_size_t capacity) { + if (!self) return C_ERR_PARAM; + + self->size = 0; + self->capacity = capacity > 0 ? capacity : DEFAULT_INIT_CAPACITY; // Enforce minimum initial allocation + self->buffer = (char*)C_ALLOC(self->capacity); + if (!self->buffer) { + self->capacity = 0; + return C_ERR_NOMEM; + } + self->buffer[0] = '\0'; + + return C_ERR_OK; +} + +void c_StringBuffer_Destroy(c_StringBuffer_t* self) { + if (!self) return; + if (self->buffer) { + C_FREE(self->buffer); + } + self->size = 0; + self->capacity = 0; +} + +c_err_t c_StringBuffer_Append(c_StringBuffer_t* self, const char* string, c_size_t length) { + if (!self || !self->buffer || !string || length == 0) return C_ERR_PARAM; + + c_err_t err = c_StringBuffer_EnsureCapacity(self, length); + if (err != C_ERR_OK) return err; + + memcpy(self->buffer + self->size, string, length); + self->size += length; + self->buffer[self->size] = '\0'; + + return C_ERR_OK; +} + +c_err_t c_StringBuffer_Prepend(c_StringBuffer_t* self, const char* string, c_size_t length) { + return c_StringBuffer_InsertAt(self, 0, string, length); +} + +c_err_t c_StringBuffer_InsertAt(c_StringBuffer_t* self, c_size_t index, const char* string, c_size_t length) { + if (!self || !self->buffer || !string || length == 0) return C_ERR_PARAM; + if (index > self->size) return C_ERR_OUTOFBOUND; + + c_err_t err = c_StringBuffer_EnsureCapacity(self, length); + if (err != C_ERR_OK) return err; + + // Shift memory to the right using memmove to prevent overlapping issues + memmove(self->buffer + index + length, self->buffer + index, self->size - index); + memcpy(self->buffer + index, string, length); + + self->size += length; + self->buffer[self->size] = '\0'; + + return C_ERR_OK; +} + +c_err_t c_StringBuffer_RemoveAt(c_StringBuffer_t* self, c_size_t index, c_size_t length) { + if (!self || !self->buffer) return C_ERR_PARAM; + if (index >= self->size) return C_ERR_OUTOFBOUND; + if (length ==0) return C_SUCCESS; + + // Clamp length if it attempts to read past the end of the current buffer + if (index + length > self->size) { + length = self->size - index; + } + + // Shift trailing memory to the left to close the character gap + memmove(self->buffer + index, self->buffer + index + length, self->size - (index + length)); + self->size -= length; + self->buffer[self->size] = '\0'; + + return C_ERR_OK; +} + +void c_StringBuffer_Clear(c_StringBuffer_t* self) { + if (!self || !self->buffer) return; + self->size = 0; + self->buffer[0] = '\0'; +} + +/* --- Explicit String-Wrapper Interfaces --- */ + +c_err_t c_StringBuffer_AppendStr(c_StringBuffer_t* self, const char* string) { + if (!string) return C_ERR_PARAM; + return c_StringBuffer_Append(self, string, strlen(string)); +} + +c_err_t c_StringBuffer_PrependStr(c_StringBuffer_t* self, const char* string) { + if (!string) return C_ERR_PARAM; + return c_StringBuffer_Prepend(self, string, strlen(string)); +} + +c_err_t c_StringBuffer_InsertStrAt(c_StringBuffer_t* self, const char* string, c_size_t index) { + if (!string) return C_ERR_PARAM; + return c_StringBuffer_InsertAt(self, index, string, strlen(string)); +} + +c_err_t c_StringBuffer_CopyTo(c_StringBuffer_t* self, c_size_t index, c_size_t length, char* buffer, c_size_t buffer_length) { + // 1. Guard against invalid pointers, empty destinations, or index out-of-bounds + if (!self || !self->buffer || !buffer || buffer_length == 0) { + return C_ERR_PARAM; + } + if (index > self->size) { + return C_ERR_OUTOFBOUND; + } + + // 2. Clamp requested copy length if it exceeds the remaining data payload bounds + if (index + length > self->size) { + length = self->size - index; + } + + // 3. Enforce destination buffer capacity threshold checks + // The requested segment requires at least (length + 1) bytes for safe null-termination + if (length >= buffer_length) { + return C_ERR_OUTOFBOUND; // Destination buffer is too small to store the segment safely + } + + // 4. Perform the raw memory copy if there are valid characters to process + if (length > 0) { + memcpy(buffer, self->buffer + index, length); + } + + // 5. Always apply a deterministic trailing null terminator + buffer[length] = '\0'; + + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_StringBuffer_VPrintf(c_StringBuffer_t* self, const char* format, va_list args) { + if (!self || !format) return C_ERR_PARAM; + + // Make a copy of args to measure the required layout length safely + va_list args_copy; + va_copy(args_copy, args); + int formatted_len = vsnprintf(NULL, 0, format, args_copy); + va_end(args_copy); + + if (formatted_len < 0) return C_ERR_PARAM; + if (formatted_len == 0) return C_SUCCESS; + + c_size_t length = (c_size_t)formatted_len; + + c_err_t err = c_StringBuffer_EnsureCapacity(self, length); + if (err != C_SUCCESS) return err; + + // Use the original args list for writing directly into the structure block + vsnprintf(self->buffer + self->size, length + 1, format, args); + + self->size += length; + self->buffer[self->size] = '\0'; + + return C_SUCCESS; +} + +c_err_t c_StringBuffer_VPrintfAt(c_StringBuffer_t* self, c_size_t index, const char* format, va_list args) { + if (!self || !format) return C_ERR_PARAM; + if (index > self->size) return C_ERR_OUTOFBOUND; + + // Measure the length of the new formatted slice + va_list args_copy; + va_copy(args_copy, args); + int formatted_len = vsnprintf(NULL, 0, format, args_copy); + va_end(args_copy); + + if (formatted_len < 0) return C_ERR_PARAM; + if (formatted_len == 0) return C_SUCCESS; + + c_size_t length = (c_size_t)formatted_len; + + c_err_t err = c_StringBuffer_EnsureCapacity(self, length); + if (err != C_SUCCESS) return err; + + // Safely backup the target downstream character that will be stomped by vsnprintf's '\0' + char backup_char = '\0'; + if (index < self->size) { + backup_char = self->buffer[index]; + } + + // Shift the existing string buffer memory forward + memmove(self->buffer + index + length, self->buffer + index, self->size - index); + + // Render formatted string fragments safely into the newly allocated block gap + vsnprintf(self->buffer + index, length + 1, format, args); + + // Overwrite the accidental inner null-terminator using our clean structural backup + if (index < self->size) { + self->buffer[index + length] = backup_char; + } + + self->size += length; + self->buffer[self->size] = '\0'; + + return C_SUCCESS; +} + +c_err_t c_StringBuffer_Printf(c_StringBuffer_t* self, const char* format, ...) { + va_list args; + va_start(args, format); + c_err_t err = c_StringBuffer_VPrintf(self, format, args); + va_end(args); + return err; +} + +c_err_t c_StringBuffer_PrintfAt(c_StringBuffer_t* self, c_size_t index, const char* format, ...) { + va_list args; + va_start(args, format); + c_err_t err = c_StringBuffer_VPrintfAt(self, index, format, args); + va_end(args); + return err; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#include +#include + +c_err_t c_StringBuffer_AppendTimestamp(c_StringBuffer_t* self, const char* format, const struct tm* time_info) { + if (!self || !format || !time_info) return C_ERR_PARAM; + + // Start with a reasonable initial guess for max timestamp length. + // Most standard timestamps (%Y-%m-%d %H:%M:%S) fit in under 32 or 64 bytes. + c_size_t guess_space = 64; + c_err_t err; + + while (1) { + err = c_StringBuffer_EnsureCapacity(self, guess_space); + if (err != C_SUCCESS) return err; + + // strftime writes into the remaining available capacity space. + // self->capacity - self->size calculation leaves room for the null-terminator. + c_size_t max_write = self->capacity - self->size; + size_t written = strftime(self->buffer + self->size, max_write, format, time_info); + + // strftime returns 0 if the string didn't fit into the provided buffer size + if (written == 0) { + // Check if the pattern genuinely produces a 0-length output (like an empty format string "") + if (format[0] == '\0') { + return C_SUCCESS; + } + // Double the guess size space and try again + guess_space *= 2; + + // Put an upper bound sanity check to prevent infinite loops on broken formatting parameters + if (guess_space > 4096) { + return C_ERR_PARAM; + } + continue; + } + + // Success! Advance size tracking variable + self->size += (c_size_t)written; + // strftime automatically guarantees a null terminator at self->buffer[self->size] + break; + } + + return C_SUCCESS; +} + +c_err_t c_StringBuffer_AppendCurrentTimestamp(c_StringBuffer_t* self, const char* format, int use_utc) { + if (!self || !format) return C_ERR_PARAM; + + time_t raw_time = time(NULL); + if (raw_time == (time_t)-1) { + return C_ERR_PARAM; // Failed to retrieve system clock time + } + + struct tm time_struct; + struct tm* time_ptr; + + // Thread-safe structure assembly variants (fallback to standard if platform requires it) + if (use_utc) { +#if defined(_WIN32) || defined(_WIN64) + if (gmtime_s(&time_struct, &raw_time) != 0) return C_ERR_PARAM; + time_ptr = &time_struct; +#else + time_ptr = gmtime_r(&raw_time, &time_struct); +#endif + } else { +#if defined(_WIN32) || defined(_WIN64) + if (localtime_s(&time_struct, &raw_time) != 0) return C_ERR_PARAM; + time_ptr = &time_struct; +#else + time_ptr = localtime_r(&raw_time, &time_struct); +#endif + } + + if (!time_ptr) return C_ERR_PARAM; + + return c_StringBuffer_AppendTimestamp(self, format, time_ptr); +} + + +c_err_t c_StringBuffer_InsertTimestampAt(c_StringBuffer_t* self, c_size_t index, const char* format, const struct tm* time_info) { + if (!self || !format || !time_info) return C_ERR_PARAM; + if (index > self->size) return C_ERR_OUTOFBOUND; + + // Use a conservative local stack frame memory allocation. + // Standard timestamp strings comfortably fit within 128 bytes. + char temp_stack_buffer[128]; + char* target_buffer = temp_stack_buffer; + c_size_t allocated_size = sizeof(temp_stack_buffer); + c_size_t final_len = 0; + c_err_t result = C_SUCCESS; + + while (1) { + size_t written = strftime(target_buffer, allocated_size, format, time_info); + + if (written == 0) { + // Check if the format string pattern is intentionally empty "" + if (format[0] == '\0') { + final_len = 0; + break; + } + + // If the timestamp string didn't fit, scale up the workspace dynamically on the heap + c_size_t new_allocated_size = allocated_size * 2; + + // Loop sanity guard limit to prevent infinite allocations on bad layout configurations + if (new_allocated_size > 4096) { + if (target_buffer != temp_stack_buffer) { + free(target_buffer); + } + return C_ERR_PARAM; + } + + char* new_buffer = (target_buffer == temp_stack_buffer) + ? (char*)malloc(new_allocated_size) + : (char*)realloc(target_buffer, new_allocated_size); + + if (!new_buffer) { + if (target_buffer != temp_stack_buffer) { + free(target_buffer); + } + return C_ERR_NOMEM; + } + + // Copy data over if migrating from stack array block allocation initially + if (target_buffer == temp_stack_buffer) { + // No need to copy old data because strftime failed completely anyway + } + + target_buffer = new_buffer; + allocated_size = new_allocated_size; + continue; + } + + final_len = (c_size_t)written; + break; + } + + // Call your existing InsertAt implementation to open the gap and safely shift the array characters downstream + if (final_len > 0) { + result = c_StringBuffer_InsertAt(self, index, target_buffer, final_len); + } + + // Clean up heap space allocations if we outgrew the default 128-byte stack array footprint + if (target_buffer != temp_stack_buffer) { + free(target_buffer); + } + + return result; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_index_t c_StringBuffer_IndexOfStr(c_StringBuffer_t* self, c_size_t start_index, const char* substr) { + if (!self || !self->buffer || !substr) return C_ERR_FAIL; + if (start_index >= self->size) return C_ERR_FAIL; + + // Utilize optimized standard strstr starting from our targeted index offset + char* match = strstr(self->buffer + start_index, substr); + if (!match) return C_ERR_FAIL; + + return (c_index_t)(match - self->buffer); +} + +c_index_t c_StringBuffer_IndexOfChar(c_StringBuffer_t* self, c_size_t start_index, char target) { + if (!self || !self->buffer) return C_ERR_FAIL; + if (start_index >= self->size) return C_ERR_FAIL; + + // memchr is highly optimized by compilers using SIMD assembly operations under the hood + c_size_t search_len = self->size - start_index; + char* match = (char*)memchr(self->buffer + start_index, target, search_len); + if (!match) return C_ERR_FAIL; + + return (c_index_t)(match - self->buffer); +} + +c_index_t c_StringBuffer_LastIndexOfStr(c_StringBuffer_t* self, c_size_t start_index, const char* substr) { + if (!self || !self->buffer || !substr) return C_ERR_FAIL; + + c_size_t sub_len = strlen(substr); + if (sub_len == 0) return C_ERR_FAIL; + + // Clamp start_index to structural string boundary maximums + c_size_t upper_bound = (start_index >= self->size) ? (self->size == 0 ? 0 : self->size - 1) : start_index; + if (upper_bound < sub_len - 1) return C_ERR_FAIL; + + // Scan backwards sequentially to find the last occurrence match context + for (c_size_t i = upper_bound + 1 - sub_len; ; i--) { + if (strncmp(self->buffer + i, substr, sub_len) == 0) { + return (c_index_t)i; + } + if (i == 0) break; // Terminate condition for unsigned down-counting loops + } + + return C_ERR_FAIL; +} + +c_index_t c_StringBuffer_LastIndexOfChar(c_StringBuffer_t* self, c_size_t start_index, char target) { + if (!self || !self->buffer || self->size == 0) return C_ERR_PARAM; + + c_size_t upper_bound = (start_index >= self->size) ? (self->size - 1) : start_index; + + // Backwards structural loop checking character identities cleanly + for (c_size_t i = upper_bound; ; i--) { + if (self->buffer[i] == target) { + return (c_index_t)i; + } + if (i == 0) break; + } + + return C_ERR_FAIL; +} + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_StringBuffer_ReplaceStr(c_StringBuffer_t* self, const char* old_str, const char* new_str) { + if (!self || !old_str || !new_str) return C_ERR_PARAM; + + c_size_t old_len = strlen(old_str); + if (old_len == 0) return C_SUCCESS; // Replacing an empty string is a no-op + + c_size_t new_len = strlen(new_str); + + // Pass 1: Count total occurrences to evaluate memory requirements safely + c_size_t occurrences = 0; + const char* scan = self->buffer; + if (scan) { + while ((scan = strstr(scan, old_str)) != NULL) { + occurrences++; + scan += old_len; + } + } + + if (occurrences == 0) return C_SUCCESS; // No matches found + + // Calculate structural payload delta modifications + long long delta = (long long)new_len - (long long)old_len; + c_size_t final_size = self->size + (occurrences * delta); + + // Expand buffer layout upfront if the replacement string expands the footprint + if (delta > 0) { + c_err_t err = c_StringBuffer_EnsureCapacity(self, occurrences * delta); + if (err != C_SUCCESS) return err; + } + + // Pass 2: Apply the substitution matrix via pointer offsets + char* read_ptr = self->buffer; + char* write_ptr = self->buffer; + + // If the string expands, we must write from right-to-left to prevent stomping data. + // However, an easy and clean way to handle all deltas without complex memory logic + // is utilizing a temporary buffer, or shifting segments sequentially. + // Let's implement an in-place single-buffer scan-and-shift variant: + c_size_t current_index = 0; + while (current_index < self->size) { + char* match = strstr(self->buffer + current_index, old_str); + if (!match) break; + + c_index_t match_idx = (c_index_t)(match - self->buffer); + + if (delta != 0) { + // Shift the trailing data behind the old string block configuration + c_size_t tail_len = self->size - (match_idx + old_len); + memmove(self->buffer + match_idx + new_len, self->buffer + match_idx + old_len, tail_len); + } + + // Copy the replacement string elements into the target slot + if (new_len > 0) { + memcpy(self->buffer + match_idx, new_str, new_len); + } + + // Adjust tracking dimensions + self->size += delta; + current_index = match_idx + new_len; + } + + self->buffer[self->size] = '\0'; // Strictly enforce final null-termination + return C_SUCCESS; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#include +#include + +c_err_t c_StringBuffer_TrimLeft(c_StringBuffer_t* self) { + if (!self) return C_ERR_PARAM; + if (self->size == 0) return C_SUCCESS; + + c_size_t spaces = 0; + + // Scan forward to count leading whitespace characters + // isspace covers: ' ', '\t', '\n', '\v', '\f', '\r' + while (spaces < self->size && isspace((unsigned char)self->buffer[spaces])) { + spaces++; + } + + if (spaces == 0) return C_SUCCESS; // No leading whitespace found + + // Shift the remaining structural payload left to overwrite the whitespace + c_size_t remaining_bytes = self->size - spaces; + if (remaining_bytes > 0) { + memmove(self->buffer, self->buffer + spaces, remaining_bytes); + } + + self->size = remaining_bytes; + self->buffer[self->size] = '\0'; // Strictly enforce structural null-termination + + return C_SUCCESS; +} + +c_err_t c_StringBuffer_TrimRight(c_StringBuffer_t* self) { + if (!self) return C_ERR_PARAM; + if (self->size == 0) return C_SUCCESS; + + // Scan backwards from the tail using unsigned down-counting loop guard rails + c_size_t i = self->size; + while (i > 0 && isspace((unsigned char)self->buffer[i - 1])) { + i--; + } + + // Adjust structural sizes down directly without moving memory arrays + self->size = i; + if (self->buffer && self->capacity > 0) { + self->buffer[self->size] = '\0'; + } + + return C_SUCCESS; +} + +c_err_t c_StringBuffer_Trim(c_StringBuffer_t* self) { + if (!self) return C_ERR_PARAM; + + // Performance optimization: Clean up tail bytes first to minimize memory movement blocks + c_err_t err = c_StringBuffer_TrimRight(self); + if (err != C_SUCCESS) return err; + + return c_StringBuffer_TrimLeft(self); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_StringBuffer_ToLower(c_StringBuffer_t* self) { + if (!self || !self->buffer) return C_ERR_PARAM; + + for (c_size_t i = 0; i < self->size; i++) { + self->buffer[i] = (char)tolower((unsigned char)self->buffer[i]); + } + return C_SUCCESS; +} + +c_err_t c_StringBuffer_ToUpper(c_StringBuffer_t* self) { + if (!self || !self->buffer) return C_ERR_PARAM; + + for (c_size_t i = 0; i < self->size; i++) { + self->buffer[i] = (char)toupper((unsigned char)self->buffer[i]); + } + return C_SUCCESS; +} + + +c_err_t c_StringBuffer_Split(c_StringBuffer_t* self, const char* delimiter, c_StringBuffer_t** out_tokens, c_size_t* out_count) { + // 1. 严格的参数校验 + if (!self || !self->buffer || !delimiter || !out_tokens || !out_count) { + return C_ERR_PARAM; + } + + // 显式将输出重置,防止调用方读取未初始化的脏数据 + *out_tokens = NULL; + *out_count = 0; + + c_size_t delim_len = strlen(delimiter); + if (delim_len == 0) { + return C_ERR_PARAM; // 分隔符不能为空字符串 + } + + // 2. 第一轮扫描:计算一共会拆分出多少个 Token,以便一次性分配连续数组空间 + c_size_t token_count = 1; + const char* scan = self->buffer; + while ((scan = strstr(scan, delimiter)) != NULL) { + token_count++; + scan += delim_len; // 跳过当前分隔符继续匹配 + } + + // 3. 一次性分配容纳所有结构体的数组 + c_StringBuffer_t* tokens = (c_StringBuffer_t*)malloc(token_count * sizeof(c_StringBuffer_t)); + if (!tokens) { + return C_ERR_NOMEM; + } + + // 预先清空结构体数组,使后续的防御性回滚清理更加安全 + for (c_size_t i = 0; i < token_count; i++) { + tokens[i].buffer = NULL; + tokens[i].capacity = 0; + tokens[i].size = 0; + } + + // 4. 第二轮扫描:精准切片并填充到独立的结构体中 + c_size_t current_token = 0; + c_size_t start_idx = 0; + + while (start_idx <= self->size) { + // 寻找下一个分隔符的位置 + char* match = strstr(self->buffer + start_idx, delimiter); + + // 计算当前 Token 的字节长度 + c_size_t token_len = match ? (c_size_t)(match - (self->buffer + start_idx)) : (self->size - start_idx); + + // 初始化子 StringBuffer(分配其内部的 char* 缓冲区) + c_err_t err = c_StringBuffer_Init(&tokens[current_token], token_len); + if (err != C_SUCCESS) goto error_cleanup; + + // 如果长度大于 0,将片段内容追加拷贝进去 + if (token_len > 0) { + err = c_StringBuffer_Append(&tokens[current_token], self->buffer + start_idx, token_len); + if (err != C_SUCCESS) goto error_cleanup; + } + + current_token++; + if (!match) break; // 已处理完最后一个片段,退出循环 + + // 步进索引:当前片段长度 + 分隔符长度 + start_idx += token_len + delim_len; + } + + // 5. 成功赋值输出 + *out_tokens = tokens; + *out_count = token_count; + return C_SUCCESS; + +// 防御性垃圾回收:如果中途任何一个 Token 内存分配失败,完整回滚,绝不泄露 +error_cleanup: + for (c_size_t i = 0; i < token_count; i++) { + // c_StringBuffer_Destroy 内部有对 NULL 的安全校验 + c_StringBuffer_Destroy(&tokens[i]); + } + free(tokens); + return C_ERR_NOMEM; +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_StringBuffer_Join(c_StringBuffer_t* self, const c_StringBuffer_t tokens[], c_size_t count, const char* separator) { + if (!self || (!tokens && count > 0) || !separator) return C_ERR_PARAM; + + c_StringBuffer_Clear(self); + if (count == 0) return C_SUCCESS; + + c_size_t sep_len = strlen(separator); + c_size_t total_required_space = 0; + + // Pass 1: Compute exactly how much capacity is needed upfront to prevent intermediate reallocations + for (c_size_t i = 0; i < count; i++) { + total_required_space += tokens[i].size; + if (i < count - 1) { + total_required_space += sep_len; + } + } + + c_err_t err = c_StringBuffer_EnsureCapacity(self, total_required_space); + if (err != C_SUCCESS) return err; + + // Pass 2: Fast sequential data copying into the pre-sized buffer + for (c_size_t i = 0; i < count; i++) { + if (tokens[i].size > 0) { + memcpy(self->buffer + self->size, tokens[i].buffer, tokens[i].size); + self->size += tokens[i].size; + } + + if (i < count - 1 && sep_len > 0) { + memcpy(self->buffer + self->size, separator, sep_len); + self->size += sep_len; + } + } + + self->buffer[self->size] = '\0'; // Strictly enforce final null-termination + return C_SUCCESS; +} + +int c_StringBuffer_Equals(const c_StringBuffer_t* self, const char* string) { + if (!self || !string) return 0; + if (!self->buffer) return (string[0] == '\0'); + + // Optimization: Check sizing footprints first before comparing bytes + c_size_t str_len = strlen(string); + if (self->size != str_len) return 0; + + return (strcmp(self->buffer, string) == 0); +} + +int c_StringBuffer_EqualsIgnoreCase(const c_StringBuffer_t* self, const char* string) { + if (!self || !string) return 0; + if (!self->buffer) return (string[0] == '\0'); + + c_size_t str_len = strlen(string); + if (self->size != str_len) return 0; + + // Character-by-character validation mapped safely onto tolower limits + for (c_size_t i = 0; i < self->size; i++) { + if (tolower((unsigned char)self->buffer[i]) != tolower((unsigned char)string[i])) { + return 0; // Immediate mismatch exit + } + } + + return 1; // Content identities match perfectly +} + +int c_StringBuffer_Compare(const c_StringBuffer_t* self, const char* string) { + // Standardize null pointers to make safety deterministic + const char* s1 = (self && self->buffer) ? self->buffer : ""; + const char* s2 = string ? string : ""; + + return strcmp(s1, s2); +} + +c_err_t c_StringBuffer_Reverse(c_StringBuffer_t* self) { + if (!self) return C_ERR_PARAM; + if (self->size <= 1) return C_SUCCESS; // No-op if empty or single character + + c_size_t left = 0; + c_size_t right = self->size - 1; + + // Fast symmetric swap loop executing entirely in-place + while (left < right) { + char temp = self->buffer[left]; + self->buffer[left] = self->buffer[right]; + self->buffer[right] = temp; + + left++; + right--; + } + + // Maintain safety by preserving the existing null-terminator position + self->buffer[self->size] = '\0'; + return C_SUCCESS; +} + + +c_err_t c_StringBuffer_Substr(const c_StringBuffer_t* self, c_size_t index, c_size_t length, c_StringBuffer_t* out_substring) { + if (!self || !out_substring) return C_ERR_PARAM; + + // Explicitly zero out the target structure descriptor up front to prevent undefined state access on failure + out_substring->buffer = NULL; + out_substring->capacity = 0; + out_substring->size = 0; + + if (index > self->size) return C_ERR_OUTOFBOUND; + + // Clamp the target length parameter dynamically if it exceeds the remaining data payload bounds + if (index + length > self->size) { + length = self->size - index; + } + + // Initialize the out string buffer with the exact exact footprint space required + c_err_t err = c_StringBuffer_Init(out_substring, length); + if (err != C_SUCCESS) return err; + + if (length > 0) { + err = c_StringBuffer_Append(out_substring, self->buffer + index, length); + if (err != C_SUCCESS) { + c_StringBuffer_Destroy(out_substring); + return err; + } + } + + return C_SUCCESS; +} + +c_err_t c_StringBuffer_Slice(const c_StringBuffer_t* self, c_size_t start_index, c_size_t end_index, c_StringBuffer_t* out_slice) { + if (!self || !out_slice) return C_ERR_PARAM; + + out_slice->buffer = NULL; + out_slice->capacity = 0; + out_slice->size = 0; + + if (start_index > self->size) return C_ERR_OUTOFBOUND; + + // Clamp end_index if it exceeds the structural size boundary limits + if (end_index > self->size) { + end_index = self->size; + } + + // If indices are out of order or equal, return an empty initialized string buffer instance safely + c_size_t length = (end_index > start_index) ? (end_index - start_index) : 0; + + c_err_t err = c_StringBuffer_Init(out_slice, length); + if (err != C_SUCCESS) return err; + + if (length > 0) { + err = c_StringBuffer_Append(out_slice, self->buffer + start_index, length); + if (err != C_SUCCESS) { + c_StringBuffer_Destroy(out_slice); + return err; + } + } + + return C_SUCCESS; +} + + +c_err_t c_StringBuffer_strtoul(const c_StringBuffer_t* self, c_size_t start_index, int base, unsigned long* out_value, c_size_t* out_end_index) { + if (!self || !self->buffer || !out_value) return C_ERR_PARAM; + if (start_index >= self->size) return C_ERR_OUTOFBOUND; + + // Reset errno before executing standard parsing functions to isolate previous system actions + int current_errno = errno; + errno = 0; + + char* parse_end = NULL; + const char* start_ptr = self->buffer + start_index; + + unsigned long result = strtoul(start_ptr, &parse_end, base); + + // Error Validation Condition 1: Check for standard numerical overflow/underflow + if (errno == ERANGE) { + return C_ERR_OUTOFBOUND; // Numerical envelope exceeded bounds + } + + // Error Validation Condition 2: No structural digits could be parsed at all + if (parse_end == start_ptr) { + errno = current_errno; // Restore system errno + return C_ERR_PARAM; + } + + // Assign the computed scalar result out safely + *out_value = result; + + // Map pointer arithmetic distances back into the context of our indexing structural offset + if (out_end_index) { + *out_end_index = start_index + (c_size_t)(parse_end - start_ptr); + } + + errno = current_errno; // Restore system errno + return C_SUCCESS; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_StringBuffer_SetLength(c_StringBuffer_t* sb, c_size_t new_length) { + // 1. Core safety verification of structural parameters + if (!sb) { + return C_ERR_PARAM; + } + + // 2. Case A: Truncation step (new_length is within current memory boundaries) + if (new_length <= sb->size) { + sb->size = new_length; + if (sb->buffer && sb->size < sb->capacity) { + sb->buffer[sb->size] = '\0'; // Seal the new structural boundary line instantly + } + return C_ERR_OK; + } + + // 3. Case B: Buffer Expansion step (new_length exceeds current logical boundary size) + // Verify if we need to resize the dynamic backing heap array matrix + if (new_length >= sb->capacity) { + c_size_t new_capacity = sb->capacity == 0 ? 16 : sb->capacity * 2; + if (new_capacity <= new_length) { + new_capacity = new_length + 1; // Secure extra room for the trailing null-terminator + } + + char* new_array = (char*)C_REALLOC(sb->buffer, new_capacity * sizeof(char)); + if (!new_array) { + return C_ERR_NOMEM; // Bubble up out-of-memory errors cleanly + } + sb->buffer = new_array; + sb->capacity = new_capacity; + } + + // 4. Zero-pad the newly appended logical spacing area segment + memset(sb->buffer + sb->size, 0, new_length - sb->size); + + // 5. Commit trailing metadata fields and place the final structural terminator string hook + sb->size = new_length; + sb->buffer[sb->size] = '\0'; + + return C_ERR_OK; +} diff --git a/Foundation/c_StringBuffer.h b/Foundation/c_StringBuffer.h new file mode 100644 index 0000000..e620c8e --- /dev/null +++ b/Foundation/c_StringBuffer.h @@ -0,0 +1,116 @@ +#ifndef INCLUDED_C_STRINGBUFFER_H +#define INCLUDED_C_STRINGBUFFER_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +#ifndef INCLUDED_STDARG_H +#define INCLUDED_STDARG_H +#include +#endif /*INCLUDED_STDARG_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + char* buffer; + c_size_t capacity; + c_size_t size; +}c_StringBuffer_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_StringBuffer_Init(c_StringBuffer_t* self, c_size_t capacity); + +void c_StringBuffer_Destroy(c_StringBuffer_t* self); + +c_err_t c_StringBuffer_Append(c_StringBuffer_t* self, const char* string, c_size_t length); + +c_err_t c_StringBuffer_Prepend(c_StringBuffer_t* self, const char* string, c_size_t length); + +c_err_t c_StringBuffer_InsertAt(c_StringBuffer_t* self, c_size_t index, const char* string, c_size_t length); + +c_err_t c_StringBuffer_RemoveAt(c_StringBuffer_t* self, c_size_t index, c_size_t length); + +void c_StringBuffer_Clear(c_StringBuffer_t* self); + +c_err_t c_StringBuffer_AppendStr(c_StringBuffer_t* self, const char* string); +c_err_t c_StringBuffer_PrependStr(c_StringBuffer_t* self, const char* string); +c_err_t c_StringBuffer_InsertStrAt(c_StringBuffer_t* self, const char* string, c_size_t index); + +c_err_t c_StringBuffer_CopyTo(c_StringBuffer_t* self, c_size_t index, c_size_t length, char* buffer, c_size_t buffer_length); + +c_err_t c_StringBuffer_Printf(c_StringBuffer_t* self, const char* format, ...); +c_err_t c_StringBuffer_PrintfAt(c_StringBuffer_t* self, c_size_t index, const char* format, ...); + +c_err_t c_StringBuffer_VPrintf(c_StringBuffer_t* self, const char* format, va_list args); +c_err_t c_StringBuffer_VPrintfAt(c_StringBuffer_t* self, c_size_t index, const char* format, va_list args); + +c_err_t c_StringBuffer_AppendTimestamp(c_StringBuffer_t* self, const char* format, const struct tm* time_info); +c_err_t c_StringBuffer_AppendCurrentTimestamp(c_StringBuffer_t* self, const char* format, int use_utc); +c_err_t c_StringBuffer_InsertTimestampAt(c_StringBuffer_t* self, c_size_t index, const char* format, const struct tm* time_info); + +c_index_t c_StringBuffer_IndexOfStr(c_StringBuffer_t* self, c_size_t start_index, const char* substr); +c_index_t c_StringBuffer_IndexOfChar(c_StringBuffer_t* self, c_size_t start_index, char target); +c_index_t c_StringBuffer_LastIndexOfStr(c_StringBuffer_t* self, c_size_t start_index, const char* substr); +c_index_t c_StringBuffer_LastIndexOfChar(c_StringBuffer_t* self, c_size_t start_index, char target); + +c_err_t c_StringBuffer_ReplaceStr(c_StringBuffer_t* self, const char* old_str, const char* new_str); + +c_err_t c_StringBuffer_Trim(c_StringBuffer_t* self); +c_err_t c_StringBuffer_TrimLeft(c_StringBuffer_t* self); +c_err_t c_StringBuffer_TrimRight(c_StringBuffer_t* self); + +c_err_t c_StringBuffer_ToLower(c_StringBuffer_t* self); +c_err_t c_StringBuffer_ToUpper(c_StringBuffer_t* self); + +/* + * 重新设计的 Split 函数 + * @param self: 原始字符串缓冲区指针 + * @param delimiter: 分隔符字符串(不能为 NULL 或空字符串) + * @param out_tokens: 输出参数,用于接收分配的 c_StringBuffer_t 结构体数组指针 + * @param out_count: 输出参数,用于接收拆分出来的 Token 总数 + */ +c_err_t c_StringBuffer_Split(c_StringBuffer_t* self, const char* delimiter, c_StringBuffer_t** out_tokens, c_size_t* out_count); + +c_err_t c_StringBuffer_Join(c_StringBuffer_t* self, const c_StringBuffer_t tokens[], c_size_t count, const char* separator); + +int c_StringBuffer_Equals(const c_StringBuffer_t* self, const char* string); +int c_StringBuffer_EqualsIgnoreCase(const c_StringBuffer_t* self, const char* string); + +int c_StringBuffer_Compare(const c_StringBuffer_t* self, const char* string); +c_err_t c_StringBuffer_Reverse(c_StringBuffer_t* self); + +c_err_t c_StringBuffer_Substr(const c_StringBuffer_t* self, c_size_t index, c_size_t length, c_StringBuffer_t* out_substring); +c_err_t c_StringBuffer_Slice(const c_StringBuffer_t* self, c_size_t start_index, c_size_t end_index, c_StringBuffer_t* out_slice); + +/* + * Parses an unsigned long value from the buffer starting at a specific index. + * @param self: The string buffer instance. + * @param start_index: The index position to start scanning from. + * @param base: The number base system to parse (0, 2-36). + * @param out_value: Destination pointer for the parsed unsigned long. + * @param out_end_index: Optional destination pointer for the index of the first character after the number. + */ +c_err_t c_StringBuffer_strtoul(const c_StringBuffer_t* self, c_size_t start_index, int base, unsigned long* out_value, c_size_t* out_end_index); + + +/** + * Set the logical character length of the string buffer. + * If the new length is less than the current size, the string is truncated. + * If the new length is greater, the buffer expands and is zero-padded. + * + * @param sb Pointer to the active c_StringBuffer_t instance + * @param new_length The targeted logical length boundary to apply + * @return + * - C_ERR_OK if success + * - C_ERR_PARAM if parameters are invalid + * - C_ERR_NOMEM if expansion fails due to system exhaustion + */ +c_err_t c_StringBuffer_SetLength(c_StringBuffer_t* sb, c_size_t new_length); + +#endif /*INCLUDED_C_STRINGBUFFER_H*/ diff --git a/Foundation/c_StringList.c b/Foundation/c_StringList.c new file mode 100644 index 0000000..48fd9a4 --- /dev/null +++ b/Foundation/c_StringList.c @@ -0,0 +1,124 @@ +#include +#include + +c_err_t c_StringList_Init(c_StringList* list, c_size_t initial_capacity) { + if (!list) return C_ERR_PARAM; + list->size = 0; + list->capacity = initial_capacity; + if (initial_capacity > 0) { + list->strings = (char**)C_ALLOC(initial_capacity * sizeof(char*)); + if (!list->strings) { + list->capacity = 0; + return C_ERR_NOMEM; + } + } else { + list->strings = NULL; + } + return C_ERR_OK; +} + +void c_StringList_Destroy(c_StringList* list) { + if (!list) return; + if (list->strings) { + for (c_size_t i = 0; i < list->size; i++) { + C_FREE(list->strings[i]); + } + C_FREE(list->strings); + } + list->size = 0; + list->capacity = 0; +} + +c_err_t c_StringList_Append(c_StringList* list, const char* str) { + if (!list || !str) return C_ERR_PARAM; + if (list->size >= list->capacity) { + c_size_t new_capacity = (list->capacity == 0) ? 4 : list->capacity * 2; + char** new_array = (char**)C_REALLOC(list->strings, new_capacity * sizeof(char*)); + if (!new_array) return C_ERR_NOMEM; + list->strings = new_array; + list->capacity = new_capacity; + } + char* copy = (char*)C_ALLOC(strlen(str) + 1); + if (!copy) return C_ERR_NOMEM; + strcpy(copy, str); + list->strings[list->size++] = copy; + return C_ERR_OK; +} + + +/** + * Insert a new null-terminated string copy row at the very front of the collection list (Index 0) + */ +c_err_t c_StringList_Prepend(c_StringList* list, const char* str) { + if (!list || !str) { + return C_ERR_PARAM; + } + + // 1. Dynamic scale check and array reallocation if capacity bounds are reached + if (list->size >= list->capacity) { + c_size_t new_capacity = (list->capacity == 0) ? 4 : list->capacity * 2; + char** new_array = (char**)C_REALLOC(list->strings, new_capacity * sizeof(char*)); + if (!new_array) { + return C_ERR_NOMEM; + } + list->strings = new_array; + list->capacity = new_capacity; + } + + // 2. Allocate an isolated deep-copy heap buffer for the incoming string characters + char* str_copy = (char*)C_ALLOC(strlen(str) + 1); + if (!str_copy) { + return C_ERR_NOMEM; + } + strcpy(str_copy, str); + + // 3. Shift all existing string pointer entries rightward by 1 to make room at index 0 + for (c_size_t i = list->size; i > 0; i--) { + list->strings[i] = list->strings[i - 1]; + } + + // 4. Place the newly allocated string capsule at the front and increment tracking metrics + list->strings[0] = str_copy; + list->size++; + + return C_ERR_OK; +} + +/** + * Remove the first occurrence of a string value from the collection. + * Cleans up memory allocations and tightly shifts elements to prevent layout leaks. + */ +c_err_t c_StringList_Remove(c_StringList* list, const char* str) { + if (!list || !list->strings || !str) { + return C_ERR_PARAM; + } + + c_size_t target_idx = (c_size_t)-1; + + // 1. Linearly scan the string array to locate a matching value match + for (c_size_t i = 0; i < list->size; i++) { + if (list->strings[i] != NULL && strcmp(list->strings[i], str) == 0) { + target_idx = i; + break; + } + } + + // If the targeted string value does not exist, return an error code parameter + if (target_idx == (c_size_t)-1) { + return C_ERR_PARAM; + } + + // 2. Safely free the individual string characters heap capsule + C_FREE(list->strings[target_idx]); // Safe macro automatically sets index entry to NULL + + // 3. Shift subsequent array entries leftward to maintain dense cache prefetching locality + for (c_size_t i = target_idx; i < list->size - 1; i++) { + list->strings[i] = list->strings[i + 1]; + } + + // 4. Update trailing metadata bounds + list->size--; + list->strings[list->size] = NULL; // Ensure the newly vacant tail slot is nullified safely + + return C_ERR_OK; +} diff --git a/Foundation/c_StringList.h b/Foundation/c_StringList.h new file mode 100644 index 0000000..c88c9e5 --- /dev/null +++ b/Foundation/c_StringList.h @@ -0,0 +1,30 @@ +#ifndef INCLUDED_C_STRINGLIST_H +#define INCLUDED_C_STRINGLIST_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_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_StringList; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_StringList_Init(c_StringList* list, c_size_t initial_capacity); + +void c_StringList_Destroy(c_StringList* list); + +c_err_t c_StringList_Append(c_StringList* list, const char* str); + +c_err_t c_StringList_Prepend(c_StringList* list, const char* str); + +c_err_t c_StringList_Remove(c_StringList* list, const char* str); + +#endif /*INCLUDED_C_STRINGLIST_H*/ diff --git a/Foundation/c_Thread.c b/Foundation/c_Thread.c new file mode 100644 index 0000000..3884e29 --- /dev/null +++ b/Foundation/c_Thread.c @@ -0,0 +1 @@ +#include diff --git a/Foundation/c_Thread.h b/Foundation/c_Thread.h new file mode 100644 index 0000000..bebb118 --- /dev/null +++ b/Foundation/c_Thread.h @@ -0,0 +1,125 @@ +#ifndef INCLUDED_C_THREAD_H +#define INCLUDED_C_THREAD_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef void* c_ThreadResult_t; + +#if defined(_WIN32) || defined(_WIN64) + #include + #include + typedef HANDLE c_Thread_t; + typedef DWORD c_ThreadId_t; // Windows 使用 DWORD 作为线程 ID + #define C_THREAD_FUNC_RETURN_TYPE unsigned __stdcall + #define C_THREAD_FUNC_RETURN_VTYPE unsigned +#else + #include + typedef pthread_t c_Thread_t; + typedef unsigned long c_ThreadId_t; // POSIX 转换为数字 ID + #define C_THREAD_FUNC_RETURN_TYPE void* + #define C_THREAD_FUNC_RETURN_VTYPE C_THREAD_FUNC_RETURN_TYPE +#endif + +// Universal thread creation signature +typedef C_THREAD_FUNC_RETURN_TYPE (*c_ThreadFn_t)(void*); + +C_STATIC_FORCE_INLINE +c_bool_t c_Thread_Create(c_Thread_t* thread, c_ThreadFn_t func, void* arg) { +#if defined(_WIN32) || defined(_WIN64) + // *thread = CreateThread(NULL, 0, func, arg, 0, NULL); + // return (*thread != NULL); + *thread = (HANDLE)_beginthreadex(NULL, 0, (unsigned (__stdcall *)(void*))func, arg, 0, NULL); + return (*thread != NULL); +#else + return (pthread_create(thread, NULL, func, arg) == 0); +#endif +} + +C_STATIC_FORCE_INLINE +void c_Thread_Join(c_Thread_t thread) { +#if defined(_WIN32) || defined(_WIN64) + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); +#else + pthread_join(thread, NULL); +#endif +} + +C_STATIC_FORCE_INLINE +void c_Thread_Sleep(unsigned int milliseconds) { + #if defined(_WIN32) || defined(_WIN64) + Sleep(milliseconds); + #else + usleep(milliseconds * 1000); + #endif +} + +C_STATIC_FORCE_INLINE +c_ThreadId_t c_Thread_SelfId(void) { +#if defined(_WIN32) || defined(_WIN64) + return GetCurrentThreadId(); +#else + return (c_ThreadId_t)pthread_self(); +#endif +} + +C_STATIC_FORCE_INLINE +c_bool_t c_Thread_Detach(c_Thread_t thread) { +#if defined(_WIN32) || defined(_WIN64) + // Windows 中,关闭线程句柄并不等于终止线程。 + // 它只是减少内核对象的引用计数。线程运行结束时,内核对象会自动销毁。 + // 这与 POSIX 的 detach 行为完全一致。 + if (thread != NULL) { + return (CloseHandle(thread) != 0); + } + return C_FALSE; +#else + // POSIX 直接使用 pthread_detach + return (pthread_detach(thread) == 0); +#endif +} + +C_STATIC_FORCE_INLINE +c_bool_t c_Thread_Equal(c_Thread_t t1, c_Thread_t t2) { +#if defined(_WIN32) || defined(_WIN64) + // Windows 下可以通过比较线程 ID 来判断是否为同一个线程 + // 即使其中一个是 GetCurrentThread() 产生的伪句柄,GetThreadId 也能正确识别 + return (GetThreadId(t1) == GetThreadId(t2)); +#else + // POSIX 提供专用的比较函数 + return (pthread_equal(t1, t2) != 0); +#endif +} + +C_STATIC_FORCE_INLINE +c_bool_t c_Thread_JoinWithResult(c_Thread_t thread, c_ThreadResult_t* out_result) { +#if defined(_WIN32) || defined(_WIN64) + if (WaitForSingleObject(thread, INFINITE) == WAIT_OBJECT_0) { + DWORD exit_code = 0; + if (GetExitCodeThread(thread, &exit_code)) { + if (out_result) { + // 将 DWORD 转换为指针类型输出 + *out_result = (c_ThreadResult_t)(uintptr_t)exit_code; + } + CloseHandle(thread); + return C_TRUE; + } + } + CloseHandle(thread); // 即使失败也尝试关闭句柄 + return C_FALSE; +#else + // POSIX 直接在 join 时传入指针变量的地址 + return (pthread_join(thread, out_result) == 0); +#endif +} + +#define c_Thread_Exit(x) return (C_THREAD_FUNC_RETURN_VTYPE)(x) + +#define c_Thread_Fn(fn) C_THREAD_FUNC_RETURN_TYPE fn + +#endif /*INCLUDED_C_THREAD_H*/ diff --git a/Foundation/c_ThreadPool.c b/Foundation/c_ThreadPool.c new file mode 100644 index 0000000..651d64e --- /dev/null +++ b/Foundation/c_ThreadPool.c @@ -0,0 +1,122 @@ +#include +#include + +// Worker thread routine +static c_Thread_Fn(thread_pool_worker(void* arg)) { + c_ThreadPool_t* pool = (c_ThreadPool_t*)arg; + c_ThreadPoolTask_t* task; + while (!pool->is_shutdown) { + task = NULL; + + // Use a 500ms timeout for timedpop to allow periodic shutdown condition verification + if (c_LockQueue_TimedPop(&pool->task_queue, (void**)&task, pool->check_interval_ms)!=C_ERR_OK) { + if (task) { + if (task->function) { + // Execute user payload safely + task->function(task->argument); + } + // C_FREE(task); // Free the memory allocated for the task wrapper + c_Pool_Free(&pool->task_pool, task); + } + } + } + c_Thread_Exit(0); +} + +/** + * @brief Initializes a fixed-size thread pool. + */ +c_err_t c_ThreadPool_Init(c_ThreadPool_t* pool, int thread_count, int queue_capacity, int task_pool_size, int check_interval_ms) { + if (!pool || thread_count <= 0 || queue_capacity <= 0) return C_ERR_PARAM; + + pool->is_shutdown = C_FALSE; + pool->thread_count = thread_count; + pool->check_interval_ms = check_interval_ms; + + if (c_Pool_Init(&pool->task_pool, sizeof(c_ThreadPoolTask_t), task_pool_size)!=C_ERR_OK) { + return C_ERR_FAIL; + } + + // Allocate the thread handle array + pool->threads = (c_Thread_t*)C_ALLOC(sizeof(c_Thread_t) * thread_count); + if (!pool->threads) { + return C_ERR_NOMEM; + } + + // Initialize our previously constructed cross-platform thread-safe queue + c_err_t err = c_LockQueue_Init(&pool->task_queue, queue_capacity); + if (err!=C_ERR_OK) { + C_FREE(pool->threads); + return err; + } + + // Spawn the requested worker threads + for (int i = 0; i < thread_count; i++) { + if (!c_Thread_Create(&pool->threads[i], thread_pool_worker, pool)) { + // Rollback strategy on failures + pool->is_shutdown = C_TRUE; + c_LockQueue_Shutdown(&pool->task_queue); + for (int j = 0; j < i; j++) { + c_Thread_Join(pool->threads[j]); + } + c_LockQueue_Destroy(&pool->task_queue); + C_FREE(pool->threads); + return C_ERR_FAIL; + } + } + + return C_ERR_OK; +} + +/** + * @brief Submits a work payload to the pool. + */ +c_bool_t c_ThreadPool_Submit(c_ThreadPool_t* pool, void (*function)(void*), void* argument){ + if (!pool || !function || pool->is_shutdown) return C_FALSE; + + // c_ThreadPoolTask_t* task = (c_ThreadPoolTask_t*)C_ALLOC(sizeof(*task)); + c_ThreadPoolTask_t* task = c_Pool_Alloc(&pool->task_pool); + if (!task) return C_FALSE; + + task->function = function; + task->argument = argument; + + // Push the task into our thread-safe buffer. If full, this blocks the calling thread + if (c_LockQueue_Push(&pool->task_queue, task)!=C_ERR_OK) { + C_FREE(task); + return C_FALSE; + } + + return C_TRUE; +} + +/** + * @brief Orderly terminates the thread pool, waiting for running jobs to finish. + */ +void c_ThreadPool_Destroy(c_ThreadPool_t* pool) { + if (!pool) return; + + // 1. Terminate the processing loop + pool->is_shutdown = C_TRUE; + + // 2. Shut down the queue to unblock workers waiting indefinitely + c_LockQueue_Shutdown(&pool->task_queue); + + // 3. Join all worker threads safely + for (int i = 0; i < pool->thread_count; i++) { + c_Thread_Join(pool->threads[i]); + } + + // 4. Drain any remaining unexecuted tasks to prevent memory leaks + // void* unexecuted_task = NULL; + // while (c_LockQueue_Pop(&pool->task_queue, &unexecuted_task)==C_ERR_SUCCESS) { + // C_FREE(unexecuted_task); + // } + c_Pool_DryUp(&pool->task_pool); + c_Pool_Destroy(&pool->task_pool); + + // 5. Reclaim memory structures + c_LockQueue_Destroy(&pool->task_queue); + C_FREE(pool->threads); +} + diff --git a/Foundation/c_ThreadPool.h b/Foundation/c_ThreadPool.h new file mode 100644 index 0000000..248ce5c --- /dev/null +++ b/Foundation/c_ThreadPool.h @@ -0,0 +1,44 @@ +#ifndef INCLUDED_C_THREADPOOL_H +#define INCLUDED_C_THREADPOOL_H + +#ifndef INCLUDED_C_LOCKQUEUE_H +#include +#endif /*INCLUDED_C_LOCKQUEUE_H*/ + +#ifndef INCLUDED_C_THREAD_H +#include +#endif /*INCLUDED_C_THREAD_H*/ + +#ifndef INCLUDED_C_POOL_H +#include +#endif /*INCLUDED_C_POOL_H*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +// Struct representing a single executable unit of work +typedef struct { + void (*function)(void*); // Pointer to the user function + void* argument; // Argument passed to the function +} c_ThreadPoolTask_t; + +typedef struct { + c_LockQueue_t task_queue; // Safe queue storing thread_pool_task_t pointers + c_Thread_t* threads; // Array of worker thread handles + int thread_count; // Total number of worker threads + volatile c_bool_t is_shutdown; // Shutdown flag + int check_interval_ms; + c_Pool_t task_pool; +} c_ThreadPool_t; + +// Public Core API +c_err_t c_ThreadPool_Init(c_ThreadPool_t* pool, int thread_count, int queue_capacity, int task_pool_size, int check_interval_ms); + +void c_ThreadPool_Destroy(c_ThreadPool_t* pool); + +c_bool_t c_ThreadPool_Submit(c_ThreadPool_t* pool, void (*function)(void*), void* argument); + + +#endif /*INCLUDED_C_THREADPOOL_H*/ diff --git a/Foundation/c_ThreadPool.t.c b/Foundation/c_ThreadPool.t.c new file mode 100644 index 0000000..be18b81 --- /dev/null +++ b/Foundation/c_ThreadPool.t.c @@ -0,0 +1,39 @@ +#include "c_ThreadPool.h" +#include +#include + +// Sample payload mimicking work +void compute_square(void* arg) { + int val = *(int*)arg; + printf("[Thread Pool Task] Processing square of %d = %d\n", val, val * val); + free(arg); // Free parameter memory passed during submission +} + + +int main(int argc, char** argv){ + // Create a pool with 4 worker threads and a max capacity of 20 pending tasks + c_ThreadPool_t pool = {0}; + c_ThreadPool_Init(&pool, 4, 20, 10, 500); + + printf("--- Thread Pool Initialized with 4 Workers ---\n"); + + // Queue 10 dynamic processing computations + for (int i = 1; i <= 10; i++) { + int* num = (int*)malloc(sizeof(int)); + *num = i; + if (!c_ThreadPool_Submit(&pool, compute_square, num)) { + fprintf(stderr, "[Thread Pool Task] Submit %d failed\n", i); + } + } + + // Force main to simulate work before cleaning up + printf("All tasks submitted. Waiting for processing to settle...\n"); + + c_Thread_Sleep(2000); + + printf("--- Destroying Thread Pool ---\n"); + c_ThreadPool_Destroy(&pool); + printf("Thread pool destroyed cleanly.\n"); + + return 0; +} diff --git a/Foundation/c_timespec.c b/Foundation/c_timespec.c new file mode 100644 index 0000000..c3bc468 --- /dev/null +++ b/Foundation/c_timespec.c @@ -0,0 +1,98 @@ +#include +#include + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +struct timespec c_timespec_mod(struct timespec ts1, struct timespec ts2) +{ + int i = 0; + bool neg1 = false; + bool neg2 = false; + + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + /* If ts2 is zero, just return ts1 + */ + if (ts2.tv_sec == 0 && ts2.tv_nsec == 0) + { + return ts1; + } + + /* If inputs are negative, flip and record sign + */ + if (ts1.tv_sec < 0 || ts1.tv_nsec < 0) + { + neg1 = true; + ts1.tv_sec = -ts1.tv_sec; + ts1.tv_nsec = -ts1.tv_nsec; + } + + if (ts2.tv_sec < 0 || ts2.tv_nsec < 0) + { + neg2 = true; + ts2.tv_sec = -ts2.tv_sec; + ts2.tv_nsec = -ts2.tv_nsec; + } + + /* Shift ts2 until it is larger than ts1 or is about to overflow + */ + while ((ts2.tv_sec < (LONG_MAX >> 1)) && c_timespec_ge(ts1, ts2)) + { + i++; + ts2.tv_nsec <<= 1; + ts2.tv_sec <<= 1; + if (ts2.tv_nsec > C_NSEC_PER_SEC) + { + ts2.tv_nsec -= C_NSEC_PER_SEC; + ts2.tv_sec++; + } + } + + /* Division by repeated subtraction + */ + while (i >= 0) + { + if (c_timespec_ge(ts1, ts2)) + { + ts1 = c_timespec_sub(ts1, ts2); + } + + if (i == 0) + { + break; + } + + i--; + if (ts2.tv_sec & 1) + { + ts2.tv_nsec += C_NSEC_PER_SEC; + } + ts2.tv_nsec >>= 1; + ts2.tv_sec >>= 1; + } + + /* If signs differ and result is nonzero, subtract once more to cross zero + */ + if (neg1 ^ neg2 && (ts1.tv_sec != 0 || ts1.tv_nsec != 0)) + { + ts1 = c_timespec_sub(ts1, ts2); + } + + /* Restore sign + */ + if (neg1) + { + ts1.tv_sec = -ts1.tv_sec; + ts1.tv_nsec = -ts1.tv_nsec; + } + + return ts1; +} + + diff --git a/Foundation/c_timespec.h b/Foundation/c_timespec.h new file mode 100644 index 0000000..8596777 --- /dev/null +++ b/Foundation/c_timespec.h @@ -0,0 +1,258 @@ +#ifndef INCLUDED_C_TIMESPEC_H +#define INCLUDED_C_TIMESPEC_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#define C_NSEC_PER_USEC 1000L +#define C_USEC_PER_MSEC 1000L +#define C_MSEC_PER_SEC 1000L +#define C_NSEC_PER_MSEC 1000000L +#define C_USEC_PER_SEC 1000000L +#define C_NSEC_PER_SEC 1000000000L + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_normalise(struct timespec ts) +{ + while(ts.tv_nsec >= C_NSEC_PER_SEC) + { + ++(ts.tv_sec); + ts.tv_nsec -= C_NSEC_PER_SEC; + } + + while(ts.tv_nsec <= -C_NSEC_PER_SEC) + { + --(ts.tv_sec); + ts.tv_nsec += C_NSEC_PER_SEC; + } + + if(ts.tv_nsec < 0) + { + /* Negative nanoseconds isn't valid according to POSIX. + * Decrement tv_sec and roll tv_nsec over. + */ + + --(ts.tv_sec); + ts.tv_nsec = (C_NSEC_PER_SEC + ts.tv_nsec); + } + + return ts; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_add(struct timespec ts1, struct timespec ts2) +{ + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + ts1.tv_sec += ts2.tv_sec; + ts1.tv_nsec += ts2.tv_nsec; + + return c_timespec_normalise(ts1); +} + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_sub(struct timespec ts1, struct timespec ts2) +{ + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + ts1.tv_sec -= ts2.tv_sec; + ts1.tv_nsec -= ts2.tv_nsec; + + return c_timespec_normalise(ts1); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + + +C_STATIC_FORCE_INLINE +int c_timespec_cmp(struct timespec ts1, struct timespec ts2) +{ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + if(ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec) + { + return 0; + } + else if((ts1.tv_sec > ts2.tv_sec) + || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec)) + { + return 1; + } + else { + return -1; + } +} + +C_STATIC_FORCE_INLINE +bool c_timespec_eq(struct timespec ts1, struct timespec ts2) +{ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + return (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec); +} + +C_STATIC_FORCE_INLINE +bool c_timespec_gt(struct timespec ts1, struct timespec ts2) +{ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec)); +} + +C_STATIC_FORCE_INLINE +bool c_timespec_ge(struct timespec ts1, struct timespec ts2) +{ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec >= ts2.tv_nsec)); +} + +C_STATIC_FORCE_INLINE +bool c_timespec_lt(struct timespec ts1, struct timespec ts2) +{ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec < ts2.tv_nsec)); +} + +C_STATIC_FORCE_INLINE +bool c_timespec_le(struct timespec ts1, struct timespec ts2) +{ + ts1 = c_timespec_normalise(ts1); + ts2 = c_timespec_normalise(ts2); + + return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec <= ts2.tv_nsec)); +} + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_min(struct timespec ts1, struct timespec ts2) { + if(c_timespec_le(ts1, ts2)) { + return ts1; + } else { + return ts2; + } +} + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_max(struct timespec ts1, struct timespec ts2) { + if(c_timespec_ge(ts1, ts2)) { + return ts1; + } else { + return ts2; + } +} + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_clamp(struct timespec ts, struct timespec min, struct timespec max) { + if(c_timespec_gt(ts, max)) { + return max; + } + if(c_timespec_lt(ts, min)) { + return min; + } + return ts; +} + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_from_double(double s) +{ + struct timespec ts = { + .tv_sec = s, + .tv_nsec = (s - (long)(s)) * C_NSEC_PER_SEC, + }; + + return c_timespec_normalise(ts); +} + + + +C_STATIC_FORCE_INLINE +double c_timespec_to_double(struct timespec ts) +{ + return ((double)(ts.tv_sec) + ((double)(ts.tv_nsec) / C_NSEC_PER_SEC)); +} + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_from_timeval(struct timeval tv) +{ + struct timespec ts = { + .tv_sec = tv.tv_sec, + .tv_nsec = tv.tv_usec * C_NSEC_PER_USEC + }; + + return c_timespec_normalise(ts); +} + +C_STATIC_FORCE_INLINE +struct timeval c_timespec_to_timeval(struct timespec ts) +{ + ts = c_timespec_normalise(ts); + + struct timeval tv = { + .tv_sec = ts.tv_sec, + .tv_usec = ts.tv_nsec / C_NSEC_PER_USEC, + }; + + return tv; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +struct timespec c_timespec_from_ms(long milliseconds) +{ + struct timespec ts = { + .tv_sec = (milliseconds / C_MSEC_PER_SEC), + .tv_nsec = (milliseconds % C_MSEC_PER_SEC) * C_NSEC_PER_MSEC, + }; + + return c_timespec_normalise(ts); +} + +C_STATIC_FORCE_INLINE +long c_timespec_to_ms(struct timespec ts) +{ + return (ts.tv_sec * C_MSEC_PER_SEC) + (ts.tv_nsec / C_NSEC_PER_MSEC); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +struct timespec c_timespec_mod(struct timespec ts1, struct timespec ts2); + +#endif /*INCLUDED_C_TIMESPEC_H*/ diff --git a/Foundation/c_utf8.c b/Foundation/c_utf8.c new file mode 100644 index 0000000..37b7147 --- /dev/null +++ b/Foundation/c_utf8.c @@ -0,0 +1,817 @@ +#include + +c_size_t c_utf8_strlen(const char* str) { + if (!str) return 0; + + c_size_t char_count = 0; + c_size_t i = 0; + + while (str[i] != '\0') { + i += c_utf8_char_len(str[i]); // 跳过当前字符占用的全部字节 + char_count++; + } + + return char_count; +} + +const char* c_utf8_strchr(const char* str, const char* utf8_char) { + if (!str || !utf8_char || utf8_char[0] == '\0') return NULL; + + c_size_t target_bytes = c_utf8_char_len(utf8_char[0]); + c_size_t i = 0; + + while (str[i] != '\0') { + c_size_t curr_bytes = c_utf8_char_len(str[i]); + + // 当且仅当两个字符占用的字节数相同,且多字节内容完全一致时匹配成功 + if (curr_bytes == target_bytes) { + if (memcmp(&str[i], utf8_char, target_bytes) == 0) { + return &str[i]; + } + } + i += curr_bytes; // 移动到下一个 UTF-8 字符 + } + + return NULL; +} + +char* c_utf8_strncpy(char* dest, const char* src, c_size_t char_num) { + if (!dest || !src || char_num == 0) return dest; + + c_size_t src_idx = 0; + c_size_t dest_idx = 0; + c_size_t copied_chars = 0; + + while (src[src_idx] != '\0' && copied_chars < char_num) { + c_size_t char_bytes = c_utf8_char_len(src[src_idx]); + + // 批量精确拷贝当前完整字符的 N 个字节 + memcpy(&dest[dest_idx], &src[src_idx], char_bytes); + + src_idx += char_bytes; + dest_idx += char_bytes; + copied_chars++; + } + + // 兼容 strncpy 标准:如果源串长度小于 char_num,则用 '\0' 填充剩余的空隙 + // 注意:这里的剩余空隙在实际工程中通常按字节填充更安全 + dest[dest_idx] = '\0'; + + return dest; +} + +int c_utf8_strncmp(const char* str1, const char* str2, c_size_t char_num) { + if (!str1 || !str2 || char_num == 0) return 0; + + c_size_t idx1 = 0; + c_size_t idx2 = 0; + c_size_t compared_chars = 0; + + while (compared_chars < char_num) { + // 任意一端到达末尾 + if (str1[idx1] == '\0' || str2[idx2] == '\0') { + return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]); + } + + c_size_t len1 = c_utf8_char_len(str1[idx1]); + c_size_t len2 = c_utf8_char_len(str2[idx2]); + + // 如果单字长字节不相等,直接根据当前字符进行排序比较 + if (len1 != len2) { + return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]); + } + + // 长度相同时,直接比较当前单个 UTF-8 字符的内容 + int res = memcmp(&str1[idx1], &str2[idx2], len1); + if (res != 0) { + return res; + } + + idx1 += len1; + idx2 += len2; + compared_chars++; + } + + return 0; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +char* c_utf8_tolower(char* str) { + if (!str) return NULL; + + c_size_t i = 0; + while (str[i] != '\0') { + unsigned char b1 = (unsigned char)str[i]; + c_size_t len = c_utf8_char_len(str[i]); + + // Case A: Standard Single-byte ASCII Case Folding + if (len == 1) { + if (b1 >= 'A' && b1 <= 'Z') { + str[i] = (char)(b1 + 32); + } + } + // Case B: Double-byte UTF-8 Case Folding (e.g., Cyrillic / Greek scripts) + else if (len == 2) { + unsigned char b2 = (unsigned char)str[i + 1]; + + // Cyrillic script transformations (Capital letters: 0xD0 0x80 to 0xD0 0xBF) + if (b1 == 0xD0) { + if (b2 >= 0x90 && b2 <= 0xAF) { + // Shift to lowercase variant range located inside 0xD0 / 0xD1 blocks + str[i + 1] = (char)(b2 + 0x20); + } else if (b2 >= 0xB0 && b2 <= 0xBF) { + str[i] = (char)0xD1; + str[i + 1] = (char)(b2 - 0x20); + } + } + // Greek script transformations (Capital letters: 0xCE 0x91 to 0xCE 0xAB) + else if (b1 == 0xCE) { + if (b2 >= 0x91 && b2 <= 0xAB && b2 != 0xA2) { // 0xA2 is a special variant + // Shift down to lowercase variant range block inside 0xCE / 0xCF + if (b2 <= 0x9F) { + str[i + 1] = (char)(b2 + 0x20); + } else { + str[i] = (char)0xCF; + str[i + 1] = (char)(b2 - 0x20); + } + } + } + } + // Multi-byte Chinese ideographs (3 bytes) and Emojis (4 bytes) lack casing concepts; step past them + i += len; + } + + return str; +} + + +char* c_utf8_toupper(char* str) { + if (!str) return NULL; + + c_size_t i = 0; + while (str[i] != '\0') { + unsigned char b1 = (unsigned char)str[i]; + c_size_t len = c_utf8_char_len(str[i]); + + // Case A: Standard Single-byte ASCII Case Folding + if (len == 1) { + if (b1 >= 'a' && b1 <= 'z') { + str[i] = (char)(b1 - 32); + } + } + // Case B: Double-byte UTF-8 Case Folding (e.g., Cyrillic / Greek scripts) + else if (len == 2) { + unsigned char b2 = (unsigned char)str[i + 1]; + + // Cyrillic script transformations (Lowercase letters: 0xD0 0xB0 to 0xD0 0xBF, and 0xD1 0x80 to 0xD1 0x8F) + if (b1 == 0xD0) { + if (b2 >= 0xB0 && b2 <= 0xBF) { + // Shift up to uppercase variant range located inside the 0xD0 block + str[i + 1] = (char)(b2 - 0x20); + } + } else if (b1 == 0xD1) { + if (b2 >= 0x80 && b2 <= 0x8F) { + // Convert leading byte from 0xD1 back to 0xD0 and realign low byte + str[i] = (char)0xD0; + str[i + 1] = (char)(b2 + 0x20); + } + } + // Greek script transformations (Lowercase letters: 0xCE 0xB1 to 0xCE 0xBF, and 0xCF 0x80 to 0xCF 0x8B) + else if (b1 == 0xCE) { + if (b2 >= 0xB1 && b2 <= 0xBF) { + // Shift down to uppercase variant range block inside 0xCE + str[i + 1] = (char)(b2 - 0x20); + } + } else if (b1 == 0xCF) { + if (b2 >= 0x80 && b2 <= 0x8B) { + // Convert leading byte from 0xCF back to 0xCE and realign low byte + str[i] = (char)0xCE; + str[i + 1] = (char)(b2 + 0x20); + } + } + } + // 3-byte characters (Chinese Ideographs) and 4-byte characters (Emojis) lack casing concepts; jump past them safely + i += len; + } + + return str; +} + + +char* c_utf8_strcat(char* dest, const char* src) { + if (!dest || !src) return dest; + + // Locate the termination boundary point of the original destination array + c_size_t dest_idx = 0; + while (dest[dest_idx] != '\0') { + dest_idx++; + } + + // Continuously append source bytes until hitting the terminator character + c_size_t src_idx = 0; + while (src[src_idx] != '\0') { + dest[dest_idx++] = src[src_idx++]; + } + + // Force secure terminal character sealing + dest[dest_idx] = '\0'; + + return dest; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +const char* c_utf8_strstr(const char* haystack, const char* needle) { + if (!haystack || !needle) return NULL; + + // An empty needle matches the beginning of the haystack per standard strstr specification + if (needle[0] == '\0') { + return haystack; + } + + c_size_t h_idx = 0; + while (haystack[h_idx] != '\0') { + c_size_t n_idx = 0; + c_size_t current_match_idx = h_idx; + + // Perform byte-by-byte substring evaluation from the current boundary anchor + while (haystack[current_match_idx] != '\0' && needle[n_idx] != '\0' && + haystack[current_match_idx] == needle[n_idx]) { + current_match_idx++; + n_idx++; + } + + // If we successfully traversed the entire needle string, a match is found + if (needle[n_idx] == '\0') { + return &haystack[h_idx]; + } + + // Advance to the next valid UTF-8 character point in the haystack + h_idx += c_utf8_char_len(haystack[h_idx]); + } + + return NULL; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ +#include + +const char* c_utf8_strrchr(const char* str, const char* utf8_char) { + if (!str || !utf8_char || utf8_char[0] == '\0') return NULL; + + c_size_t target_bytes = c_utf8_char_len(utf8_char[0]); + c_size_t i = 0; + const char* last_match = NULL; + + while (str[i] != '\0') { + c_size_t curr_bytes = c_utf8_char_len(str[i]); + + // Continuous linear check, updating our tracker to keep the furthest matched offset + if (curr_bytes == target_bytes) { + if (memcmp(&str[i], utf8_char, target_bytes) == 0) { + last_match = &str[i]; + } + } + i += curr_bytes; // Jump forward explicitly across whole multi-byte characters + } + + return last_match; +} + +/** + * @brief Internal helper to verify if a specific UTF-8 character pointer matches any delimiter in the list. + */ +C_STATIC_FORCE_INLINE +c_bool_t c_utf8_is_delim(const char* current_char, const char* delims, c_size_t* delim_len) { + c_size_t d_idx = 0; + c_size_t c_len = c_utf8_char_len(*current_char); + + while (delims[d_idx] != '\0') { + c_size_t d_len = c_utf8_char_len(delims[d_idx]); + if (c_len == d_len && memcmp(current_char, &delims[d_idx], c_len) == 0) { + *delim_len = d_len; + return C_TRUE; + } + d_idx += d_len; + } + return C_FALSE; +} + +char* c_utf8_strtok(char* str, const char* delims, char** saveptr) { + if (!delims || !saveptr) return NULL; + + // Use our saved pointer context if str is passed as NULL + char* token_cursor = (str != NULL) ? str : *saveptr; + if (!token_cursor || *token_cursor == '\0') { + return NULL; + } + + // Step 1: Skip over any leading delimiter sequences to locate the token start + c_size_t skip_len = 0; + while (*token_cursor != '\0' && c_utf8_is_delim(token_cursor, delims, &skip_len)) { + token_cursor += skip_len; + } + + // If we hit the absolute end of the input string while skipping delims, no tokens exist + if (*token_cursor == '\0') { + *saveptr = token_cursor; + return NULL; + } + + char* token_start = token_cursor; + + // Step 2: Track forward to locate the terminal delimiter boundary of this token + while (*token_cursor != '\0') { + c_size_t next_char_len = c_utf8_char_len(*token_cursor); + c_size_t match_delim_len = 0; + + if (c_utf8_is_delim(token_cursor, delims, &match_delim_len)) { + // Found a boundary delimiter! Overwrite its leading byte with a null terminator + *token_cursor = '\0'; + // Save state context tracking pointing immediately past the clipped delimiter + *saveptr = token_cursor + match_delim_len; + return token_start; + } + token_cursor += next_char_len; + } + + // If we reached the end of the text string naturally, ensure the saveptr updates to string termination + *saveptr = token_cursor; + return token_start; +} + + +/** + * @brief Internal helper to return the uppercase variant value of a single ASCII or 2-byte UTF-8 character. + * Returns the original trailing byte structure if no case mapping applies. + */ +C_STATIC_FORCE_INLINE +void c_utf8_fold_char(const char* src, size_t len, unsigned char* out_b1, unsigned char* out_b2) { + *out_b1 = (unsigned char)src[0]; + *out_b2 = (len > 1) ? (unsigned char)src[1] : 0; + + // Single-byte ASCII case folding + if (len == 1) { + if (*out_b1 >= 'a' && *out_b1 <= 'z') { + *out_b1 -= 32; + } + } + // Double-byte UTF-8 case folding (Cyrillic & Greek scripts) + else if (len == 2) { + // Cyrillic script: 0xD0 0xB0...0xBF to 0xD0 0x90...0x9F; 0xD1 0x80...0x8F to 0xD0 0xA0...0xAF + if (*out_b1 == 0xD0) { + if (*out_b2 >= 0xB0 && *out_b2 <= 0xBF) { + *out_b2 -= 0x20; + } + } else if (*out_b1 == 0xD1) { + if (*out_b2 >= 0x80 && *out_b2 <= 0x8F) { + *out_b1 = 0xD0; + *out_b2 += 0x20; + } + } + // Greek script: 0xCE 0xB1...0xBF to 0xCE 0x91...0x9F; 0xCF 0x80...0x8B to 0xCE 0xA0...0xAB + else if (*out_b1 == 0xCE) { + if (*out_b2 >= 0xB1 && *out_b2 <= 0xBF) { + *out_b2 -= 0x20; + } + } else if (*out_b1 == 0xCF) { + if (*out_b2 >= 0x80 && *out_b2 <= 0x8B) { + *out_b1 = 0xCE; + *out_b2 += 0x20; + } + } + } +} + +int c_utf8_strncasecmp(const char* str1, const char* str2, c_size_t char_num) { + if (!str1 || !str2 || char_num == 0) return 0; + + c_size_t idx1 = 0; + c_size_t idx2 = 0; + c_size_t compared_chars = 0; + + while (compared_chars < char_num) { + // Handle termination boundaries gracefully + if (str1[idx1] == '\0' || str2[idx2] == '\0') { + return (int)((unsigned char)str1[idx1] - (unsigned char)str2[idx2]); + } + + c_size_t len1 = c_utf8_char_len(str1[idx1]); + c_size_t len2 = c_utf8_char_len(str2[idx2]); + + // Fold characters to uppercase form for comparison + unsigned char f1_b1, f1_b2; + unsigned char f2_b1, f2_b2; + c_utf8_fold_char(&str1[idx1], len1, &f1_b1, &f1_b2); + c_utf8_fold_char(&str2[idx2], len2, &f2_b1, &f2_b2); + + // Compare first bytes or script widths + if (f1_b1 != f2_b1) { + return (int)f1_b1 - (int)f2_b1; + } + + // Compare second bytes (relevant for 2-byte sequences) + if (f1_b2 != f2_b2) { + return (int)f1_b2 - (int)f2_b2; + } + + // For 3-byte (Chinese) or 4-byte characters, fallback to raw memory comparison if lead bytes matched + if (len1 > 2) { + if (len1 != len2) { + return (int)len1 - (int)len2; + } + int raw_res = memcmp(&str1[idx1], &str2[idx2], len1); + if (raw_res != 0) { + return raw_res; + } + } + + // Advance iteration offsets + idx1 += len1; + idx2 += len2; + compared_chars++; + } + + return 0; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_utf8_to_unicode(const char* str, c_ucs4_t* out_codepoint, c_size_t* out_bytes_consumed) { + if (!str || *str == '\0' || !out_codepoint || !out_bytes_consumed) { + return C_ERR_PARAM; + } + + unsigned char b1 = (unsigned char)str[0]; + c_size_t len = c_utf8_char_len(str[0]); + c_ucs4_t cp = 0; + + // Case 1: 1-Byte ASCII (0xxxxxxx) + if (len == 1) { + if (b1 >= 0x80) return C_ERR_PARAM; // Guard against malformed lead bytes + cp = b1; + } + // Case 2: 2-Byte Sequence (110xxxxx 10xxxxxx) + else if (len == 2) { + unsigned char b2 = (unsigned char)str[1]; + if ((b2 & 0xC0) != 0x80) return C_ERR_PARAM; // Validate continuation byte + + cp = ((b1 & 0x1F) << 6) | (b2 & 0x3F); + if (cp < 0x80) return C_ERR_PARAM; // Overlong encoding defense + } + // Case 3: 3-Byte Sequence (1110xxxx 10xxxxxx 10xxxxxx) + else if (len == 3) { + unsigned char b2 = (unsigned char)str[1]; + unsigned char b3 = (unsigned char)str[2]; + if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) return C_ERR_PARAM; + + cp = ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F); + if (cp < 0x0800) return C_ERR_PARAM; // Overlong encoding defense + if (cp >= 0xD800 && cp <= 0xDFFF) return C_ERR_PARAM; // Surrogate pairs rejection + } + // Case 4: 4-Byte Sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx) + else if (len == 4) { + unsigned char b2 = (unsigned char)str[1]; + unsigned char b3 = (unsigned char)str[2]; + unsigned char b4 = (unsigned char)str[3]; + if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 || (b4 & 0xC0) != 0x80) return C_ERR_PARAM; + + cp = ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F); + if (cp < 0x010000) return C_ERR_PARAM; // Overlong encoding defense + } + else { + return C_ERR_PARAM; + } + + // Limit validation (Unicode max standard is U+10FFFF) + if (cp > 0x10FFFF) { + return C_ERR_PARAM; + } + + *out_codepoint = cp; + *out_bytes_consumed = len; + return C_ERR_OK; +} + + +c_err_t c_utf8_from_unicode(c_ucs4_t codepoint, char* dest_buffer, c_size_t* out_bytes_written) { + if (!dest_buffer || !out_bytes_written) { + return C_ERR_PARAM; + } + + // Reject out-of-range codepoints or UTF-16 surrogate pairs (reserved for UTF-16 only) + if (codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + return C_ERR_PARAM; + } + + // Case 1: Standard ASCII range (U+0000 to U+007F) -> Requires 1 byte + if (codepoint <= 0x7F) { + dest_buffer[0] = (char)codepoint; + *out_bytes_written = 1; + } + // Case 2: U+0080 to U+07FF -> Requires 2 bytes + else if (codepoint <= 0x7FF) { + dest_buffer[0] = (char)(0xC0 | ((codepoint >> 6) & 0x1F)); + dest_buffer[1] = (char)(0x80 | (codepoint & 0x3F)); + *out_bytes_written = 2; + } + // Case 3: U+0800 to U+FFFF -> Requires 3 bytes (Handles most Chinese characters) + else if (codepoint <= 0xFFFF) { + dest_buffer[0] = (char)(0xE0 | ((codepoint >> 12) & 0x0F)); + dest_buffer[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + dest_buffer[2] = (char)(0x80 | (codepoint & 0x3F)); + *out_bytes_written = 3; + } + // Case 4: U+10000 to U+10FFFF -> Requires 4 bytes (Handles Emojis and ancient scripts) + else { + dest_buffer[0] = (char)(0xF0 | ((codepoint >> 18) & 0x07)); + dest_buffer[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F)); + dest_buffer[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + dest_buffer[3] = (char)(0x80 | (codepoint & 0x3F)); + *out_bytes_written = 4; + } + + // Securely seal the local buffer layout array with a trailing null terminator + dest_buffer[*out_bytes_written] = '\0'; + + return C_ERR_OK; +} + + +c_err_t c_utf8_to_unicode_array(const char* str, c_ucs4_t* dest_array, c_size_t array_capacity, c_size_t* out_chars_written) { + if (!str || !dest_array || !out_chars_written) { + return C_ERR_PARAM; + } + + c_size_t src_idx = 0; + c_size_t chars_count = 0; + + while (str[src_idx] != '\0') { + // Enforce array capacity threshold constraints + if (chars_count >= array_capacity) { + *out_chars_written = chars_count; + return C_ERR_PARAM; // Destination array is too small to fit the remaining string + } + + c_ucs4_t cp = 0; + c_size_t bytes_consumed = 0; + + // Decode the single character point via your core decoding function + c_err_t err = c_utf8_to_unicode(&str[src_idx], &cp, &bytes_consumed); + if (err != C_ERR_OK) { + *out_chars_written = chars_count; + return err; // Propagate the malformed stream error up + } + + dest_array[chars_count++] = cp; + src_idx += bytes_consumed; + } + + *out_chars_written = chars_count; + return C_ERR_OK; +} + +c_err_t c_utf8_from_unicode_array(const c_ucs4_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written) { + if (!src_array || !dest_buffer || !out_bytes_written) { + return C_ERR_PARAM; + } + + c_size_t dest_idx = 0; + + for (c_size_t i = 0; i < src_array_len; i++) { + char temp_char_buf[5]; // Temporary standalone slot buffer + c_size_t bytes_written = 0; + + // Encode single code point state back to byte wrappers + c_err_t err = c_utf8_from_unicode(src_array[i], temp_char_buf, &bytes_written); + if (err != C_ERR_OK) { + *out_bytes_written = dest_idx; + return err; + } + + // Verify if destination capacity bounds can hold the new character block (+1 for terminal null) + if (dest_idx + bytes_written + 1 > dest_capacity) { + *out_bytes_written = dest_idx; + dest_buffer[dest_idx] = '\0'; // Gracefully terminate the current chunk before failing + return C_ERR_PARAM; + } + + // Copy raw encoded data bytes into our tracking stream layout + memcpy(dest_buffer + dest_idx, temp_char_buf, bytes_written); + dest_idx += bytes_written; + } + + // Force strict trailing character termination closure + dest_buffer[dest_idx] = '\0'; + *out_bytes_written = dest_idx; + + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + + +c_err_t c_utf8_to_utf16(const char* str, c_uint16_t* dest_array, c_size_t array_capacity, c_size_t* out_units_written) { + if (!str || !dest_array || !out_units_written) { + return C_ERR_PARAM; + } + + c_size_t src_idx = 0; + c_size_t units_count = 0; + + while (str[src_idx] != '\0') { + c_ucs4_t cp = 0; + c_size_t bytes_consumed = 0; + + // 1. Decode the UTF-8 sequence into a Unicode codepoint + c_err_t err = c_utf8_to_unicode(&str[src_idx], &cp, &bytes_consumed); + if (err != C_ERR_OK) { + *out_units_written = units_count; + return err; + } + + // 2. Encode the codepoint into UTF-16 + if (cp <= 0xFFFF) { + // BMP Range: Requires exactly one 16-bit code unit + if (units_count + 1 >= array_capacity) { + *out_units_written = units_count; + return C_ERR_PARAM; // Out of bounds + } + dest_array[units_count++] = (c_uint16_t)cp; + } else { + // Supplementary Planes (Astral): Requires a surrogate pair (two 16-bit units) + if (units_count + 2 >= array_capacity) { + *out_units_written = units_count; + return C_ERR_PARAM; // Out of bounds + } + cp -= 0x10000; + dest_array[units_count++] = (c_uint16_t)(0xD800 | ((cp >> 10) & 0x3FF)); // High Surrogate + dest_array[units_count++] = (c_uint16_t)(0xDC00 | (cp & 0x3FF)); // Low Surrogate + } + + src_idx += bytes_consumed; + } + + // Append the trailing null terminator to make it a valid UTF-16 string + if (units_count < array_capacity) { + dest_array[units_count] = 0; + } else { + *out_units_written = units_count; + return C_ERR_PARAM; + } + + *out_units_written = units_count; + return C_ERR_OK; +} + +c_err_t c_utf8_from_utf16(const c_uint16_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written) { + if (!src_array || !dest_buffer || !out_bytes_written) { + return C_ERR_PARAM; + } + + c_size_t src_idx = 0; + c_size_t dest_idx = 0; + + while (src_idx < src_array_len) { + c_ucs4_t cp = 0; + c_uint16_t u1 = src_array[src_idx++]; + + // 1. Decode UTF-16 to Unicode codepoint + if (u1 >= 0xD800 && u1 <= 0xDBFF) { + // High surrogate detected, look ahead for the matching low surrogate + if (src_idx >= src_array_len) { + *out_bytes_written = dest_idx; + return C_ERR_PARAM; // Truncated/Malformed surrogate pair + } + c_uint16_t u2 = src_array[src_idx++]; + if (u2 < 0xDC00 || u2 > 0xDFFF) { + *out_bytes_written = dest_idx; + return C_ERR_PARAM; // Missing or invalid low surrogate + } + cp = (((u1 & 0x3FF) << 10) | (u2 & 0x3FF)) + 0x10000; + } else if (u1 >= 0xDC00 && u1 <= 0xDFFF) { + // Isolated low surrogate is invalid in a lead position + *out_bytes_written = dest_idx; + return C_ERR_PARAM; + } else { + // Normal BMP character + cp = u1; + } + + // 2. Encode the Unicode codepoint back into the destination UTF-8 buffer + char temp_buf[5]; + c_size_t bytes_written = 0; + c_err_t err = c_utf8_from_unicode(cp, temp_buf, &bytes_written); + if (err != C_ERR_OK) { + *out_bytes_written = dest_idx; + return err; + } + + // Verify if destination capacity bounds can hold the new block (+1 for terminal null) + if (dest_idx + bytes_written + 1 > dest_capacity) { + *out_bytes_written = dest_idx; + dest_buffer[dest_idx] = '\0'; + return C_ERR_PARAM; // Overflow protection + } + + memcpy(dest_buffer + dest_idx, temp_buf, bytes_written); + dest_idx += bytes_written; + } + + // Force strict trailing character termination closure + dest_buffer[dest_idx] = '\0'; + *out_bytes_written = dest_idx; + + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_utf16_swap_endian(c_uint16_t* utf16_array, c_size_t length) { + if (!utf16_array) { + return C_ERR_PARAM; + } + + for (c_size_t i = 0; i < length; i++) { + c_uint16_t value = utf16_array[i]; + // Bitwise swap: (value >> 8) extracts the high byte, (value << 8) extracts the low byte + utf16_array[i] = (c_uint16_t)(((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)); + } + + return C_ERR_OK; +} + + +c_err_t c_utf16_to_unicode(const c_uint16_t* src_units, c_size_t src_capacity, c_ucs4_t* out_codepoint, c_size_t* out_units_read) { + if (!src_units || src_capacity == 0 || !out_codepoint || !out_units_read) { + return C_ERR_PARAM; + } + + c_uint16_t u1 = src_units[0]; + + // Case 1: High Surrogate Point Detection (0xD800 to 0xDBFF) + if (u1 >= 0xD800 && u1 <= 0xDBFF) { + if (src_capacity < 2) { + return C_ERR_PARAM; // Truncated sequence: expected a matching low surrogate + } + + c_uint16_t u2 = src_units[1]; + if (u2 < 0xDC00 || u2 > 0xDFFF) { + return C_ERR_PARAM; // Malformed sequence: missing a valid trailing low surrogate + } + + // Reconstruct Astral Plane Codepoint via formula: ((High - 0xD800) << 10) + (Low - 0xDC00) + 0x10000 + *out_codepoint = (c_ucs4_t)((((u1 & 0x3FF) << 10) | (u2 & 0x3FF)) + 0x10000); + *out_units_read = 2; + } + // Case 2: Isolated Low Surrogate Error Check (0xDC00 to 0xDFFF) + else if (u1 >= 0xDC00 && u1 <= 0xDFFF) { + return C_ERR_PARAM; // Isolated low surrogate is mathematically invalid in a lead position + } + // Case 3: Standard BMP Range Character + else { + *out_codepoint = (c_ucs4_t)u1; + *out_units_read = 1; + } + + return C_ERR_OK; +} + +c_err_t c_utf16_from_unicode(c_ucs4_t codepoint, c_uint16_t* dest_units, c_size_t dest_capacity, c_size_t* out_units_written) { + if (!dest_units || dest_capacity == 0 || !out_units_written) { + return C_ERR_PARAM; + } + + // Limit Validation: Reject invalid Astral values or illegal UTF-16 surrogate codepoint blocks + if (codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + return C_ERR_PARAM; + } + + // Case 1: BMP Range (U+0000 to U+FFFF) -> Requires 1 code unit + if (codepoint <= 0xFFFF) { + dest_units[0] = (c_uint16_t)codepoint; + *out_units_written = 1; + } + // Case 2: Supplementary Planes (U+10000 to U+10FFFF) -> Requires 2 code units (Surrogate Pair) + else { + if (dest_capacity < 2) { + return C_ERR_PARAM; // Insufficient buffer capacity + } + + c_ucs4_t adjusted = codepoint - 0x10000; + dest_units[0] = (c_uint16_t)(0xD800 | ((adjusted >> 10) & 0x3FF)); // High Surrogate + dest_units[1] = (c_uint16_t)(0xDC00 | (adjusted & 0x3FF)); // Low Surrogate + *out_units_written = 2; + } + + return C_ERR_OK; +} + + diff --git a/Foundation/c_utf8.h b/Foundation/c_utf8.h new file mode 100644 index 0000000..c374880 --- /dev/null +++ b/Foundation/c_utf8.h @@ -0,0 +1,242 @@ +#ifndef INCLUDED_C_UTF8_H +#define INCLUDED_C_UTF8_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +#ifndef C_UNICODE_MAX +#define C_UNICODE_MAX 0x10FFFFU +#endif + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/* --- Unicode Codepoint Type Definition --- */ +typedef uint32_t c_ucs4_t; // UCS-4 / UTF-32 representation for a single Unicode Codepoint +typedef uint16_t c_uint16_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +bool c_is_valid_unicode(uint32_t code) { + // Unicode 码点不能超过 0x10FFFF,且必须排除 UTF-16 代理对范围 (0xD800 ~ 0xDFFF) + if (code > C_UNICODE_MAX) return false; + if (code >= 0xD800 && code <= 0xDFFF) return false; + return true; +} + +/** + * @brief 获取一个 UTF-8 字符在当前指针位置所占用的实际字节数 (1 ~ 4 字节) + */ +C_STATIC_FORCE_INLINE c_size_t c_utf8_char_len(char leading_byte) { + unsigned char b = (unsigned char)leading_byte; + if (b < 0x80) return 1; // 单字节 ASCII: 0xxxxxxx + if ((b & 0xE0) == 0xC0) return 2; // 双字节字符: 110xxxxx + if ((b & 0xF0) == 0xE0) return 3; // 三字节字符(大部分汉字): 1110xxxx + if ((b & 0xF8) == 0xF0) return 4; // 四字节字符(Emoji等): 11110xxx + return 1; // 非法 UTF-8 引导字节,防御性返回 1 防止死循环 +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 计算一个 UTF-8 字符串的有效字形数(字符数),而非字节数 (兼容 strlen) + */ +c_size_t c_utf8_strlen(const char* str); + +/** + * @brief 查找字符在 UTF-8 字符串中第一次出现的位置 (兼容 strchr) + * @param str 源字符串 + * @param utf8_char 待查找的 UTF-8 字符(支持多字节字符,如 "中") + * @return const char* 指向找到的第一个字节的指针,未找到返回 NULL + */ +const char* c_utf8_strchr(const char* str, const char* utf8_char); + +/** + * @brief 复制指定字形数量的 UTF-8 字符串 (兼容 strncpy) + * @note 能够完美感知 UTF-8 字符边界,绝不会切断汉字,且自动在末尾补 '\0' + * @param dest 目标缓冲区 + * @param src 源字符串 + * @param char_num 要复制的 UTF-8 字符/字形数量 + * @return char* 指向目标缓冲区 dest 的指针 + */ +char* c_utf8_strncpy(char* dest, const char* src, c_size_t char_num); + +/** + * @brief 比较两个 UTF-8 字符串的前 n 个字符 (兼容 strncmp) + * @param str1 字符串 1 + * @param str2 字符串 2 + * @param char_num 要比较的 UTF-8 字符/字形数量 + * @return int 小于 0、等于 0 或大于 0 + */ +int c_utf8_strncmp(const char* str1, const char* str2, c_size_t char_num); + + +/** + * @brief Converts a UTF-8 character string to lowercase in-place. + * Supports standard ASCII case folding and common multi-byte scripts. + * @param str Pointer to the mutable null-terminated UTF-8 string. + * @return char* Pointer to the original string. + */ +char* c_utf8_tolower(char* str); + +/** + * @brief Converts a UTF-8 character string to uppercase in-place. + * Supports standard ASCII case folding and common multi-byte scripts. + * @param str Pointer to the mutable null-terminated UTF-8 string. + * @return char* Pointer to the original string. + */ +char* c_utf8_toupper(char* str); + +/** + * @brief Appends the source UTF-8 string to the destination string buffer (Compatible with strcat). + * @param dest Pointer to the null-terminated destination buffer. + * @param src Pointer to the null-terminated source string. + * @return char* Pointer to the destination string destination pointer. + */ +char* c_utf8_strcat(char* dest, const char* src); + +/** + * @brief Finds the first occurrence of a substring in a UTF-8 string (Compatible with strstr). + * @param haystack The null-terminated UTF-8 string to scan. + * @param needle The null-terminated UTF-8 substring to search for. + * @return const char* Pointer to the first byte of the matched substring in haystack, or NULL if not found. + */ +const char* c_utf8_strstr(const char* haystack, const char* needle); + +/** + * @brief Finds the last occurrence of a specific character in a UTF-8 string (Compatible with strrchr). + * @param str The null-terminated UTF-8 string to scan. + * @param utf8_char The null-terminated UTF-8 character string to find (can be a multi-byte sequence like "中"). + * @return const char* Pointer to the last occurrence of the matched character in str, or NULL if not found. + */ +const char* c_utf8_strrchr(const char* str, const char* utf8_char); + +/** + * @brief Tokenizes a string into a series of tokens based on multiple multi-byte delimiters. + * This function is thread-safe and reentrant, operating similarly to POSIX strtok_r. + * @param str The mutable UTF-8 string to tokenize. Pass NULL on subsequent calls. + * @param delims A raw byte sequence containing multi-byte UTF-8 delimiters. + * @param saveptr A user-allocated tracking pointer to maintain state context across consecutive calls. + * @return char* Pointer to the beginning of the next valid token, or NULL when no more tokens are found. + */ +char* c_utf8_strtok(char* str, const char* delims, char** saveptr); + +/** + * @brief Compares two UTF-8 strings case-insensitively up to a specified number of characters. + * @param str1 Pointer to the first null-terminated UTF-8 string. + * @param str2 Pointer to the second null-terminated UTF-8 string. + * @param char_num Maximum number of UTF-8 characters (codepoints) to compare. + * @return int An integer less than, equal to, or greater than zero if str1 is found, + * respectively, to be less than, to match, or be greater than str2. + */ +int c_utf8_strncasecmp(const char* str1, const char* str2, c_size_t char_num); + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Converts the next UTF-8 byte sequence at a given pointer into a single Unicode Codepoint. + * @param str Pointer to the current position in a null-terminated UTF-8 string. + * @param out_codepoint Pointer to the destination where the decoded Unicode integer is saved. + * @param out_bytes_consumed Pointer to save the number of source bytes processed (1 to 4). + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM on invalid/corrupted UTF-8 byte streams. + */ +c_err_t c_utf8_to_unicode(const char* str, c_ucs4_t* out_codepoint, c_size_t* out_bytes_consumed); + + +/** + * @brief Encodes a single Unicode Codepoint (UCS-4) into a destination UTF-8 byte array. + * @param codepoint The source Unicode integer character point to encode. + * @param dest_buffer Pointer to a char array buffer (must have at least 5 bytes capacity). + * @param out_bytes_written Pointer to save the number of encoded bytes stored in dest_buffer. + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM if the codepoint is out of valid Unicode ranges or parameters are NULL. + */ +c_err_t c_utf8_from_unicode(c_ucs4_t codepoint, char* dest_buffer, c_size_t* out_bytes_written); + +/** + * @brief Decodes an entire null-terminated UTF-8 string into an array of Unicode Codepoints. + * @param str Pointer to the null-terminated source UTF-8 string. + * @param dest_array Pointer to the destination array where decoded codepoints will be stored. + * @param array_capacity Maximum number of elements that dest_array can hold. + * @param out_chars_written Pointer to save the total number of codepoints successfully stored. + * @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid/NULL parameters or if the destination array capacity is exceeded. + */ +c_err_t c_utf8_to_unicode_array(const char* str, c_ucs4_t* dest_array, c_size_t array_capacity, c_size_t* out_chars_written); + +/** + * @brief Encodes an array of Unicode Codepoints back into a null-terminated UTF-8 byte stream. + * @param src_array Pointer to the source array of Unicode codepoints. + * @param src_array_len The number of codepoint elements inside src_array to process. + * @param dest_buffer Pointer to the destination char buffer. + * @param dest_capacity Maximum byte capacity of the destination buffer (including room for '\0'). + * @param out_bytes_written Pointer to save the total number of bytes written to dest_buffer (excluding '\0'). + * @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid/NULL parameters or if dest_capacity is exceeded. + */ +c_err_t c_utf8_from_unicode_array(const c_ucs4_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written); + +/** + * @brief Converts an entire null-terminated UTF-8 string into an array of UTF-16 code units. + * @param str Pointer to the null-terminated source UTF-8 string. + * @param dest_array Pointer to the destination array where UTF-16 code units will be stored. + * @param array_capacity Maximum number of 16-bit elements that dest_array can hold. + * @param out_units_written Pointer to save the total number of UTF-16 code units successfully stored (excluding terminal '\0'). + * @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid/NULL parameters or if capacity is exceeded. + */ +c_err_t c_utf8_to_utf16(const char* str, c_uint16_t* dest_array, c_size_t array_capacity, c_size_t* out_units_written); + +/** + * @brief Converts an array of UTF-16 code units back into a null-terminated UTF-8 byte stream. + * @param src_array Pointer to the source array of UTF-16 code units. + * @param src_array_len The number of 16-bit elements inside src_array to process. + * @param dest_buffer Pointer to the destination char buffer. + * @param dest_capacity Maximum byte capacity of the destination buffer (including room for '\0'). + * @param out_bytes_written Pointer to save the total number of bytes written to dest_buffer (excluding '\0'). + * @return c_err_t C_ERR_OK on success, C_ERR_PARAM on invalid/NULL parameters, malformed surrogates, or if dest_capacity is exceeded. + */ +c_err_t c_utf8_from_utf16(const c_uint16_t* src_array, c_size_t src_array_len, char* dest_buffer, c_size_t dest_capacity, c_size_t* out_bytes_written); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Swaps the byte order (endianness) of a UTF-16 string array in-place. + * @param utf16_array Pointer to the source/destination array of UTF-16 code units. + * @param length The number of 16-bit elements inside the array to process. + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM if the array pointer is NULL. + */ +c_err_t c_utf16_swap_endian(c_uint16_t* utf16_array, c_size_t length); + + +/** + * @brief Decodes a UTF-16 character stream starting at a given pointer into a single Unicode codepoint. + * @param src_units Pointer to the current code unit position in a UTF-16 array. + * @param src_capacity Remaining elements left available to read inside the source array. + * @param out_codepoint Pointer to the destination where the decoded Unicode integer is saved. + * @param out_units_read Pointer to save the number of 16-bit code units processed (1 for BMP, 2 for Surrogate Pairs). + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM on malformed surrogate sequences or missing parameters. + */ +c_err_t c_utf16_to_unicode(const c_uint16_t* src_units, c_size_t src_capacity, c_ucs4_t* out_codepoint, c_size_t* out_units_read); + +/** + * @brief Encodes a single Unicode codepoint into a target UTF-16 array buffer. + * @param codepoint The source Unicode integer character point to encode. + * @param dest_units Pointer to the destination 16-bit code unit array buffer. + * @param dest_capacity Maximum number of 16-bit elements the destination buffer can accept. + * @param out_units_written Pointer to save the number of 16-bit code units generated (1 or 2). + * @return c_err_t C_ERR_OK on success, or C_ERR_PARAM if the codepoint is invalid or destination space is insufficient. + */ +c_err_t c_utf16_from_unicode(c_ucs4_t codepoint, c_uint16_t* dest_units, c_size_t dest_capacity, c_size_t* out_units_written); + + + +#endif /*INCLUDED_C_UTF8_H*/ diff --git a/Foundation/c_utf8.t.c b/Foundation/c_utf8.t.c new file mode 100644 index 0000000..aedcaec --- /dev/null +++ b/Foundation/c_utf8.t.c @@ -0,0 +1,302 @@ +#include "c_utf8.h" +#include +#include + +/* --- 单元测试模块集成 --- */ +#define RUN_TEST(test_case, name) \ + do { \ + printf("[RUN] %s... ", name); \ + if (test_case) { \ + printf("\033[32mPASSED\033[0m\n"); \ + } else { \ + printf("\033[31mFAILED\033[0m (%s:%d)\n", __FILE__, __LINE__); \ + return C_ERR_FAIL; \ + } \ + } while(0) + +c_err_t c_Utf8String_UnitTest(void) { + printf("==================================================\n"); + printf(" STARTING C_UTF8STRING UNIT TESTING \n"); + printf("==================================================\n"); + + const char* sample = "NLP大模型_2026"; // 包含 3个大写英文、3个汉字、1个下划线、4个数字 = 11个字符 + + /* 1. c_utf8_strlen 测试 */ + // 传统的 strlen(sample) 会返回 3 + 3*3 + 1 + 4 = 17 字节 + RUN_TEST(c_utf8_strlen(sample) == 11, "c_utf8_strlen correctly counts character points"); + RUN_TEST(c_utf8_strlen("") == 0, "c_utf8_strlen handles empty strings"); + + /* 2. c_utf8_strchr 测试 */ + const char* find_eng = c_utf8_strchr(sample, "P"); + const char* find_chn = c_utf8_strchr(sample, "模"); + const char* find_none = c_utf8_strchr(sample, "国"); + + RUN_TEST(find_eng != NULL && *find_eng == 'P', "c_utf8_strchr locate ASCII element"); + // "模" 在 "模型_2026" 头部,其后紧跟 "型" + RUN_TEST(find_chn != NULL && strncmp(find_chn, "模型", 6) == 0, "c_utf8_strchr locate multi-byte Chinese word"); + RUN_TEST(find_none == NULL, "c_utf8_strchr returns NULL for non-existing chars"); + + /* 3. c_utf8_strncpy 安全截断测试 */ + char dest_buf[64]; + // 截断前 5 个字符 -> "NLP大模" (绝不会出现半个汉字或乱码断裂) + c_utf8_strncpy(dest_buf, sample, 5); + RUN_TEST(c_utf8_strlen(dest_buf) == 5, "c_utf8_strncpy slices correct character width"); + RUN_TEST(strcmp(dest_buf, "NLP大模") == 0, "c_utf8_strncpy safe boundary isolation checked"); + + /* 4. c_utf8_strncmp 字符匹配测试 */ + RUN_TEST(c_utf8_strncmp("自然语言", "自然选择", 2) == 0, "c_utf8_strncmp matches first 2 shared Chinese words"); + RUN_TEST(c_utf8_strncmp("自然语言", "自然选择", 3) != 0, "c_utf8_strncmp detects variance at 3rd word slot"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + /* 5. Lowercase Transform Verification (c_utf8_tolower) */ + char case_buf[64] = "NLP大模型_2026_Go!"; + c_utf8_tolower(case_buf); + RUN_TEST(strcmp(case_buf, "nlp大模型_2026_go!") == 0, "c_utf8_tolower translates ASCII while isolating Chinese layout characters"); + + // Cyrillic multi-byte letter test ("П" -> 0xD0 0x9F converted down to "п" -> 0xD0 0xBF) + char cyrillic_buf[8] = { (char)0xD0, (char)0x9F, '\0' }; + c_utf8_tolower(cyrillic_buf); + RUN_TEST((unsigned char)cyrillic_buf[1] == 0xBF, "c_utf8_tolower successfully transforms multi-byte Cyrillic characters"); + + /* 6. Concatenation Verification (c_utf8_strcat) */ + char cat_dest[32] = "自然"; + c_utf8_strcat(cat_dest, "语言"); + RUN_TEST(strcmp(cat_dest, "自然语言") == 0, "c_utf8_strcat appends string tokens cleanly"); + RUN_TEST(c_utf8_strlen(cat_dest) == 4, "Post-concatenation size checks out at 4 characters total"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 24. Uppercase Transform Verification (c_utf8_toupper) */ + char upper_buf[] = "nlp大模型_2026_go!"; + c_utf8_toupper(upper_buf); + RUN_TEST(strcmp(upper_buf, "NLP大模型_2026_GO!") == 0, "c_utf8_toupper translates ASCII while isolating Chinese layout characters"); + + // Cyrillic multi-byte lowercase letter test ("п" -> 0xD0 0xBF converted up to "П" -> 0xD0 0x9F) + char cyr_upper_buf[] = { (char)0xD0, (char)0xBF, '\0' }; + c_utf8_toupper(cyr_upper_buf); + RUN_TEST((unsigned char)cyr_upper_buf[1] == 0x9F, "c_utf8_toupper successfully transforms multi-byte Cyrillic lowercase characters"); + + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 7. Character Scanning Verification (c_utf8_strchr) */ + const char* scan_base = "NLP大模型_2026"; + + /* 8. Substring Scanning Verification (c_utf8_strstr) */ + const char* match_str = c_utf8_strstr(scan_base, "大模型"); + const char* miss_str = c_utf8_strstr(scan_base, "小模型"); + const char* empty_str = c_utf8_strstr(scan_base, ""); + + RUN_TEST(match_str != NULL && strncmp(match_str, "大模型_2026", 14) == 0, "c_utf8_strstr fetches multi-byte string locations"); + RUN_TEST(miss_str == NULL, "c_utf8_strstr returns NULL cleanly on substring mismatch"); + RUN_TEST(empty_str == scan_base, "c_utf8_strstr returns parent head context given empty needle input"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 16. Reverse Scanning Verification (c_utf8_strrchr) */ + const char* r_base = "自然_模型_自然_2026"; + const char* last_match = c_utf8_strrchr(r_base, "自然"); + const char* first_match = c_utf8_strchr(r_base, "自然"); + + RUN_TEST(last_match != NULL && last_match != first_match, "c_utf8_strrchr skips the first match to pull the last occurrence"); + RUN_TEST(strncmp(last_match, "自然_2026", 11) == 0, "c_utf8_strrchr locates the correct trailing block address"); + + /* 17. Reentrant Tokenization Verification (c_utf8_strtok) */ + char token_source[] = ",NLP,,大模型,2026,"; // Multi-byte Chinese comma delimiters + const char* delimiters = ","; + char* save_context = NULL; + + // First Call + char* token = c_utf8_strtok(token_source, delimiters, &save_context); + RUN_TEST(token != NULL && strcmp(token, "NLP") == 0, "c_utf8_strtok parses the first token and skips leading delims"); + + // Second Call (Pass NULL to proceed) + token = c_utf8_strtok(NULL, delimiters, &save_context); + RUN_TEST(token != NULL && strcmp(token, "大模型") == 0, "c_utf8_strtok correctly extracts multi-byte Chinese '大模型'"); + + // Third Call + token = c_utf8_strtok(NULL, delimiters, &save_context); + RUN_TEST(token != NULL && strcmp(token, "2026") == 0, "c_utf8_strtok extracts '2026'"); + + // Fourth Call - Termination + token = c_utf8_strtok(NULL, delimiters, &save_context); + RUN_TEST(token == NULL, "c_utf8_strtok returns NULL cleanly when parsing is finished"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 25. Case-Insensitive Bounded Comparison Verification (c_utf8_strncasecmp) */ + // Test Case 1: Simple matching mixed-case strings + RUN_TEST(c_utf8_strncasecmp("Nlp大模型", "NLP大模型", 6) == 0, "c_utf8_strncasecmp matches mixed cases up to 6 characters"); + + // Test Case 2: Verification of prefix character restrictions bounding + RUN_TEST(c_utf8_strncasecmp("NLP大模型_v2", "nlp大模型_v3", 6) == 0, "c_utf8_strncasecmp returns 0 if differences fall past the character count limit"); + RUN_TEST(c_utf8_strncasecmp("NLP大模型_v2", "nlp大模型_v3", 9) != 0, "c_utf8_strncasecmp registers structural variance when character limits cover differences"); + + // Test Case 3: Mixed language case sorting behavior + RUN_TEST(c_utf8_strncasecmp("自然语言NLP", "自然语言nlp", 7) == 0, "c_utf8_strncasecmp handles matching trailing ASCII case differences after multi-byte blocks"); + + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 18. UTF-8 to Unicode Codepoint Decoding Verification */ + const char* utf8_src = "中!🚀"; // "中" (3 bytes), "!" (1 byte), "🚀" Emoji (4 bytes) + c_ucs4_t cp = 0; + c_size_t bytes_step = 0; + + // Decode first character ("中" -> Expected Codepoint: U+4E2D) + c_err_t err = c_utf8_to_unicode(utf8_src, &cp, &bytes_step); + RUN_TEST(err == C_ERR_OK && bytes_step == 3, "c_utf8_to_unicode processes 3-byte Chinese characters"); + RUN_TEST(cp == 0x4E2D, "Decoded codepoint matches U+4E2D ('中') accurately"); + + // Decode next sequence ("!" -> Expected Codepoint: U+0021) + err = c_utf8_to_unicode(utf8_src + bytes_step, &cp, &bytes_step); + RUN_TEST(err == C_ERR_OK && bytes_step == 1, "c_utf8_to_unicode processes 1-byte ASCII markers"); + RUN_TEST(cp == 0x0021, "Decoded codepoint matches U+0021 ('!')"); + + // Decode next sequence (Rocket Emoji "🚀" -> Expected Codepoint: U+1F680) + err = c_utf8_to_unicode(utf8_src + 4, &cp, &bytes_step); // Skip forward 4 bytes total + RUN_TEST(err == C_ERR_OK && bytes_step == 4, "c_utf8_to_unicode decodes 4-byte astral plane emojis"); + RUN_TEST(cp == 0x1F680, "Decoded codepoint matches U+1F680 ('🚀')"); + + /* 19. Unicode Codepoint to UTF-8 Encoding Verification */ + char encode_buf[8]; + c_size_t written_len = 0; + + // Encode U+4E2D back to UTF-8 + err = c_utf8_from_unicode(0x4E2D, encode_buf, &written_len); + RUN_TEST(err == C_ERR_OK && written_len == 3, "c_utf8_from_unicode encodes U+4E2D back into 3 bytes"); + RUN_TEST(strcmp(encode_buf, "中") == 0, "Encoded string content matches '中' flawlessly"); + + // Encode U+1F680 back to UTF-8 + err = c_utf8_from_unicode(0x1F680, encode_buf, &written_len); + RUN_TEST(err == C_ERR_OK && written_len == 4, "c_utf8_from_unicode encodes U+1F680 back into 4 bytes"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 20. UTF-8 String Stream <-> Unicode Array Conversions Verification */ + const char* mixed_sentence = "NLP大模型!🚀"; // Length: 3 ASCII, 3 Chinese (9B), 1 ASCII, 1 Emoji (4B) = 8 characters total + c_ucs4_t uni_array[16]; + c_size_t total_chars = 0; + + // Test Point 1: Decode stream into codepoint container array + err = c_utf8_to_unicode_array(mixed_sentence, uni_array, 16, &total_chars); + RUN_TEST(err == C_ERR_OK && total_chars == 8, "c_utf8_to_unicode_array maps complex string streams into separate integer points"); + RUN_TEST(uni_array[0] == 'N' && uni_array[3] == 0x5927 && uni_array[7] == 0x1F680, "Decoded array positions hold proper character points ('N', '大', '🚀')"); + + // Test Point 2: Trigger array capacity guard protection + c_ucs4_t tight_array[4]; + err = c_utf8_to_unicode_array(mixed_sentence, tight_array, 4, &total_chars); + RUN_TEST(err == C_ERR_PARAM && total_chars == 4, "c_utf8_to_unicode_array safely blocks operations and returns current progress on buffer limit hits"); + + // Test Point 3: Reverse operation - Encode codepoint array back into native UTF-8 string layout + char reconstructed_str[64]; + c_size_t written_bytes = 0; + err = c_utf8_from_unicode_array(uni_array, 8, reconstructed_str, sizeof(reconstructed_str), &written_bytes); + RUN_TEST(err == C_ERR_OK && written_bytes == 17, "c_utf8_from_unicode_array successfully packs codepoints back into 17 raw bytes"); + RUN_TEST(strcmp(reconstructed_str, mixed_sentence) == 0, "Reconstructed stream data matches original expression perfectly"); + + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 21. UTF-8 <-> UTF-16 Array Conversions Verification */ + const char* utf8_sentence = "NLP大模型!🚀"; // Contains ASCII, 3-byte Chinese, and a 4-byte Astral Plane Emoji + c_uint16_t utf16_array[32]; + c_size_t units_written = 0; + + // Test Point 1: Convert UTF-8 stream to UTF-16 code units + // "NLP" (3 units) + "大模型" (3 units) + "!" (1 unit) + "🚀" (Surrogate pair = 2 units) = 9 units total + err = c_utf8_to_utf16(utf8_sentence, utf16_array, 32, &units_written); + RUN_TEST(err == C_ERR_OK && units_written == 9, "c_utf8_to_utf16 successfully packs characters including astral planes"); + RUN_TEST(utf16_array[0] == 'N' && utf16_array[3] == 0x5927, "Verify standard BMP mapping inside UTF-16 array structure"); + // Verify high and low surrogate points for the Rocket Emoji 🚀 + RUN_TEST(utf16_array[7] == 0xD83D && utf16_array[8] == 0xDE80, "Verify surrogate pair matching (0xD83D 0xDE80) for U+1F680"); + + // Test Point 2: Convert UTF-16 array back to native UTF-8 string layout + char reconstructed_utf8[64]; + c_size_t bytes_written_utf8 = 0; + err = c_utf8_from_utf16(utf16_array, units_written, reconstructed_utf8, sizeof(reconstructed_utf8), &bytes_written_utf8); + RUN_TEST(err == C_ERR_OK && bytes_written_utf8 == 17, "c_utf8_from_utf16 unpacks units back into 17 raw bytes"); + RUN_TEST(strcmp(reconstructed_utf8, utf8_sentence) == 0, "Reconstructed UTF-8 matches the original string precisely"); + + // Test Point 3: Malformed Surrogate Pair Detection + c_uint16_t malformed_utf16[] = { 0xD83D, 'A' }; // High surrogate followed by a literal letter (invalid) + char error_buf[16]; + c_size_t err_bytes = 0; + err = c_utf8_from_utf16(malformed_utf16, 2, error_buf, sizeof(error_buf), &err_bytes); + RUN_TEST(err == C_ERR_PARAM, "c_utf8_from_utf16 successfully flags and rejects malformed/orphaned surrogate code units"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 22. UTF-16 Endianness Byte-Swapping Verification (c_utf16_swap_endian) */ + c_uint16_t sample_utf16[] = { 0xD83D, 0xDE80, 0x5927 }; // 🚀 and 大 in standard host endianness + c_size_t array_len = sizeof(sample_utf16) / sizeof(sample_utf16[0]); + + // Test Point 1: Guard against NULL parameters + RUN_TEST(c_utf16_swap_endian(NULL, array_len) == C_ERR_PARAM, "c_utf16_swap_endian safely rejects NULL pointer arrays"); + + // Test Point 2: Perform initial byte-swap transformation + err = c_utf16_swap_endian(sample_utf16, array_len); + RUN_TEST(err == C_ERR_OK, "c_utf16_swap_endian executes successfully"); + // 0xD83D -> 0x3DD8, 0xDE80 -> 0x80DE, 0x5927 -> 0x2759 + RUN_TEST(sample_utf16[0] == 0x3DD8, "First code unit correctly bit-swapped (0xD83D -> 0x3DD8)"); + RUN_TEST(sample_utf16[1] == 0x80DE, "Second code unit correctly bit-swapped (0xDE80 -> 0x80DE)"); + RUN_TEST(sample_utf16[2] == 0x2759, "Third code unit correctly bit-swapped (0x5927 -> 0x2759)"); + + // Test Point 3: Swap back to restore the original host endianness values + err = c_utf16_swap_endian(sample_utf16, array_len); + RUN_TEST(err == C_ERR_OK, "c_utf16_swap_endian reverts state back on secondary execution pass"); + RUN_TEST(sample_utf16[0] == 0xD83D && sample_utf16[1] == 0xDE80 && sample_utf16[2] == 0x5927, "Original internal value contexts perfectly preserved"); + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + /* 23. UTF-16 Single-Char Unicode Conversions Verification */ + c_ucs4_t decoded_cp = 0; + c_size_t units_moved = 0; + + // Test Point 1: Decode Standard BMP Character ('大' -> U+5927) + c_uint16_t bmp_sample[] = { 0x5927 }; + err = c_utf16_to_unicode(bmp_sample, 1, &decoded_cp, &units_moved); + RUN_TEST(err == C_ERR_OK && units_moved == 1, "c_utf16_to_unicode processes BMP code units"); + RUN_TEST(decoded_cp == 0x5927, "Decoded BMP matches expected U+5927 successfully"); + + // Test Point 2: Decode Surrogate Pair Character (Rocket Emoji '🚀' -> High: 0xD83D, Low: 0xDE80) + c_uint16_t astral_sample[] = { 0xD83D, 0xDE80 }; + err = c_utf16_to_unicode(astral_sample, 2, &decoded_cp, &units_moved); + RUN_TEST(err == C_ERR_OK && units_moved == 2, "c_utf16_to_unicode correctly handles surrogate pairs"); + RUN_TEST(decoded_cp == 0x1F680, "Decoded Astral match returns U+1F680 ('🚀')"); + + // Test Point 3: Error validation protection against malformed surrogate chains + c_uint16_t isolated_high[] = { 0xD83D }; // Lacks matching trailing block + RUN_TEST(c_utf16_to_unicode(isolated_high, 1, &decoded_cp, &units_moved) == C_ERR_PARAM, "c_utf16_to_unicode rejects truncated surrogate sequences"); + + // Test Point 4: Encode Astral Plane back to UTF-16 code units + c_uint16_t encode_units[2]; + units_written = 0; + err = c_utf16_from_unicode(0x1F680, encode_units, 2, &units_written); + RUN_TEST(err == C_ERR_OK && units_written == 2, "c_utf16_from_unicode builds surrogate pairs for plane codepoints"); + RUN_TEST(encode_units[0] == 0xD83D && encode_units[1] == 0xDE80, "Generated high and low values match standard encoding targets"); + + + /* ------------------------------------------------------------------------------------------------------------------ */ + /* */ + + printf("==================================================\n"); + printf("\033[32mSUCCESS: ALL UTF-8 COMPATIBLE INTERFACES PASSED!\033[0m\n"); + printf("==================================================\n"); + return C_ERR_OK; +} + + +int main(int argc, char** argv){ + return c_Utf8String_UnitTest(); +} diff --git a/Foundation/c_utf8_file.c b/Foundation/c_utf8_file.c new file mode 100644 index 0000000..dbe6328 --- /dev/null +++ b/Foundation/c_utf8_file.c @@ -0,0 +1,208 @@ +#include + +#include +#include + +c_err_t c_utf8_file_read(const char* filepath, c_StringBuffer_t* out_sb) { + if (!filepath || !out_sb || !out_sb->buffer) { + return C_ERR_PARAM; + } + + FILE* file = fopen(filepath, "rb"); // Open in binary mode to prevent Windows crlf translations + if (!file) { + return C_ERR_FAIL; + } + + // Step 1: Detect and handle the optional 3-byte UTF-8 BOM sequence + unsigned char bom[3]; + c_size_t bom_read = fread(bom, 1, 3, file); + c_bool_t has_bom = C_FALSE; + + if (bom_read == 3 && bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF) { + has_bom = C_TRUE; // BOM sequence matched; file pointer is positioned right past it + } else { + // No BOM found; rewind file pointer back to the absolute beginning of the stream + fseek(file, 0, SEEK_SET); + } + + // Step 2: Read data sequentially via stream chunking loops + char read_chunk[1024]; + c_size_t bytes_read = 0; + c_size_t partial_offset = 0; + + while ((bytes_read = fread(read_chunk + partial_offset, 1, sizeof(read_chunk) - partial_offset, file)) > 0) { + c_size_t total_available_bytes = bytes_read + partial_offset; + c_size_t valid_process_boundary = total_available_bytes; + + // Verify that the chunk boundary does not break a multi-byte character in half. + // Look back from the absolute end of the chunk to catch multi-byte headers. + if (read_chunk[total_available_bytes - 1] & 0x80) { + c_size_t lookback = 1; + // Scan backward up to 4 bytes to find the leading byte of the fractured character + while (lookback <= 4 && lookback <= total_available_bytes) { + unsigned char b = (unsigned char)read_chunk[total_available_bytes - lookback]; + if ((b & 0xC0) == 0xC0) { // Found a multi-byte lead byte + c_size_t expected_len = c_utf8_char_len((char)b); + if (lookback < expected_len) { + // Character is indeed fractured; shrink chunk boundary to omit it + valid_process_boundary = total_available_bytes - lookback; + } + break; + } + if ((b & 0x80) == 0) { // Standard ASCII character, boundary is clean + break; + } + lookback++; + } + } + + // Pipe valid, cohesive text fragments into your dynamic string buffer tracker + if (valid_process_boundary > 0) { + c_err_t err = c_StringBuffer_Append(out_sb, read_chunk, valid_process_boundary); + if (err != C_ERR_OK) { + fclose(file); + return err; + } + } + + // Move remaining fractured bytes to the front of the next chunk buffer iteration pass + partial_offset = total_available_bytes - valid_process_boundary; + if (partial_offset > 0) { + memmove(read_chunk, read_chunk + valid_process_boundary, partial_offset); + } + } + + // Process residual bytes if the file stream terminates abruptly with an incomplete character sequence + if (partial_offset > 0) { + c_StringBuffer_Append(out_sb, read_chunk, partial_offset); + } + + fclose(file); + return C_ERR_OK; +} + +c_err_t c_utf8_file_write(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom) { + if (!filepath || !sb || !sb->buffer) { + return C_ERR_PARAM; + } + + FILE* file = fopen(filepath, "wb"); // Open in binary mode for precise byte preservation + if (!file) { + return C_ERR_FAIL; + } + + // Explicitly inject the UTF-8 BOM sequence if requested by the configuration parameter + if (write_bom) { + unsigned char bom[3] = {0xEF, 0xBB, 0xBF}; + if (fwrite(bom, 1, 3, file) != 3) { + fclose(file); + return C_ERR_FAIL; + } + } + + // Flush the string buffer's raw tracking payload into disk blocks + if (sb->size > 0) { + c_size_t written = fwrite(sb->buffer, 1, sb->size, file); + if (written != sb->size) { + fclose(file); + return C_ERR_FAIL; + } + } + + fclose(file); + return C_ERR_OK; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +c_err_t c_utf8_file_append(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom) { + if (!filepath || !sb || !sb->buffer) { + return C_ERR_PARAM; + } + + // Check if the file already exists by attempting to open it in read mode + FILE* check_file = fopen(filepath, "rb"); + c_bool_t file_exists = (check_file != NULL); + if (file_exists) { + fclose(check_file); + } + + // Open the file in append-binary mode + FILE* file = fopen(filepath, "ab"); + if (!file) { + return C_ERR_FAIL; + } + + // Write the BOM only if requested AND the file is brand new + if (write_bom && !file_exists) { + unsigned char bom[3] = {0xEF, 0xBB, 0xBF}; + if (fwrite(bom, 1, 3, file) != 3) { + fclose(file); + return C_ERR_FAIL; + } + } + + // Append the string buffer's raw tracking payload + if (sb->size > 0) { + c_size_t written = fwrite(sb->buffer, 1, sb->size, file); + if (written != sb->size) { + fclose(file); + return C_ERR_FAIL; + } + } + + fclose(file); + return C_ERR_OK; +} + +c_err_t c_utf8_file_readline(FILE* file, c_StringBuffer_t* out_line) { + if (!file || !out_line || !out_line->buffer) { + return C_ERR_PARAM; + } + + // Clear previous string buffer trackers to prepare for fresh line ingestion + c_StringBuffer_Clear(out_line); + + char read_chunk[256]; + c_bool_t data_extracted = C_FALSE; + long line_start_pos = ftell(file); + + while (fgets(read_chunk, sizeof(read_chunk), file) != NULL) { + data_extracted = C_TRUE; + c_size_t chunk_len = strlen(read_chunk); + + // Check if the chunk contains a newline character + char* newline_ptr = strchr(read_chunk, '\n'); + if (newline_ptr != NULL) { + // Calculate exact copy length up to the newline boundary + c_size_t copy_len = newline_ptr - read_chunk; + + if (copy_len > 0) { + // Strip carriage returns '\r' for safe cross-platform matching + if (read_chunk[copy_len - 1] == '\r') { + copy_len--; + } + } + + if (copy_len > 0) { + c_err_t err = c_StringBuffer_Append(out_line, read_chunk, copy_len); + if (err != C_ERR_OK) return err; + } + + return C_ERR_OK; // Line read complete + } + + // If no newline is found, the line is longer than our chunk; append everything and keep reading + c_err_t err = c_StringBuffer_Append(out_line, read_chunk, chunk_len); + if (err != C_ERR_OK) return err; + } + + // Handle end-of-file (EOF) state + if (data_extracted) { + return C_ERR_OK; // Returned the final trailing line containing no newline char + } + + return C_ERR_FAIL; // Reached EOF without extracting any data bytes +} + diff --git a/Foundation/c_utf8_file.h b/Foundation/c_utf8_file.h new file mode 100644 index 0000000..6f44364 --- /dev/null +++ b/Foundation/c_utf8_file.h @@ -0,0 +1,61 @@ +#ifndef INCLUDED_C_UTF8_FILE_H +#define INCLUDED_C_UTF8_FILE_H + +#ifndef INCLUDED_C_STRINGBUFFER_H +#include +#endif /*INCLUDED_C_STRINGBUFFER_H*/ + +#ifndef INCLUDED_C_UTF8_H +#include +#endif /*INCLUDED_C_UTF8_H*/ + +#ifndef INCLUDED_STDIO_H +#define INCLUDED_STDIO_H +#include +#endif /*INCLUDED_STDIO_H*/ + + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief Reads the entire contents of a UTF-8 text file into a string buffer structure. + * Automatically handles, validates, and skips the UTF-8 BOM marker if present. + * @param filepath Path to the target source file on disk. + * @param out_sb Pointer to a pre-initialized c_StringBuffer_t container to collect file data. + * @return c_err_t C_ERR_OK on complete success, C_ERR_PARAM on invalid inputs, or C_ERR_FAIL if file access throws errors. + */ +c_err_t c_utf8_file_read(const char* filepath, c_StringBuffer_t* out_sb); + +/** + * @brief Writes data from a string buffer out to a disk file using a UTF-8 text layout stream. + * @param filepath Path to the destination file on disk. + * @param sb Pointer to the source string buffer containing data. + * @param write_bom If set to C_TRUE, explicitly prefixes the file layout with the 3-byte UTF-8 BOM marker. + * @return c_err_t C_ERR_OK on complete success, C_ERR_PARAM on invalid inputs, or C_ERR_FAIL on disk write errors. + */ +c_err_t c_utf8_file_write(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom); + +/** + * @brief Appends text from a string buffer to a disk file. + * If the target file does not exist, it initializes it (with an optional BOM marker). + * @param filepath Path to the destination file on disk. + * @param sb Pointer to the source string buffer containing the append payload. + * @param write_bom If set to C_TRUE and the file is new, prefixes the stream with the 3-byte UTF-8 BOM. + * @return c_err_t C_ERR_OK on complete success, C_ERR_PARAM on invalid inputs, or C_ERR_FAIL on disk write errors. + */ +c_err_t c_utf8_file_append(const char* filepath, c_StringBuffer_t* sb, c_bool_t write_bom); + +/** + * @brief Reads a single line of text from an open file stream into a string buffer (Dynamic fgets). + * Automatically handles standard '\n' and '\r\n' line endings. + * @param file An active file stream pointer opened in binary read ("rb") mode. + * @param out_line Pointer to a pre-initialized c_StringBuffer_t container to collect the line string. + * @return c_err_t C_ERR_OK on successful line read, C_ERR_FAIL when reaching EOF with no data, or parameter errors. + */ +c_err_t c_utf8_file_readline(FILE* file, c_StringBuffer_t* out_line); + + +#endif /*INCLUDED_C_UTF8_FILE_H*/ diff --git a/Foundation/c_utf8_file.t.c b/Foundation/c_utf8_file.t.c new file mode 100644 index 0000000..ffc7b62 --- /dev/null +++ b/Foundation/c_utf8_file.t.c @@ -0,0 +1,116 @@ +#include "c_utf8_file.h" +#include +#include + +#define RUN_TEST(test_case, name) \ + do { \ + printf("[RUN] %s... ", name); \ + if (test_case) { \ + printf("\033[32mPASSED\033[0m\n"); \ + } else { \ + printf("\033[31mFAILED\033[0m (%s:%d)\n", __FILE__, __LINE__); \ + return C_ERR_FAIL; \ + } \ + } while(0) + +static c_err_t c_utf8_file_test(void) { + printf("==================================================\n"); + printf(" STARTING C_UTF8_FILE UNIT TESTING \n"); + printf("==================================================\n"); + + /* 26. UTF-8 File I/O Operations Verification */ + c_StringBuffer_t write_sb; + c_StringBuffer_t read_sb; + const char* test_filename = "nlp_utf8_test.txt"; + const char* payload = "NLP大模型_2026_🚀"; + + c_StringBuffer_Init(&write_sb, 32); + c_StringBuffer_Init(&read_sb, 32); + 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); + c_StringBuffer_Init(&line_sb, 32); + + // 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(); +}