975 lines
34 KiB
C
975 lines
34 KiB
C
#include <c_StringBuffer.h>
|
|||
|
|
#include <c_Memory.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
|
|
/* */
|
||
|
|
|
||
|
|
#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_free_space) {
|
||
|
|
c_size_t current_free = self->capacity - self->size - 1; // 抛去隐式预留的 1 字节 \0 位
|
||
|
|
if (current_free >= required_free_space) return C_ERR_OK;
|
||
|
|
|
||
|
|
c_size_t new_capacity = self->capacity * GROWTH_FACTOR;
|
||
|
|
// 阶跃扩容保护,直到能完全装下需求空间
|
||
|
|
while ((new_capacity - self->size - 1) < required_free_space) {
|
||
|
|
new_capacity *= GROWTH_FACTOR;
|
||
|
|
}
|
||
|
|
|
||
|
|
return c_StringBuffer_Resize(self, new_capacity);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
|
|
/* */
|
||
|
|
|
||
|
|
|
||
|
|
c_err_t c_StringBuffer_Init(c_StringBuffer_t* self, c_size_t capacity, c_Allocator_t* allocator){
|
||
|
|
if (!self) return C_ERR_PARAM;
|
||
|
|
|
||
|
|
self->allocator = (allocator != NULL) ? *allocator : c_DefaultAllocator;
|
||
|
|
self->size = 0;
|
||
|
|
self->capacity = (capacity > 0) ? (capacity + 1) : DEFAULT_INIT_CAPACITY;
|
||
|
|
|
||
|
|
// 内部多态自治申领空间
|
||
|
|
self->buffer = (char*)c_Allocator_Alloc(&self->allocator, 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_Allocator_Free(&self->allocator, self->buffer);
|
||
|
|
self->buffer = NULL;
|
||
|
|
}
|
||
|
|
self->size = 0;
|
||
|
|
self->capacity = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
c_err_t c_StringBuffer_Resize(c_StringBuffer_t* self, c_size_t new_capacity) {
|
||
|
|
if (!self || new_capacity == 0) return C_ERR_PARAM;
|
||
|
|
if (new_capacity == self->capacity) return C_ERR_OK;
|
||
|
|
|
||
|
|
// 防止 new_capacity 算入 \0 的扩容本身越界
|
||
|
|
if (new_capacity > (c_size_t)-1) return C_ERR_OUTOFBOUND;
|
||
|
|
|
||
|
|
c_size_t old_bytes = self->capacity;
|
||
|
|
c_size_t new_bytes = new_capacity;
|
||
|
|
|
||
|
|
// 对接你最新的 Realloc 包装,闭环支持伙伴系统在同一阶数(Order)内的 O(1) 原地续约
|
||
|
|
void* new_buffer = c_Allocator_Realloc(&self->allocator, self->buffer, old_bytes, new_bytes);
|
||
|
|
if (!new_buffer) return C_ERR_NOMEM; // 扩容失败,老数据和老空间依然安全原装存活
|
||
|
|
|
||
|
|
self->buffer = (char*)new_buffer;
|
||
|
|
self->capacity = new_capacity;
|
||
|
|
|
||
|
|
// 裁切防护:如果显式调小了容量,且当前已有的数据长度超过了新物理上限,强制进行数据阶段和 \0 封底
|
||
|
|
if (self->size >= self->capacity) {
|
||
|
|
self->size = self->capacity - 1;
|
||
|
|
self->buffer[self->size] = '\0';
|
||
|
|
}
|
||
|
|
|
||
|
|
return C_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
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_ERR_OK;
|
||
|
|
|
||
|
|
// 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_ERR_OK;
|
||
|
|
|
||
|
|
c_size_t length = (c_size_t)formatted_len;
|
||
|
|
|
||
|
|
c_err_t err = c_StringBuffer_EnsureCapacity(self, length);
|
||
|
|
if (err != C_ERR_OK) 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
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_ERR_OK;
|
||
|
|
|
||
|
|
c_size_t length = (c_size_t)formatted_len;
|
||
|
|
|
||
|
|
c_err_t err = c_StringBuffer_EnsureCapacity(self, length);
|
||
|
|
if (err != C_ERR_OK) 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
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 <time.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
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_ERR_OK) 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_ERR_OK;
|
||
|
|
}
|
||
|
|
// 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
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_ERR_OK;
|
||
|
|
|
||
|
|
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) {
|
||
|
|
c_Allocator_Free(&self->allocator, target_buffer);
|
||
|
|
}
|
||
|
|
return C_ERR_PARAM;
|
||
|
|
}
|
||
|
|
|
||
|
|
char* new_buffer = (target_buffer == temp_stack_buffer)
|
||
|
|
? (char*)c_Allocator_Alloc(&self->allocator, new_allocated_size)
|
||
|
|
: (char*)c_Allocator_Realloc(&self->allocator, target_buffer, allocated_size, new_allocated_size);
|
||
|
|
|
||
|
|
if (!new_buffer) {
|
||
|
|
if (target_buffer != temp_stack_buffer) {
|
||
|
|
c_Allocator_Free(&self->allocator, 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) {
|
||
|
|
c_Allocator_Free(&self->allocator, 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_NOTFOUND;
|
||
|
|
if (start_index >= self->size) return C_ERR_NOTFOUND;
|
||
|
|
|
||
|
|
// Utilize optimized standard strstr starting from our targeted index offset
|
||
|
|
char* match = strstr(self->buffer + start_index, substr);
|
||
|
|
if (!match) return C_ERR_NOTFOUND;
|
||
|
|
|
||
|
|
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_NOTFOUND;
|
||
|
|
if (start_index >= self->size) return C_ERR_NOTFOUND;
|
||
|
|
|
||
|
|
// 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_NOTFOUND;
|
||
|
|
|
||
|
|
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_NOTFOUND;
|
||
|
|
|
||
|
|
c_size_t sub_len = strlen(substr);
|
||
|
|
if (sub_len == 0) return C_ERR_NOTFOUND;
|
||
|
|
|
||
|
|
// 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_NOTFOUND;
|
||
|
|
|
||
|
|
// 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_NOTFOUND;
|
||
|
|
}
|
||
|
|
|
||
|
|
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_NOTFOUND;
|
||
|
|
|
||
|
|
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_NOTFOUND;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
|
|
/* */
|
||
|
|
|
||
|
|
|
||
|
|
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_ERR_OK; // 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_ERR_OK; // 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_ERR_OK) 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
|
|
/* */
|
||
|
|
|
||
|
|
#include <ctype.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
c_err_t c_StringBuffer_TrimLeft(c_StringBuffer_t* self) {
|
||
|
|
if (!self) return C_ERR_PARAM;
|
||
|
|
if (self->size == 0) return C_ERR_OK;
|
||
|
|
|
||
|
|
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_ERR_OK; // 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
c_err_t c_StringBuffer_TrimRight(c_StringBuffer_t* self) {
|
||
|
|
if (!self) return C_ERR_PARAM;
|
||
|
|
if (self->size == 0) return C_ERR_OK;
|
||
|
|
|
||
|
|
// 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
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_ERR_OK) 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
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*)c_Allocator_Alloc(&self->allocator, 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, &self->allocator);
|
||
|
|
if (err != C_ERR_OK) goto error_cleanup;
|
||
|
|
|
||
|
|
// 如果长度大于 0,将片段内容追加拷贝进去
|
||
|
|
if (token_len > 0) {
|
||
|
|
err = c_StringBuffer_Append(&tokens[current_token], self->buffer + start_idx, token_len);
|
||
|
|
if (err != C_ERR_OK) goto error_cleanup;
|
||
|
|
}
|
||
|
|
|
||
|
|
current_token++;
|
||
|
|
if (!match) break; // 已处理完最后一个片段,退出循环
|
||
|
|
|
||
|
|
// 步进索引:当前片段长度 + 分隔符长度
|
||
|
|
start_idx += token_len + delim_len;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 5. 成功赋值输出
|
||
|
|
*out_tokens = tokens;
|
||
|
|
*out_count = token_count;
|
||
|
|
return C_ERR_OK;
|
||
|
|
|
||
|
|
// 防御性垃圾回收:如果中途任何一个 Token 内存分配失败,完整回滚,绝不泄露
|
||
|
|
error_cleanup:
|
||
|
|
for (c_size_t i = 0; i < token_count; i++) {
|
||
|
|
// c_StringBuffer_Destroy 内部有对 NULL 的安全校验
|
||
|
|
c_StringBuffer_Destroy(&tokens[i]);
|
||
|
|
}
|
||
|
|
c_Allocator_Free(&self->allocator, 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_ERR_OK;
|
||
|
|
|
||
|
|
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_ERR_OK) 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
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_ERR_OK; // 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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
c_err_t c_StringBuffer_Substr(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, &self->allocator);
|
||
|
|
if (err != C_ERR_OK) return err;
|
||
|
|
|
||
|
|
if (length > 0) {
|
||
|
|
err = c_StringBuffer_Append(out_substring, self->buffer + index, length);
|
||
|
|
if (err != C_ERR_OK) {
|
||
|
|
c_StringBuffer_Destroy(out_substring);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return C_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
c_err_t c_StringBuffer_Slice(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, &self->allocator);
|
||
|
|
if (err != C_ERR_OK) return err;
|
||
|
|
|
||
|
|
if (length > 0) {
|
||
|
|
err = c_StringBuffer_Append(out_slice, self->buffer + start_index, length);
|
||
|
|
if (err != C_ERR_OK) {
|
||
|
|
c_StringBuffer_Destroy(out_slice);
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return C_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
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_ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
||
|
|
/* */
|
||
|
|
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
c_size_t old_capacity = sb->capacity;
|
||
|
|
|
||
|
|
// char* new_array = (char*)C_REALLOC(sb->buffer, new_capacity * sizeof(char));
|
||
|
|
char* new_array = (char*)c_Allocator_Realloc(&sb->allocator, sb->buffer, sizeof(char) * old_capacity, sizeof(char) * new_capacity);
|
||
|
|
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;
|
||
|
|
}
|