545 lines
17 KiB
C
545 lines
17 KiB
C
/* -----------------------------------------------------------------
|
|
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 <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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 */
|
|
|
|
|
|
|