/* ----------------------------------------------------------------- FILE: dstring.c DESCRIPTION:This file contains the routines for manipulating dynamic strings. Copyright 2020 The ngspice team 3 - Clause BSD license (see COPYING or https://opensource.org/licenses/BSD-3-Clause) Author: Jim Monte ----------------------------------------------------------------- */ #include #include #include #include #include #include #include "ngspice/dstring.h" static int ds_reserve_internal(DSTRING *p_ds, size_t n_byte_alloc_opt, size_t n_byte_alloc_min); /* Instantiations of dstring functions */ extern inline int ds_cat_str(DSTRING *p_ds, const char *sz); extern inline int ds_cat_char(DSTRING *p_ds, char c); extern inline int ds_cat_ds(DSTRING *p_ds_dst, const DSTRING *p_ds_src); extern inline int ds_cat_mem(DSTRING *p_ds, const char *p_src, size_t n_char); extern inline int ds_set_length(DSTRING *p_ds, size_t length); extern inline void ds_clear(DSTRING *p_ds); extern inline char *ds_free_move(DSTRING *p_ds, unsigned int opt); extern inline char *ds_get_buf(DSTRING *p_ds); extern inline size_t ds_get_length(const DSTRING *p_ds); extern inline size_t ds_get_buf_size(const DSTRING *p_ds); /* This function initalizes a dstring using *p_buf as the initial backing * * Parameters * p_buf: Inital buffer backing the dstring * length_string: Length of string in the initial buffer * n_byte_data: Length of initial buffer. Must be at least 1 * type_buffer: Type of buffer providing initial backing * * Return codes * DS_E_OK: Init OK * DS_E_INVALID: n_byte_data = 0 length_string too long, * or unknown buffer type */ int ds_init(DSTRING *p_ds, char *p_buf, size_t length_string, size_t n_byte_buf, ds_buf_type_t type_buffer) { /* Validate buffer size */ if (n_byte_buf == 0) { return DS_E_INVALID; } /* Set current buffer */ p_ds->p_buf = p_buf; /* Set size of current string >= rather than > because this function * adds a terminating null */ if (length_string >= n_byte_buf) { return DS_E_INVALID; } p_ds->n_byte_alloc = n_byte_buf; p_ds->length = length_string; p_ds->p_buf[length_string] = '\0'; /* Set stack buffer */ if (type_buffer == ds_buf_type_stack) { p_ds->p_stack_buf = p_buf; p_ds->n_byte_stack_buf = n_byte_buf; } else if (type_buffer == ds_buf_type_heap) { p_ds->p_stack_buf = (char *) NULL; p_ds->n_byte_stack_buf = 0; } else { /* unknown buffer type */ return DS_E_INVALID; } return DS_E_OK; } /* end of function ds_init */ /* This function frees all memory used by the dstring. After calling this * function, the dstring should not be used again. */ void ds_free(DSTRING *p_ds) { if (p_ds->p_buf != p_ds->p_stack_buf) { txfree((void *) p_ds->p_buf); } } /* end of function ds_free */ /* Concatenate string */ int ds_cat_str_case(DSTRING *p_ds, const char *sz, ds_case_t case_type) { return ds_cat_mem_case(p_ds, sz, strlen(sz), case_type); } /* end of function ds_cat_str_case */ /* Concatenate character */ int ds_cat_char_case(DSTRING *p_ds, char c, ds_case_t case_type) { return ds_cat_mem_case(p_ds, &c, 1, case_type); } /* end of function ds_cat_char_case */ /* Concatenate another dstring */ int ds_cat_ds_case(DSTRING *p_ds_dst, const DSTRING *p_ds_src, ds_case_t case_type) { return ds_cat_mem_case(p_ds_dst, p_ds_src->p_buf, p_ds_src->length, case_type); } /* end of function ds_cat_ds_case */ /* General concatenation of a memory buffer. A terminating null is added. */ int ds_cat_mem_case(DSTRING *p_ds, const char *p_src, size_t n_char, ds_case_t type_case) { /* Resize buffer if necessary. Double required size, if available, * to reduce the number of allocations */ const size_t length_new = p_ds->length + n_char; const size_t n_byte_needed = length_new + 1; if (n_byte_needed > p_ds->n_byte_alloc) { if (ds_reserve_internal(p_ds, 2 * n_byte_needed, n_byte_needed) == DS_E_NO_MEMORY) { return DS_E_NO_MEMORY; } } /* For "as-is" can simply memcpy */ if (type_case == ds_case_as_is) { char *p_dst = p_ds->p_buf + p_ds->length; (void) memcpy(p_dst, p_src, n_char); p_dst += n_char; *p_dst = '\0'; p_ds->length = length_new; return DS_E_OK; } /* For lowercasing, work char by char */ if (type_case == ds_case_lower) { char *p_dst = p_ds->p_buf + p_ds->length; char *p_dst_end = p_dst + n_char; for ( ; p_dst < p_dst_end; p_dst++, p_src++) { *p_dst = (char) tolower(*p_src); } *p_dst_end = '\0'; p_ds->length = length_new; return DS_E_OK; } /* Uppercasing done like lowercasing. Note that it would be possible to * use a function pointer and select either tolower() or toupper() based * on type_case, but doing so may degrade performance by inhibiting * inlining. */ if (type_case == ds_case_upper) { char *p_dst = p_ds->p_buf + p_ds->length; char *p_dst_end = p_dst + n_char; for ( ; p_dst < p_dst_end; p_dst++, p_src++) { *p_dst = (char) toupper(*p_src); } *p_dst_end = '\0'; p_ds->length = length_new; return DS_E_OK; } return DS_E_INVALID; /* unknown case type */ } /* end of function ds_cat_mem_case */ /* Ensure minimum internal buffer size */ int ds_reserve(DSTRING *p_ds, size_t n_byte_alloc) { /* Return if buffer already large enough */ if (p_ds->n_byte_alloc >= n_byte_alloc) { return DS_E_OK; } return ds_reserve_internal(p_ds, n_byte_alloc, 0); } /* end of function ds_reserve */ /* This function resizes the buffer for the string and handles freeing * the original alloction, if necessary. It is assumed that the requested * size or sizes are larger than the current size. * * Parameters * p_ds: Dstring pointer * n_byte_alloc_opt: Optimal alloction amount * n_byte_alloc_min: Absolute minimum allocation amount or 0 if no * smaller amount can be allocated * * Return codes * DS_E_OK: At least the minimum allocation was performed * DS_E_NO_MEMORY: Unable to resize the buffer */ static int ds_reserve_internal(DSTRING *p_ds, size_t n_byte_alloc_opt, size_t n_byte_alloc_min) { size_t n_byte_alloc = n_byte_alloc_opt; /* Allocate. First try (larger) optimal size, and gradually fall back * to min size if that fails and one was provided. */ char * p_buf_new; if (n_byte_alloc_min == 0) { n_byte_alloc_min = n_byte_alloc_opt; } for ( ; ; ) { if ((p_buf_new = (char *) malloc(n_byte_alloc)) != (char *) NULL) { break; /* Allocated OK */ } if (n_byte_alloc == n_byte_alloc_min) { /* min alloc failed */ return DS_E_NO_MEMORY; } if ((n_byte_alloc /= 2) < n_byte_alloc_min) { /* last try */ n_byte_alloc = n_byte_alloc_min; } } /* end of loop trying smaller allocations */ /* Copy to the new buffer */ (void) memcpy(p_buf_new, p_ds->p_buf, p_ds->length + 1); /* If there already was a dynamic allocation, free it */ if (p_ds->p_buf != p_ds->p_stack_buf) { txfree((void *) p_ds->p_buf); } /* Assign new active buffer and its size */ p_ds->p_buf = p_buf_new; p_ds->n_byte_alloc = n_byte_alloc; return DS_E_OK; } /* end of function ds_reserve_nocheck */ /* Concatenate the result of a printf-style format * * Return codes as for ds_cat_vprintf */ int ds_cat_printf(DSTRING *p_ds, const char *sz_fmt, ...) { va_list p_arg; va_start(p_arg, sz_fmt); const int xrc = ds_cat_vprintf(p_ds, sz_fmt, p_arg); va_end(p_arg); return xrc; } /* end of function ds_cat_printf */ /* Concatenate the result of a printf-style format using va_list * * Return codes * DS_E_OK: Formatted OK * DS_E_NO_MEMORY: Unable to allocate memory to resize buffer * DS_E_INVALID: Invalid formatter / data */ int ds_cat_vprintf(DSTRING *p_ds, const char *sz_fmt, va_list p_arg) { /* Make a copy of the argument list in case need to format more than * once */ va_list p_arg2; va_copy(p_arg2, p_arg); const size_t n_byte_free = p_ds->n_byte_alloc - p_ds->length; char * const p_dst = p_ds->p_buf + p_ds->length; const int rc = vsnprintf(p_dst, n_byte_free, sz_fmt, p_arg); if (rc < 0) { /* Check for formatting error */ return DS_E_INVALID; } /* Else check for buffer large enough and set length if it is */ if ((size_t) rc < n_byte_free) { p_ds->length += (size_t) rc; return DS_E_OK; } /* Else buffer too small, so resize and format again */ { /* Double required size to avoid excessive allocations +1 for * null, which is not included in the count returned by snprintf */ const size_t n_byte_alloc_min = p_ds->length + (size_t) rc + (size_t) 1; if (ds_reserve_internal(p_ds, 2 * n_byte_alloc_min, n_byte_alloc_min) == DS_E_NO_MEMORY) { /* vsnprintf may have written bytes to the buffer. * Ensure that dstring in a consistent state by writing * a null at the length of the string */ p_ds->p_buf[p_ds->length] = '\0'; return DS_E_NO_MEMORY; } const size_t n_byte_free2 = p_ds->n_byte_alloc - p_ds->length; char * const p_dst2 = p_ds->p_buf + p_ds->length; const int rc2 = vsnprintf(p_dst2, n_byte_free2, sz_fmt, p_arg2); if (rc2 < 0) { /* Check for formatting error */ /* vsnprintf may have written bytes to the buffer. * Ensure that dstring in a consistent state by writing * a null at the length of the string */ p_ds->p_buf[p_ds->length] = '\0'; return DS_E_INVALID; } /* Else update length. No need to check buffer size since it was * sized to fit the string. */ p_ds->length += (size_t) rc2; return DS_E_OK; } } /* end of function ds_cat_vprintf */ /* Reallocate/free to eliminate unused buffer space. * * Return codes * DS_E_OK: Compacted OK * DS_E_NO_MEMORY: Compaction failed, but dstring still valid */ int ds_compact(DSTRING *p_ds) { const size_t n_byte_alloc_min = p_ds->length + 1; /* If the string is in the stack buffer, there is nothing to do */ if (p_ds->p_stack_buf == p_ds->p_buf) { return DS_E_OK; } /* Else if the string will fit in the stack buffer, copy it there and * free the allocation. */ if (p_ds->n_byte_stack_buf >= n_byte_alloc_min) { (void) memcpy(p_ds->p_stack_buf, p_ds->p_buf, n_byte_alloc_min); txfree((void *) p_ds->p_buf); p_ds->p_buf = p_ds->p_stack_buf; p_ds->n_byte_alloc = p_ds->n_byte_stack_buf; return DS_E_OK; } /* Else if the heap buffer is the minimum size, there is nothng to do */ if (n_byte_alloc_min == p_ds->n_byte_alloc) { return DS_E_OK; } /* Else realloc the heap buffer */ { void *p = TREALLOC(char, p_ds->p_buf, n_byte_alloc_min); if (p == NULL) { return DS_E_NO_MEMORY; } p_ds->p_buf = (char *) p; p_ds->n_byte_alloc = n_byte_alloc_min; return DS_E_OK; } } /* end of function ds_compact */ #ifdef DSTRING_UNIT_TEST #if defined (_WIN32) && !defined(CONSOLE) #include "ngspice/wstdio.h" #endif static void ds_print_info(DSTRING *p_ds, FILE *fp, const char *sz_id); static int ds_test_from_macro(FILE *fp); static int ds_test_from_stack(FILE *fp); static int ds_test_from_heap(FILE *fp); static int ds_test1(DSTRING *p_ds, FILE *fp); int ds_test(FILE *fp) { if (ds_test_from_macro(fp) != 0) { /* create from macro and run test */ return -1; } if (ds_test_from_stack(fp) != 0) { /* create from stack */ return -1; } if (ds_test_from_heap(fp) != 0) { /* create from heap */ return -1; } return 0; } /* end of function ds_test */ /* Run tests from a macro-created dstring */ static int ds_test_from_macro(FILE *fp) { DS_CREATE(ds, 10); (void) fprintf(fp, "Macro initialization\n"); return ds_test1(&ds, fp); } /* end of function ds_test_from_macro */ /* Run tests from a manually created stack-backed dstring */ static int ds_test_from_stack(FILE *fp) { static char p_buf[30] = "Hello World"; DSTRING ds; (void) fprintf(fp, "Stack initialization\n"); (void) ds_init(&ds, p_buf, 11, sizeof p_buf, ds_buf_type_stack); return ds_test1(&ds, fp); } /* end of function ds_test_from_stack */ /* Run tests from a heap-backed dstring */ static int ds_test_from_heap(FILE *fp) { char *p_buf = (char *) malloc(25); if (p_buf == (char *) NULL) { return -1; } (void) memcpy(p_buf, "Heap", 4); DSTRING ds; (void) ds_init(&ds, p_buf, 4, 25, ds_buf_type_heap); (void) fprintf(fp, "Heap initialization\n"); return ds_test1(&ds, fp); } /* end of function ds_test_from_heap */ static int ds_test1(DSTRING *p_ds, FILE *fp) { /* Print info on entry */ ds_print_info(p_ds, fp, "On entry to ds_test1\n"); int i; for (i = 0; i < 10; i++) { if (ds_cat_str(p_ds, "Abc") != 0) { (void) fprintf(fp, "Unable to cat string %d.\n", i); return -1; } if (ds_cat_str_case(p_ds, "Abc", ds_case_as_is) != 0) { (void) fprintf(fp, "Unable to cat string as-is %d.\n", i); return -1; } if (ds_cat_str_case(p_ds, "Abc", ds_case_upper) != 0) { (void) fprintf(fp, "Unable to cat string upper %d.\n", i); return -1; } if (ds_cat_str_case(p_ds, "Abc", ds_case_lower) != 0) { (void) fprintf(fp, "Unable to cat string lower %d.\n", i); return -1; } if (ds_cat_char(p_ds, 'z') != 0) { (void) fprintf(fp, "Unable to cat char %d.\n", i); return -1; } if (ds_cat_char_case(p_ds, 'z', ds_case_as_is) != 0) { (void) fprintf(fp, "Unable to cat char as-is %d.\n", i); return -1; } if (ds_cat_char_case(p_ds, 'z', ds_case_upper) != 0) { (void) fprintf(fp, "Unable to cat char upper %d.\n", i); return -1; } if (ds_cat_char_case(p_ds, 'Z', ds_case_lower) != 0) { (void) fprintf(fp, "Unable to cat char lower %d.\n", i); return -1; } if (ds_cat_mem(p_ds, "Zyxw", 4) != 0) { (void) fprintf(fp, "Unable to cat string %d.\n", i); return -1; } if (ds_cat_mem_case(p_ds, "Zyxw", 4, ds_case_as_is) != 0) { (void) fprintf(fp, "Unable to cat string as-is %d.\n", i); return -1; } if (ds_cat_mem_case(p_ds, "Zyxw", 4, ds_case_upper) != 0) { (void) fprintf(fp, "Unable to cat string upper %d.\n", i); return -1; } if (ds_cat_mem_case(p_ds, "Zyxw", 4, ds_case_lower) != 0) { (void) fprintf(fp, "Unable to cat string lower %d.\n", i); return -1; } if (ds_cat_printf(p_ds, "--- And finally a formatted %s (%d)", "string", i) != 0) { (void) fprintf(fp, "Unable to cat formatted string %d.\n", i); return -1; } /* Print info after cats */ ds_print_info(p_ds, fp, "After appending strings"); /* Truncate the string */ if (ds_set_length(p_ds, i * (size_t) 10) != 0) { (void) fprintf(fp, "Unable to set size %d.\n", i); return -1; } /* Print info after truncation */ ds_print_info(p_ds, fp, "After setting length"); /* Compact the string */ if (ds_compact(p_ds) != 0) { (void) fprintf(fp, "Unable to compact %d.\n", i); return -1; } /* Print info after compaction */ ds_print_info(p_ds, fp, "After compacting the string"); } /* end of loop over tests */ ds_free(p_ds); /* free buffer if allocated */ return 0; } /* end of funtion ds_test */ /* Print some info about the DSTRING */ static void ds_print_info(DSTRING *p_ds, FILE *fp, const char *sz_id) { (void) fprintf(fp, "%s: length = %zu; " "allocated buffer size = %zu; value = \"%s\"; " "address of active buffer = %p; " "address of stack buffer = %p; " "size of stack buffer = %zu\n", sz_id, ds_get_length(p_ds), ds_get_buf_size(p_ds), ds_get_buf(p_ds), ds_get_buf(p_ds), p_ds->p_stack_buf, p_ds->n_byte_stack_buf); } /* end of function ds_print_info */ #endif /* DSTRING_UNIT_TEST */