From 5d634184458767dcd507ea7a7f9bc4211251e8ac Mon Sep 17 00:00:00 2001 From: Chen Peng Date: Sun, 30 Aug 2026 12:59:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=9F=BA=E7=A1=80=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Base/c_Types.h | 6 + Foundation/c_StringView.c | 207 ++++++++++++++++++++ Foundation/c_StringView.h | 165 ++++++++++++++++ Foundation/c_StringView.t.c | 366 ++++++++++++++++++++++++++++++++++++ 4 files changed, 744 insertions(+) create mode 100644 Foundation/c_StringView.c create mode 100644 Foundation/c_StringView.h create mode 100644 Foundation/c_StringView.t.c diff --git a/Base/c_Types.h b/Base/c_Types.h index 37ae2bc..1c6eb30 100644 --- a/Base/c_Types.h +++ b/Base/c_Types.h @@ -44,6 +44,12 @@ #include #endif /*INCLUDED_C_COMPILER_H*/ +#ifndef INCLUDED_CTYPE_H +#define INCLUDED_CTYPE_H +#include +#endif /*INCLUDED_CTYPE_H*/ + + /* ------------------------------------------------------------------------------------------------------------------ */ /* TYPES */ diff --git a/Foundation/c_StringView.c b/Foundation/c_StringView.c new file mode 100644 index 0000000..0ebd3e4 --- /dev/null +++ b/Foundation/c_StringView.c @@ -0,0 +1,207 @@ +#include +#include +#include + +char* c_StringView_ToCStr(const c_StringView_t* self, char* buf, c_size_t buf_size) { + // 边界防御:缓冲区必须有效且大小至少为 1(以便容纳 \0) + if (!buf || buf_size == 0) { + return NULL; + } + + // 边界防御:如果 self 为空或底层无数据,直接赋予空字符串并返回 + if (!self || self->size == 0 || !self->str) { + buf[0] = '\0'; + return buf; + } + + // 计算实际能够安全拷贝的数据字节数(预留 1 字节给 '\0') + c_size_t max_copy = buf_size - 1; + c_size_t actual_copy = (self->size < max_copy) ? self->size : max_copy; + + // 执行内存拷贝(允许中间包含 \0 字符的特殊视图) + if (actual_copy > 0) { + memcpy(buf, self->str, actual_copy); + } + + // 在缓冲区末尾强行补上标准传统 C 字符串的结束符 + buf[actual_copy] = '\0'; + + return buf; +} + +unsigned long c_StringView_ToUL(const c_StringView_t* self, const char** endptr, int base) { + if (!self || self->size == 0 || !self->str) { + if (endptr) *endptr = (self ? self->str : NULL); + return 0; + } + + const char* start = self->str; + const char* end = self->str + self->size; + + // 1. 跳过前导空白字符 + while (start < end && isspace((unsigned char)*start)) { + start++; + } + + // 如果全是空格,直接返回 0 + if (start >= end) { + if (endptr) *endptr = self->str; + return 0; + } + + // 2. 处理正负号 (虽然是无符号转码,标准 strtoul 仍允许 '-' 号并对其求补码) + int negate = 0; + if (*start == '+') { + start++; + } else if (*start == '-') { + negate = 1; + start++; + } + + // 3. 自动识别进制 (Base == 0) 或校验 16 进制前缀 + if (base == 0) { + if (start + 1 < end && *start == '0' && (start[1] == 'x' || start[1] == 'X')) { + base = 16; + start += 2; + } else if (start < end && *start == '0') { + base = 8; + start++; + } else { + base = 10; + } + } else if (base == 16) { + // 如果显式指定了16进制,允许略过 0x/0X 前缀 + if (start + 1 < end && *start == '0' && (start[1] == 'x' || start[1] == 'X')) { + start += 2; + } + } + + unsigned long result = 0; + unsigned long cutoff = ULONG_MAX / (unsigned long)base; + int cutlim = ULONG_MAX % (unsigned long)base; + int any_digits = 0; + int overflow = 0; + + // 4. 核心解析循环,严格受限于底层 View 的 End 边界 + while (start < end) { + unsigned char c = (unsigned char)*start; + int digit; + + if (isdigit(c)) { + digit = c - '0'; + } else if (isalpha(c)) { + digit = tolower(c) - 'a' + 10; + } else { + break; // 遇到非法字符,退出解析 + } + + if (digit >= base) { + break; // 超过当前进制最大范围,退出解析 + } + + any_digits = 1; + + // 检查溢出 + if (overflow || result > cutoff || (result == cutoff && digit > cutlim)) { + overflow = 1; + } else { + result = result * (unsigned long)base + (unsigned long)digit; + } + + start++; + } + + // 5. 组装返回值与写回结束指针 + if (endptr) { + *endptr = any_digits ? start : self->str; + } + + if (overflow) { + return ULONG_MAX; + } + + return negate ? (unsigned long)(-(long)result) : result; +} + + +double c_StringView_ToDouble(const c_StringView_t* self, const char** endptr) { + if (!self || self->size == 0 || !self->str) { + if (endptr) *endptr = (self ? self->str : NULL); + return 0.0; + } + + // 1. 跳过前导空白字符 + const char* start = self->str; + const char* end = self->str + self->size; + while (start < end && isspace((unsigned char)*start)) { + start++; + } + + // 计算实际可能包含有效浮点数据的长度 + size_t active_len = end - start; + if (active_len == 0) { + if (endptr) *endptr = self->str; + return 0.0; + } + + // 2. 核心避坑:由于 strtod 需要 \0 结尾,但 StringView 并没有。 + // 我们在栈上开辟一个小缓冲区(256字节足够容纳任何有效的 IEEE 754 浮点数字符串,包括极其冗长的科学计数法)。 + // 这避免了 malloc 带来的堆内存分配开销,同时保证了线程安全。 + char local_buf[256]; + size_t copy_len = (active_len < sizeof(local_buf) - 1) ? active_len : (sizeof(local_buf) - 1); + + memcpy(local_buf, start, copy_len); + local_buf[copy_len] = '\0'; + + // 3. 调用标准库进行高精度解析(能完美处理 NaN、Inf 以及复杂的科学计数法扩展) + char* local_endptr = NULL; + double result = strtod(local_buf, &local_endptr); + + // 4. 将本地缓冲区的相对终止偏移量映射回原始的 StringView 真实指针 + if (endptr) { + if (local_endptr == local_buf) { + // 如果 strtod 完全未能识别出任何数字 + *endptr = self->str; + } else { + ptrdiff_t parsed_offset = local_endptr - local_buf; + *endptr = start + parsed_offset; + } + } + + return result; +} + + +c_index_t c_StringView_FindStr(const c_StringView_t* self, const c_size_t start_pos, const c_StringView_t* needle) { + // 1. 边界与有效性防御 + if (!self || !self->str || !needle || !needle->str) { + return -1; + } + + // 子串为空时,根据标准 C/C++ 行为,默认在有效起点处直接匹配成功 + if (needle->size == 0) { + return (start_pos <= self->size) ? (c_index_t)start_pos : -1; + } + + // 起始位置越界,或者子串长度已经超过了可供查找的剩余主串长度 + if (start_pos >= self->size || (self->size - start_pos) < needle->size) { + return -1; + } + + // 2. 核心搜索算法:在主串死边界内进行滑窗 memcmp 匹配 + c_size_t max_search_idx = self->size - needle->size; + c_size_t needle_len = needle->size; + const char* haystack = self->str; + + for (c_size_t i = start_pos; i <= max_search_idx; i++) { + // 第一步快筛:首字符匹配时再执行深度 memcmp,极大提升非匹配时滑窗的执行效率 + if (haystack[i] == needle->str[0]) { + if (memcmp(haystack + i, needle->str, needle_len) == 0) { + return (c_index_t)i; // 找到匹配,返回主串对应下标 + } + } + } + + return -1; // 遍历完毕,未找到匹配项 +} + diff --git a/Foundation/c_StringView.h b/Foundation/c_StringView.h new file mode 100644 index 0000000..5aa6f98 --- /dev/null +++ b/Foundation/c_StringView.h @@ -0,0 +1,165 @@ +#ifndef INCLUDED_C_STRINGVIEW_H +#define INCLUDED_C_STRINGVIEW_H + +#ifndef INCLUDED_C_TYPES_H +#include +#endif /*INCLUDED_C_TYPES_H*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +typedef struct { + const char* str; + c_size_t size; +}c_StringView_t; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +C_STATIC_FORCE_INLINE +c_StringView_t c_StringView_FromCStr(const char* str) { + c_StringView_t result; + result.str = str; + result.size = str?strlen(str):0; + return result; +} + +C_STATIC_FORCE_INLINE +c_StringView_t c_StringView_FromParts(const char* str, c_size_t len) { + c_StringView_t result; + result.str = str; + result.size = len; + return result; +} + +C_STATIC_FORCE_INLINE +c_StringView_t c_StringView_Sub(const c_StringView_t* self, c_size_t start, c_size_t len) { + c_StringView_t result={0}; + if (start < self->size) { + result.str = self->str + start; + result.size = ((start + len) <= self->size)?len:(self->size - start); + } + return result; +} + +C_STATIC_FORCE_INLINE +c_StringView_t c_StringView_RemovePrefix(const c_StringView_t* self, c_size_t n) { + if (n >= self->size) { + return (c_StringView_t){NULL, 0}; + } + return (c_StringView_t){self->str + n, self->size - n}; +} + +C_STATIC_FORCE_INLINE +c_StringView_t c_StringView_Trim(c_StringView_t* self) { + c_size_t start = 0; + c_size_t end = self->size; + + while (start < end && isspace((unsigned char)self->str[start])) { + start++; + } + while (end > start && isspace((unsigned char)self->str[end - 1])) { + end--; + } + return (c_StringView_t){ self->str + start, end - start }; +} + +C_STATIC_FORCE_INLINE +int c_StringView_Cmp(c_StringView_t* sv1, c_StringView_t* sv2) { + c_size_t min_len = (sv1->size < sv2->size) ? sv1->size : sv2->size; + int cmp = memcmp(sv1->str, sv2->str, min_len); + if (cmp != 0) return cmp; + + if (sv1->size < sv2->size) return -1; + if (sv1->size > sv2->size) return 1; + return 0; +} + +C_STATIC_FORCE_INLINE +bool c_StringView_IsEq(c_StringView_t* sv1, c_StringView_t* sv2) { + if (sv1->size != sv2->size) return false; + if (sv1->str == sv2->str) return true; + return memcmp(sv1->str, sv2->str, sv1->size) == 0; +} + +C_STATIC_FORCE_INLINE +bool c_StringView_HasPrefix(c_StringView_t* self, c_StringView_t* prefix) { + if (self->size < prefix->size) return false; + return memcmp(self->str, prefix->str, prefix->size) == 0; +} + +C_STATIC_FORCE_INLINE +bool c_StringView_HasSuffix(c_StringView_t* self, c_StringView_t* suffix) { + if (self->size < suffix->size) return false; + const char *start = self->str + (self->size - suffix->size); + return memcmp(start, suffix->str, suffix->size) == 0; +} + +C_STATIC_FORCE_INLINE +c_index_t c_StringView_FindChar(const c_StringView_t* self, const c_size_t start_pos, const char c) { + if (start_pos >= self->size) return -1; + for (c_size_t i = start_pos; i < self->size; i++) { + if (self->str[i] == c) { + return (c_index_t)i; + } + } + return -1; +} + +C_STATIC_FORCE_INLINE +bool c_StringView_Split(c_StringView_t * self, const char delim, c_StringView_t* left, c_StringView_t* right) { + const c_index_t idx = c_StringView_FindChar(self, 0, delim); + if (idx == -1) { + if (left) *left = *self; + if (right) *right = (c_StringView_t){ NULL, 0 }; + return false; + } + if (left) *left = c_StringView_Sub(self, 0, idx); + if (right) *right = c_StringView_Sub(self, idx + 1, self->size - idx - 1); + return true; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* */ + +/** + * @brief 将 StringView 转换为传统的以 \0 结尾的 C 字符串(输出到外部缓冲区) + * + * @param self 指向待转换的 StringView 的指针 + * @param buf 用户提供的外部字符缓冲区指针 + * @param buf_size 外部缓冲区的大小(字节数) + * @return char* 返回传入的 buf 指针。若输入参数非法或 buf_size 为 0,则返回 NULL + */ +char* c_StringView_ToCStr(const c_StringView_t* self, char* buf, c_size_t buf_size); + +/** + * @brief 将 StringView 转换为 unsigned long 整数 (模仿 strtoul) + * + * @param self 指向待解析的 StringView 的指针 + * @param endptr 可选参数。若不为 NULL,则用于存储解析终止处的字符指针 + * @param base 进制基数 (0 或 2-36)。为 0 时自动识别 0x (16进制), 0 (8进制), 否则默认10进制 + * @return unsigned long 解析出的无符号长整数。若发生溢出返回 ULONG_MAX + */ +unsigned long c_StringView_ToUL(const c_StringView_t* self, const char** endptr, int base); + +/** + * @brief 将 StringView 转换为高精度双精度浮点数 (模仿 strtod) + * + * @param self 指向待解析的 StringView 的指针 + * @param endptr 可选参数。若不为 NULL,则用于存储解析终止处的字符指针 + * @return double 解析出的浮点数。如果发生严重溢出返回 HUGE_VAL 或 -HUGE_VAL + */ +double c_StringView_ToDouble(const c_StringView_t* self, const char** endptr); + +/** + * @brief 在主 StringView 中查找指定子 StringView 首次出现的位置 + * + * @param self 指向主 StringView 的指针 + * @param start_pos 开始查找的起始下标位置 + * @param needle 指向待查找子 StringView 的指针 + * @return c_index_t 找到时返回匹配子串在主串中的起始下标(相对 self->str);未找到或参数非法时返回 -1 + */ +c_index_t c_StringView_FindStr(const c_StringView_t* self, const c_size_t start_pos, const c_StringView_t* needle); + +#endif /*INCLUDED_C_STRINGVIEW_H*/ diff --git a/Foundation/c_StringView.t.c b/Foundation/c_StringView.t.c new file mode 100644 index 0000000..039a4c3 --- /dev/null +++ b/Foundation/c_StringView.t.c @@ -0,0 +1,366 @@ +#include "c_StringView.h" +#include "c_Test.h" + +#include +#include + +TEST_CASE(test_c_StringView_Creation) { + c_StringView_t sv1 = c_StringView_FromCStr("FrameworkTest"); + ASSERT_INT_EQ(13, sv1.size); + ASSERT_TRUE(memcmp(sv1.str, "FrameworkTest", 13) == 0); + + c_StringView_t sv2 = c_StringView_FromCStr(NULL); + ASSERT_INT_EQ(0, sv2.size); + ASSERT_TRUE(sv2.str == NULL); + + c_StringView_t sv3 = c_StringView_FromParts("Data\0Payload", 12); + ASSERT_INT_EQ(12, sv3.size); +} + +TEST_CASE(test_c_StringView_Sub) { + c_StringView_t base = c_StringView_FromCStr("Container"); + + c_StringView_t sub1 = c_StringView_Sub(&base, 3, 4); + ASSERT_INT_EQ(4, sub1.size); + ASSERT_TRUE(memcmp(sub1.str, "tain", 4) == 0); + + c_StringView_t sub2 = c_StringView_Sub(&base, 5, 20); + ASSERT_INT_EQ(4, sub2.size); + ASSERT_TRUE(memcmp(sub2.str, "iner", 4) == 0); + + c_StringView_t sub3 = c_StringView_Sub(&base, 100, 2); + ASSERT_INT_EQ(0, sub3.size); +} + +TEST_CASE(test_c_StringView_RemovePrefix) { + c_StringView_t base = c_StringView_FromCStr("lib_api_call"); + + c_StringView_t res1 = c_StringView_RemovePrefix(&base, 4); + ASSERT_INT_EQ(8, res1.size); + ASSERT_TRUE(memcmp(res1.str, "api_call", 8) == 0); + + c_StringView_t res2 = c_StringView_RemovePrefix(&base, 50); + ASSERT_INT_EQ(0, res2.size); +} + +TEST_CASE(test_c_StringView_Trim) { + c_StringView_t base1 = c_StringView_FromCStr(" \r\n\t Hello StringView \t "); + c_StringView_t trimmed1 = c_StringView_Trim(&base1); + ASSERT_INT_EQ(16, trimmed1.size); + ASSERT_TRUE(memcmp(trimmed1.str, "Hello StringView", 16) == 0); + + c_StringView_t base2 = c_StringView_FromCStr(" \t \n "); + c_StringView_t trimmed2 = c_StringView_Trim(&base2); + ASSERT_INT_EQ(0, trimmed2.size); +} + +TEST_CASE(test_c_StringView_Cmp_and_IsEq) { + c_StringView_t sv1 = c_StringView_FromCStr("alpha"); + c_StringView_t sv2 = c_StringView_FromCStr("alpha"); + c_StringView_t sv3 = c_StringView_FromCStr("beta"); + c_StringView_t sv4 = c_StringView_FromCStr("alp"); + + ASSERT_TRUE(c_StringView_IsEq(&sv1, &sv2)); + ASSERT_TRUE(!c_StringView_IsEq(&sv1, &sv3)); + + ASSERT_INT_EQ(0, c_StringView_Cmp(&sv1, &sv2)); + ASSERT_TRUE(c_StringView_Cmp(&sv1, &sv3) < 0); + ASSERT_TRUE(c_StringView_Cmp(&sv1, &sv4) > 0); +} + +TEST_CASE(test_c_StringView_Fixes) { + c_StringView_t main_sv = c_StringView_FromCStr("sys_config.json"); + c_StringView_t pfx = c_StringView_FromCStr("sys_"); + c_StringView_t sfx = c_StringView_FromCStr(".json"); + c_StringView_t wrong = c_StringView_FromCStr("config"); + + ASSERT_TRUE(c_StringView_HasPrefix(&main_sv, &pfx)); + ASSERT_TRUE(!c_StringView_HasPrefix(&main_sv, &wrong)); + ASSERT_TRUE(c_StringView_HasSuffix(&main_sv, &sfx)); + ASSERT_TRUE(!c_StringView_HasSuffix(&main_sv, &wrong)); +} + +TEST_CASE(test_c_StringView_FindChar) { + c_StringView_t sv = c_StringView_FromCStr("position_test"); + + ASSERT_LL_EQ(0, c_StringView_FindChar(&sv, 0, 'p')); + ASSERT_LL_EQ(4, c_StringView_FindChar(&sv, 3, 't')); + ASSERT_LL_EQ(-1, c_StringView_FindChar(&sv, 10, 'p')); + ASSERT_LL_EQ(-1, c_StringView_FindChar(&sv, 99, 'e')); +} + +TEST_CASE(test_c_StringView_Split) { + c_StringView_t target = c_StringView_FromCStr("config_key=config_value"); + c_StringView_t left, right; + + bool is_split = c_StringView_Split(&target, '=', &left, &right); + ASSERT_TRUE(is_split); + ASSERT_INT_EQ(10, left.size); + ASSERT_TRUE(memcmp(left.str, "config_key", 10) == 0); + ASSERT_INT_EQ(12, right.size); + ASSERT_TRUE(memcmp(right.str, "config_value", 12) == 0); +} + +TEST_CASE(test_c_StringView_ToCStr_Buf_Basic) { + c_StringView_t sv = c_StringView_FromCStr("HelloView"); + char buffer[32]; + + // 1. 缓冲区足够大时的正常转换 + char* result = c_StringView_ToCStr(&sv, buffer, sizeof(buffer)); + ASSERT_PTR_NOT_NULL(result); + ASSERT_TRUE(result == buffer); // 返回值必须是传入的 buf 指针 + ASSERT_INT_EQ(9, (int)strlen(buffer)); + ASSERT_TRUE(strcmp(buffer, "HelloView") == 0); +} + +TEST_CASE(test_c_StringView_ToCStr_Buf_Truncate) { + c_StringView_t sv = c_StringView_FromCStr("BufferTruncationTest"); + char small_buffer[7]; // 只能容纳 5 个字符 + 1 个 '\0' + + // 2. 空间不足时的安全截断测试 + char* result = c_StringView_ToCStr(&sv, small_buffer, sizeof(small_buffer)); + ASSERT_PTR_NOT_NULL(result); + ASSERT_INT_EQ(6, (int)strlen(small_buffer)); + ASSERT_TRUE(strcmp(small_buffer, "Buffer") == 0); // 确保被安全截断且含有 '\0' + ASSERT_INT_EQ('\0', small_buffer[6]); +} + +TEST_CASE(test_c_StringView_ToCStr_Buf_NoNullTerminatorView) { + // 3. 核心测试:针对长字符串切片出的无 \0 结束符视图进行导出 + c_StringView_t raw = c_StringView_FromCStr("protocol://host:port"); + c_StringView_t sub = c_StringView_Sub(&raw, 11, 4); // 截取 "host" -> 注意原串后面紧跟冒号 ':' + char buffer[16]; + + char* result = c_StringView_ToCStr(&sub, buffer, sizeof(buffer)); + ASSERT_PTR_NOT_NULL(result); + ASSERT_TRUE(strcmp(buffer, "host") == 0); // 确保没有多读后面的 ':' +} + +TEST_CASE(test_c_StringView_ToCStr_Buf_Invalid) { + c_StringView_t sv = c_StringView_FromCStr("ValidData"); + char buffer[16]; + + // 4. 空参数或空大小的防御测试 + ASSERT_TRUE(c_StringView_ToCStr(&sv, NULL, 16) == NULL); + ASSERT_TRUE(c_StringView_ToCStr(&sv, buffer, 0) == NULL); + + // 5. 空视图对象的安全输出测试 + c_StringView_t empty_sv = c_StringView_FromCStr(""); + char* result = c_StringView_ToCStr(&empty_sv, buffer, sizeof(buffer)); + ASSERT_PTR_NOT_NULL(result); + ASSERT_INT_EQ(0, (int)strlen(buffer)); + ASSERT_INT_EQ('\0', buffer[0]); +} + +TEST_CASE(test_c_StringView_ToUL_Basic) { + // 1. 标准10进制转换 + c_StringView_t sv1 = c_StringView_FromCStr("12345"); + const char* end1 = NULL; + unsigned long val1 = c_StringView_ToUL(&sv1, &end1, 10); + ASSERT_INT_EQ(12345, (int)val1); + ASSERT_TRUE(end1 == sv1.str + 5); + + // 2. 带前导空格与正号 + c_StringView_t sv2 = c_StringView_FromCStr(" +999 "); + const char* end2 = NULL; + unsigned long val2 = c_StringView_ToUL(&sv2, &end2, 10); + ASSERT_INT_EQ(999, (int)val2); + ASSERT_TRUE(end2 == sv2.str + 7); // 停在末尾空格前 +} + +TEST_CASE(test_c_StringView_ToUL_Bases) { + // 1. 16进制转换 (显式指定) + c_StringView_t sv1 = c_StringView_FromCStr("0xabcdef"); + unsigned long val1 = c_StringView_ToUL(&sv1, NULL, 16); + ASSERT_TRUE(val1 == 0xABCDEF); + + // 2. 自动进制识别 (Base = 0) + c_StringView_t sv2 = c_StringView_FromCStr("0x1a"); + unsigned long val2 = c_StringView_ToUL(&sv2, NULL, 0); + ASSERT_INT_EQ(26, (int)val2); + + c_StringView_t sv3 = c_StringView_FromCStr("010"); // 八进制的 8 + unsigned long val3 = c_StringView_ToUL(&sv3, NULL, 0); + ASSERT_INT_EQ(8, (int)val3); + + // 3. 2进制转换 + c_StringView_t sv4 = c_StringView_FromCStr("1101"); + unsigned long val4 = c_StringView_ToUL(&sv4, NULL, 2); + ASSERT_INT_EQ(13, (int)val4); +} + +TEST_CASE(test_c_StringView_ToUL_Bounds) { + // 1. 利用子串功能测试没有 \0 结尾的情况 (重要!) + c_StringView_t raw = c_StringView_FromCStr("value=4567888,status=ok"); + c_StringView_t sub = c_StringView_Sub(&raw, 6, 5); // 截取 "45678" -> 注意后面紧跟 '8',如果没有边界控制会多读 + + const char* end = NULL; + unsigned long val = c_StringView_ToUL(&sub, &end, 10); + ASSERT_INT_EQ(45678, (int)val); + ASSERT_TRUE(end == sub.str + 5); // 刚好安全停在 view 的死边界 + + // 2. 溢出边界测试 + c_StringView_t ovf = c_StringView_FromCStr("999999999999999999999999999999"); + unsigned long val_ovf = c_StringView_ToUL(&ovf, NULL, 10); + ASSERT_TRUE(val_ovf == ULONG_MAX); +} + +TEST_CASE(test_c_StringView_ToUL_Invalid) { + // 非法输入测试 + c_StringView_t sv1 = c_StringView_FromCStr("not_a_number"); + const char* end1 = NULL; + unsigned long val1 = c_StringView_ToUL(&sv1, &end1, 10); + ASSERT_INT_EQ(0, (int)val1); + ASSERT_TRUE(end1 == sv1.str); // 无法解析,endptr 指回起点 + + // 空视图测试 + c_StringView_t sv2 = c_StringView_FromCStr(""); + unsigned long val2 = c_StringView_ToUL(&sv2, NULL, 10); + ASSERT_INT_EQ(0, (int)val2); +} + +TEST_CASE(test_c_StringView_ToDouble_Basic) { + // 1. 标准整数与浮点数转换 + c_StringView_t sv1 = c_StringView_FromCStr("3.1415926"); + const char* end1 = NULL; + double val1 = c_StringView_ToDouble(&sv1, &end1); + ASSERT_DOUBLE_EQ_MSG(3.1415926, val1, "Standard pi float parse failed"); + ASSERT_TRUE(end1 == sv1.str + 9); + + // 2. 带前导空格与正负号 + c_StringView_t sv2 = c_StringView_FromCStr(" -0.005 "); + const char* end2 = NULL; + double val2 = c_StringView_ToDouble(&sv2, &end2); + ASSERT_DOUBLE_EQ_MSG(-0.005, val2, "Negative float parse failed"); + ASSERT_TRUE(end2 == sv2.str + 9); // 停在末尾空格前 +} + +TEST_CASE(test_c_StringView_ToDouble_Scientific) { + // 1. 科学计数法支持 (大写 E) + c_StringView_t sv1 = c_StringView_FromCStr("1.23E4"); + double val1 = c_StringView_ToDouble(&sv1, NULL); + ASSERT_DOUBLE_EQ_MSG(12300.0, val1, "Scientific notation E+ failed"); + + // 2. 科学计数法支持 (小写 e 负指数) + c_StringView_t sv2 = c_StringView_FromCStr("5.5e-2"); + double val2 = c_StringView_ToDouble(&sv2, NULL); + ASSERT_DOUBLE_EQ_MSG(0.055, val2, "Scientific notation e- failed"); +} + +TEST_CASE(test_c_StringView_ToDouble_Bounds) { + // 1. 关键测试:没有 \0 结尾的子串切片(测试多读防护) + c_StringView_t raw = c_StringView_FromCStr("pi=3.14159,e=2.718"); + c_StringView_t sub = c_StringView_Sub(&raw, 3, 7); // 精确截取 "3.14159" -> 紧跟逗号 + + const char* end = NULL; + double val = c_StringView_ToDouble(&sub, &end); + ASSERT_DOUBLE_EQ_MSG(3.14159, val, "No null-terminator view parse failed"); + ASSERT_TRUE(end == sub.str + 7); // 必须精准停在 View 的终止边界,不被后面的字符干扰 + + // 2. 特殊浮点值测试 (不区分大小写) + c_StringView_t inf_sv = c_StringView_FromCStr("infinity"); + double val_inf = c_StringView_ToDouble(&inf_sv, NULL); + ASSERT_TRUE(isinf(val_inf)); +} + +TEST_CASE(test_c_StringView_ToDouble_Invalid) { + // 1. 完全无法解析的非数字内容 + c_StringView_t sv1 = c_StringView_FromCStr("abc"); + const char* end1 = NULL; + double val1 = c_StringView_ToDouble(&sv1, &end1); + ASSERT_DOUBLE_EQ_MSG(0.0, val1, "Invalid string should yield 0.0"); + ASSERT_TRUE(end1 == sv1.str); // 无法解析时,endptr 应该退回到起点 + + // 2. 空视图边界安全 + c_StringView_t sv2 = c_StringView_FromCStr(""); + double val2 = c_StringView_ToDouble(&sv2, NULL); + ASSERT_DOUBLE_EQ_MSG(0.0, val2, "Empty view should yield 0.0"); +} +TEST_CASE(test_c_StringView_FindStr_Basic) { + c_StringView_t main_sv = c_StringView_FromCStr("hello_world_test"); + c_StringView_t needle1 = c_StringView_FromCStr("world"); + c_StringView_t needle2 = c_StringView_FromCStr("test"); + + // 1. 标准从头查找 + c_index_t idx1 = c_StringView_FindStr(&main_sv, 0, &needle1); + ASSERT_LL_EQ(6, idx1); + + // 2. 查找位于末尾的子串 + c_index_t idx2 = c_StringView_FindStr(&main_sv, 0, &needle2); + ASSERT_LL_EQ(12, idx2); +} + +TEST_CASE(test_c_StringView_FindStr_Positions) { + c_StringView_t main_sv = c_StringView_FromCStr("abacaba_abacaba"); + c_StringView_t needle = c_StringView_FromCStr("abacaba"); + + // 1. 指定起始位置偏移量,避开第一个匹配项去寻找第二个匹配项 + c_index_t idx1 = c_StringView_FindStr(&main_sv, 0, &needle); + ASSERT_LL_EQ(0, idx1); + + c_index_t idx2 = c_StringView_FindStr(&main_sv, 1, &needle); + ASSERT_LL_EQ(8, idx2); // 成功跳过第一个,定位到后方的匹配 +} + +TEST_CASE(test_c_StringView_FindStr_NoNullTerminator) { + // 3. 核心测试:主串与子串两端均没有 \0 结束符(测试死边界控制) + c_StringView_t raw_haystack = c_StringView_FromCStr("GET /api/v1/user HTTP/1.1"); + c_StringView_t raw_needle = c_StringView_FromCStr("prefix_v1_suffix"); + + // 切片分离出没有 \0 的主串和子串 + c_StringView_t haystack_sv = c_StringView_Sub(&raw_haystack, 4, 12); // "/api/v1/user" -> 后面紧跟 " HTTP" + c_StringView_t needle_sv = c_StringView_Sub(&raw_needle, 7, 2); // "v1" -> 后面紧跟 "_suffix" + + c_index_t idx = c_StringView_FindStr(&haystack_sv, 0, &needle_sv); + ASSERT_LL_EQ(5, idx); // 必须在截取的相对视图中精确定位到偏移位置 5 +} + +TEST_CASE(test_c_StringView_FindStr_EdgeAndInvalid) { + c_StringView_t main_sv = c_StringView_FromCStr("short"); + c_StringView_t long_needle = c_StringView_FromCStr("longer_than_main"); + c_StringView_t empty_needle = c_StringView_FromCStr(""); + + // 1. 子串长于主串,直接返回 -1 + ASSERT_LL_EQ(-1, c_StringView_FindStr(&main_sv, 0, &long_needle)); + + // 2. 查找空子串,返回当前传入的检索起始点 + ASSERT_LL_EQ(2, c_StringView_FindStr(&main_sv, 2, &empty_needle)); + + // 3. 起始检索点越界,返回 -1 + ASSERT_LL_EQ(-1, c_StringView_FindStr(&main_sv, 99, &empty_needle)); + + // 4. 完全没有匹配项的情况 + c_StringView_t target = c_StringView_FromCStr("missing"); + ASSERT_LL_EQ(-1, c_StringView_FindStr(&main_sv, 0, &target)); +} + + +int main(int argc, char** argv){ + TEST_START(c_StringView_t_TestSuite); + RUN_TEST(test_c_StringView_Creation); + RUN_TEST(test_c_StringView_Sub); + RUN_TEST(test_c_StringView_RemovePrefix); + RUN_TEST(test_c_StringView_Trim); + RUN_TEST(test_c_StringView_Cmp_and_IsEq); + RUN_TEST(test_c_StringView_Fixes); + RUN_TEST(test_c_StringView_FindChar); + RUN_TEST(test_c_StringView_Split); + RUN_TEST(test_c_StringView_ToUL_Basic); + RUN_TEST(test_c_StringView_ToUL_Bases); + RUN_TEST(test_c_StringView_ToUL_Bounds); + RUN_TEST(test_c_StringView_ToUL_Invalid); + RUN_TEST(test_c_StringView_ToDouble_Basic); + RUN_TEST(test_c_StringView_ToDouble_Scientific); + RUN_TEST(test_c_StringView_ToDouble_Bounds); + RUN_TEST(test_c_StringView_ToDouble_Invalid); + RUN_TEST(test_c_StringView_ToCStr_Buf_Basic); + RUN_TEST(test_c_StringView_ToCStr_Buf_Truncate); + RUN_TEST(test_c_StringView_ToCStr_Buf_NoNullTerminatorView); + RUN_TEST(test_c_StringView_ToCStr_Buf_Invalid); + RUN_TEST(test_c_StringView_FindStr_Basic); + RUN_TEST(test_c_StringView_FindStr_Positions); + RUN_TEST(test_c_StringView_FindStr_NoNullTerminator); + RUN_TEST(test_c_StringView_FindStr_EdgeAndInvalid); + TEST_REPORT(); + RETURN_TEST_STATUS; +}