1 /* -----------------------------------------------------------------
2 FILE: dstring.c
3 DESCRIPTION:This file contains the routines for manipulating dynamic strings.
4
5 Copyright 2020 The ngspice team
6 3 - Clause BSD license
7 (see COPYING or https://opensource.org/licenses/BSD-3-Clause)
8 Author: Jim Monte
9 ----------------------------------------------------------------- */
10 #include <ctype.h>
11 #include <stdarg.h>
12 #include <stddef.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16
17 #include "ngspice/dstring.h"
18
19
20 static int ds_reserve_internal(DSTRING *p_ds,
21 size_t n_byte_alloc_opt, size_t n_byte_alloc_min);
22
23 /* Instantiations of dstring functions */
24 extern inline int ds_cat_str(DSTRING *p_ds, const char *sz);
25 extern inline int ds_cat_char(DSTRING *p_ds, char c);
26 extern inline int ds_cat_ds(DSTRING *p_ds_dst, const DSTRING *p_ds_src);
27 extern inline int ds_cat_mem(DSTRING *p_ds, const char *p_src, size_t n_char);
28 extern inline int ds_set_length(DSTRING *p_ds, size_t length);
29 extern inline void ds_clear(DSTRING *p_ds);
30 extern inline char *ds_free_move(DSTRING *p_ds, unsigned int opt);
31 extern inline char *ds_get_buf(DSTRING *p_ds);
32 extern inline size_t ds_get_length(const DSTRING *p_ds);
33 extern inline size_t ds_get_buf_size(const DSTRING *p_ds);
34
35
36 /* This function initalizes a dstring using *p_buf as the initial backing
37 *
38 * Parameters
39 * p_buf: Inital buffer backing the dstring
40 * length_string: Length of string in the initial buffer
41 * n_byte_data: Length of initial buffer. Must be at least 1
42 * type_buffer: Type of buffer providing initial backing
43 *
44 * Return codes
45 * DS_E_OK: Init OK
46 * DS_E_INVALID: n_byte_data = 0 length_string too long,
47 * or unknown buffer type
48 */
ds_init(DSTRING * p_ds,char * p_buf,size_t length_string,size_t n_byte_buf,ds_buf_type_t type_buffer)49 int ds_init(DSTRING *p_ds, char *p_buf, size_t length_string,
50 size_t n_byte_buf, ds_buf_type_t type_buffer)
51 {
52 /* Validate buffer size */
53 if (n_byte_buf == 0) {
54 return DS_E_INVALID;
55 }
56
57 /* Set current buffer */
58 p_ds->p_buf = p_buf;
59
60 /* Set size of current string >= rather than > because this function
61 * adds a terminating null */
62 if (length_string >= n_byte_buf) {
63 return DS_E_INVALID;
64 }
65
66 p_ds->n_byte_alloc = n_byte_buf;
67 p_ds->length = length_string;
68 p_ds->p_buf[length_string] = '\0';
69
70 /* Set stack buffer */
71 if (type_buffer == ds_buf_type_stack) {
72 p_ds->p_stack_buf = p_buf;
73 p_ds->n_byte_stack_buf = n_byte_buf;
74 }
75 else if (type_buffer == ds_buf_type_heap) {
76 p_ds->p_stack_buf = (char *) NULL;
77 p_ds->n_byte_stack_buf = 0;
78 }
79 else { /* unknown buffer type */
80 return DS_E_INVALID;
81 }
82
83 return DS_E_OK;
84 } /* end of function ds_init */
85
86
87
88 /* This function frees all memory used by the dstring. After calling this
89 * function, the dstring should not be used again. */
ds_free(DSTRING * p_ds)90 void ds_free(DSTRING *p_ds)
91 {
92 if (p_ds->p_buf != p_ds->p_stack_buf) {
93 txfree((void *) p_ds->p_buf);
94 }
95 } /* end of function ds_free */
96
97
98
99 /* Concatenate string */
ds_cat_str_case(DSTRING * p_ds,const char * sz,ds_case_t case_type)100 int ds_cat_str_case(DSTRING *p_ds, const char *sz, ds_case_t case_type)
101 {
102 return ds_cat_mem_case(p_ds, sz, strlen(sz), case_type);
103 } /* end of function ds_cat_str_case */
104
105
106
107 /* Concatenate character */
ds_cat_char_case(DSTRING * p_ds,char c,ds_case_t case_type)108 int ds_cat_char_case(DSTRING *p_ds, char c, ds_case_t case_type)
109 {
110 return ds_cat_mem_case(p_ds, &c, 1, case_type);
111 } /* end of function ds_cat_char_case */
112
113
114
115 /* Concatenate another dstring */
ds_cat_ds_case(DSTRING * p_ds_dst,const DSTRING * p_ds_src,ds_case_t case_type)116 int ds_cat_ds_case(DSTRING *p_ds_dst, const DSTRING *p_ds_src,
117 ds_case_t case_type)
118 {
119 return ds_cat_mem_case(p_ds_dst, p_ds_src->p_buf, p_ds_src->length,
120 case_type);
121 } /* end of function ds_cat_ds_case */
122
123
124
125 /* General concatenation of a memory buffer. A terminating null is added. */
ds_cat_mem_case(DSTRING * p_ds,const char * p_src,size_t n_char,ds_case_t type_case)126 int ds_cat_mem_case(DSTRING *p_ds, const char *p_src, size_t n_char,
127 ds_case_t type_case)
128 {
129 /* Resize buffer if necessary. Double required size, if available,
130 * to reduce the number of allocations */
131 const size_t length_new = p_ds->length + n_char;
132 const size_t n_byte_needed = length_new + 1;
133 if (n_byte_needed > p_ds->n_byte_alloc) {
134 if (ds_reserve_internal(p_ds,
135 2 * n_byte_needed, n_byte_needed) == DS_E_NO_MEMORY) {
136 return DS_E_NO_MEMORY;
137 }
138 }
139
140 /* For "as-is" can simply memcpy */
141 if (type_case == ds_case_as_is) {
142 char *p_dst = p_ds->p_buf + p_ds->length;
143 (void) memcpy(p_dst, p_src, n_char);
144 p_dst += n_char;
145 *p_dst = '\0';
146 p_ds->length = length_new;
147 return DS_E_OK;
148 }
149
150 /* For lowercasing, work char by char */
151 if (type_case == ds_case_lower) {
152 char *p_dst = p_ds->p_buf + p_ds->length;
153 char *p_dst_end = p_dst + n_char;
154 for ( ; p_dst < p_dst_end; p_dst++, p_src++) {
155 *p_dst = (char) tolower(*p_src);
156 }
157 *p_dst_end = '\0';
158 p_ds->length = length_new;
159 return DS_E_OK;
160 }
161
162 /* Uppercasing done like lowercasing. Note that it would be possible to
163 * use a function pointer and select either tolower() or toupper() based
164 * on type_case, but doing so may degrade performance by inhibiting
165 * inlining. */
166 if (type_case == ds_case_upper) {
167 char *p_dst = p_ds->p_buf + p_ds->length;
168 char *p_dst_end = p_dst + n_char;
169 for ( ; p_dst < p_dst_end; p_dst++, p_src++) {
170 *p_dst = (char) toupper(*p_src);
171 }
172 *p_dst_end = '\0';
173 p_ds->length = length_new;
174 return DS_E_OK;
175 }
176
177 return DS_E_INVALID; /* unknown case type */
178 } /* end of function ds_cat_mem_case */
179
180
181
182 /* Ensure minimum internal buffer size */
ds_reserve(DSTRING * p_ds,size_t n_byte_alloc)183 int ds_reserve(DSTRING *p_ds, size_t n_byte_alloc)
184 {
185 /* Return if buffer already large enough */
186 if (p_ds->n_byte_alloc >= n_byte_alloc) {
187 return DS_E_OK;
188 }
189
190 return ds_reserve_internal(p_ds, n_byte_alloc, 0);
191 } /* end of function ds_reserve */
192
193
194
195 /* This function resizes the buffer for the string and handles freeing
196 * the original alloction, if necessary. It is assumed that the requested
197 * size or sizes are larger than the current size.
198 *
199 * Parameters
200 * p_ds: Dstring pointer
201 * n_byte_alloc_opt: Optimal alloction amount
202 * n_byte_alloc_min: Absolute minimum allocation amount or 0 if no
203 * smaller amount can be allocated
204 *
205 * Return codes
206 * DS_E_OK: At least the minimum allocation was performed
207 * DS_E_NO_MEMORY: Unable to resize the buffer */
ds_reserve_internal(DSTRING * p_ds,size_t n_byte_alloc_opt,size_t n_byte_alloc_min)208 static int ds_reserve_internal(DSTRING *p_ds,
209 size_t n_byte_alloc_opt, size_t n_byte_alloc_min)
210 {
211 size_t n_byte_alloc = n_byte_alloc_opt;
212 /* Allocate. First try (larger) optimal size, and gradually fall back
213 * to min size if that fails and one was provided. */
214 char * p_buf_new;
215 if (n_byte_alloc_min == 0) {
216 n_byte_alloc_min = n_byte_alloc_opt;
217 }
218 for ( ; ; ) {
219 if ((p_buf_new = (char *) malloc(n_byte_alloc)) != (char *) NULL) {
220 break; /* Allocated OK */
221 }
222
223 if (n_byte_alloc == n_byte_alloc_min) { /* min alloc failed */
224 return DS_E_NO_MEMORY;
225 }
226
227 if ((n_byte_alloc /= 2) < n_byte_alloc_min) { /* last try */
228 n_byte_alloc = n_byte_alloc_min;
229 }
230 } /* end of loop trying smaller allocations */
231
232 /* Copy to the new buffer */
233 (void) memcpy(p_buf_new, p_ds->p_buf, p_ds->length + 1);
234
235 /* If there already was a dynamic allocation, free it */
236 if (p_ds->p_buf != p_ds->p_stack_buf) {
237 txfree((void *) p_ds->p_buf);
238 }
239
240 /* Assign new active buffer and its size */
241 p_ds->p_buf = p_buf_new;
242 p_ds->n_byte_alloc = n_byte_alloc;
243
244 return DS_E_OK;
245 } /* end of function ds_reserve_nocheck */
246
247
248
249 /* Concatenate the result of a printf-style format
250 *
251 * Return codes as for ds_cat_vprintf */
ds_cat_printf(DSTRING * p_ds,const char * sz_fmt,...)252 int ds_cat_printf(DSTRING *p_ds, const char *sz_fmt, ...)
253 {
254 va_list p_arg;
255 va_start(p_arg, sz_fmt);
256 const int xrc = ds_cat_vprintf(p_ds, sz_fmt, p_arg);
257 va_end(p_arg);
258 return xrc;
259 } /* end of function ds_cat_printf */
260
261
262
263 /* Concatenate the result of a printf-style format using va_list
264 *
265 * Return codes
266 * DS_E_OK: Formatted OK
267 * DS_E_NO_MEMORY: Unable to allocate memory to resize buffer
268 * DS_E_INVALID: Invalid formatter / data
269 */
ds_cat_vprintf(DSTRING * p_ds,const char * sz_fmt,va_list p_arg)270 int ds_cat_vprintf(DSTRING *p_ds, const char *sz_fmt, va_list p_arg)
271 {
272 /* Make a copy of the argument list in case need to format more than
273 * once */
274 va_list p_arg2;
275 va_copy(p_arg2, p_arg);
276 const size_t n_byte_free = p_ds->n_byte_alloc - p_ds->length;
277 char * const p_dst = p_ds->p_buf + p_ds->length;
278 const int rc = vsnprintf(p_dst, n_byte_free, sz_fmt, p_arg);
279 if (rc < 0) { /* Check for formatting error */
280 return DS_E_INVALID;
281 }
282
283 /* Else check for buffer large enough and set length if it is */
284 if ((size_t) rc < n_byte_free) {
285 p_ds->length += (size_t) rc;
286 return DS_E_OK;
287 }
288
289 /* Else buffer too small, so resize and format again */
290 {
291 /* Double required size to avoid excessive allocations +1 for
292 * null, which is not included in the count returned by snprintf */
293 const size_t n_byte_alloc_min =
294 p_ds->length + (size_t) rc + (size_t) 1;
295 if (ds_reserve_internal(p_ds,
296 2 * n_byte_alloc_min, n_byte_alloc_min) == DS_E_NO_MEMORY) {
297 /* vsnprintf may have written bytes to the buffer.
298 * Ensure that dstring in a consistent state by writing
299 * a null at the length of the string */
300 p_ds->p_buf[p_ds->length] = '\0';
301 return DS_E_NO_MEMORY;
302 }
303 const size_t n_byte_free2 = p_ds->n_byte_alloc - p_ds->length;
304 char * const p_dst2 = p_ds->p_buf + p_ds->length;
305 const int rc2 = vsnprintf(p_dst2, n_byte_free2, sz_fmt, p_arg2);
306 if (rc2 < 0) { /* Check for formatting error */
307 /* vsnprintf may have written bytes to the buffer.
308 * Ensure that dstring in a consistent state by writing
309 * a null at the length of the string */
310 p_ds->p_buf[p_ds->length] = '\0';
311 return DS_E_INVALID;
312 }
313
314 /* Else update length. No need to check buffer size since it was
315 * sized to fit the string. */
316 p_ds->length += (size_t) rc2;
317 return DS_E_OK;
318 }
319 } /* end of function ds_cat_vprintf */
320
321
322
323
324 /* Reallocate/free to eliminate unused buffer space.
325 *
326 * Return codes
327 * DS_E_OK: Compacted OK
328 * DS_E_NO_MEMORY: Compaction failed, but dstring still valid */
ds_compact(DSTRING * p_ds)329 int ds_compact(DSTRING *p_ds)
330 {
331 const size_t n_byte_alloc_min = p_ds->length + 1;
332
333 /* If the string is in the stack buffer, there is nothing to do */
334 if (p_ds->p_stack_buf == p_ds->p_buf) {
335 return DS_E_OK;
336 }
337
338 /* Else if the string will fit in the stack buffer, copy it there and
339 * free the allocation. */
340 if (p_ds->n_byte_stack_buf >= n_byte_alloc_min) {
341 (void) memcpy(p_ds->p_stack_buf, p_ds->p_buf, n_byte_alloc_min);
342 txfree((void *) p_ds->p_buf);
343 p_ds->p_buf = p_ds->p_stack_buf;
344 p_ds->n_byte_alloc = p_ds->n_byte_stack_buf;
345 return DS_E_OK;
346 }
347
348 /* Else if the heap buffer is the minimum size, there is nothng to do */
349 if (n_byte_alloc_min == p_ds->n_byte_alloc) {
350 return DS_E_OK;
351 }
352
353 /* Else realloc the heap buffer */
354 {
355 void *p = TREALLOC(char, p_ds->p_buf, n_byte_alloc_min);
356 if (p == NULL) {
357 return DS_E_NO_MEMORY;
358 }
359 p_ds->p_buf = (char *) p;
360 p_ds->n_byte_alloc = n_byte_alloc_min;
361 return DS_E_OK;
362 }
363 } /* end of function ds_compact */
364
365
366
367 #ifdef DSTRING_UNIT_TEST
368 #if defined (_WIN32) && !defined(CONSOLE)
369 #include "ngspice/wstdio.h"
370 #endif
371 static void ds_print_info(DSTRING *p_ds, FILE *fp, const char *sz_id);
372 static int ds_test_from_macro(FILE *fp);
373 static int ds_test_from_stack(FILE *fp);
374 static int ds_test_from_heap(FILE *fp);
375 static int ds_test1(DSTRING *p_ds, FILE *fp);
376
377
ds_test(FILE * fp)378 int ds_test(FILE *fp)
379 {
380 if (ds_test_from_macro(fp) != 0) { /* create from macro and run test */
381 return -1;
382 }
383 if (ds_test_from_stack(fp) != 0) { /* create from stack */
384 return -1;
385 }
386 if (ds_test_from_heap(fp) != 0) { /* create from heap */
387 return -1;
388 }
389
390 return 0;
391 } /* end of function ds_test */
392
393
394
395 /* Run tests from a macro-created dstring */
ds_test_from_macro(FILE * fp)396 static int ds_test_from_macro(FILE *fp)
397 {
398 DS_CREATE(ds, 10);
399 (void) fprintf(fp, "Macro initialization\n");
400 return ds_test1(&ds, fp);
401 } /* end of function ds_test_from_macro */
402
403
404
405 /* Run tests from a manually created stack-backed dstring */
ds_test_from_stack(FILE * fp)406 static int ds_test_from_stack(FILE *fp)
407 {
408 static char p_buf[30] = "Hello World";
409 DSTRING ds;
410 (void) fprintf(fp, "Stack initialization\n");
411 (void) ds_init(&ds, p_buf, 11, sizeof p_buf, ds_buf_type_stack);
412 return ds_test1(&ds, fp);
413 } /* end of function ds_test_from_stack */
414
415
416
417 /* Run tests from a heap-backed dstring */
ds_test_from_heap(FILE * fp)418 static int ds_test_from_heap(FILE *fp)
419 {
420 char *p_buf = (char *) malloc(25);
421 if (p_buf == (char *) NULL) {
422 return -1;
423 }
424 (void) memcpy(p_buf, "Heap", 4);
425 DSTRING ds;
426 (void) ds_init(&ds, p_buf, 4, 25, ds_buf_type_heap);
427 (void) fprintf(fp, "Heap initialization\n");
428 return ds_test1(&ds, fp);
429 } /* end of function ds_test_from_heap */
430
431
432
ds_test1(DSTRING * p_ds,FILE * fp)433 static int ds_test1(DSTRING *p_ds, FILE *fp)
434 {
435 /* Print info on entry */
436 ds_print_info(p_ds, fp, "On entry to ds_test1\n");
437
438 int i;
439 for (i = 0; i < 10; i++) {
440 if (ds_cat_str(p_ds, "Abc") != 0) {
441 (void) fprintf(fp, "Unable to cat string %d.\n", i);
442 return -1;
443 }
444 if (ds_cat_str_case(p_ds, "Abc", ds_case_as_is) != 0) {
445 (void) fprintf(fp, "Unable to cat string as-is %d.\n", i);
446 return -1;
447 }
448 if (ds_cat_str_case(p_ds, "Abc", ds_case_upper) != 0) {
449 (void) fprintf(fp, "Unable to cat string upper %d.\n", i);
450 return -1;
451 }
452 if (ds_cat_str_case(p_ds, "Abc", ds_case_lower) != 0) {
453 (void) fprintf(fp, "Unable to cat string lower %d.\n", i);
454 return -1;
455 }
456 if (ds_cat_char(p_ds, 'z') != 0) {
457 (void) fprintf(fp, "Unable to cat char %d.\n", i);
458 return -1;
459 }
460 if (ds_cat_char_case(p_ds, 'z', ds_case_as_is) != 0) {
461 (void) fprintf(fp, "Unable to cat char as-is %d.\n", i);
462 return -1;
463 }
464 if (ds_cat_char_case(p_ds, 'z', ds_case_upper) != 0) {
465 (void) fprintf(fp, "Unable to cat char upper %d.\n", i);
466 return -1;
467 }
468 if (ds_cat_char_case(p_ds, 'Z', ds_case_lower) != 0) {
469 (void) fprintf(fp, "Unable to cat char lower %d.\n", i);
470 return -1;
471 }
472
473 if (ds_cat_mem(p_ds, "Zyxw", 4) != 0) {
474 (void) fprintf(fp, "Unable to cat string %d.\n", i);
475 return -1;
476 }
477 if (ds_cat_mem_case(p_ds, "Zyxw", 4, ds_case_as_is) != 0) {
478 (void) fprintf(fp, "Unable to cat string as-is %d.\n", i);
479 return -1;
480 }
481 if (ds_cat_mem_case(p_ds, "Zyxw", 4, ds_case_upper) != 0) {
482 (void) fprintf(fp, "Unable to cat string upper %d.\n", i);
483 return -1;
484 }
485 if (ds_cat_mem_case(p_ds, "Zyxw", 4, ds_case_lower) != 0) {
486 (void) fprintf(fp, "Unable to cat string lower %d.\n", i);
487 return -1;
488 }
489
490 if (ds_cat_printf(p_ds, "--- And finally a formatted %s (%d)",
491 "string", i) != 0) {
492 (void) fprintf(fp, "Unable to cat formatted string %d.\n", i);
493 return -1;
494 }
495
496 /* Print info after cats */
497 ds_print_info(p_ds, fp, "After appending strings");
498
499 /* Truncate the string */
500 if (ds_set_length(p_ds, i * (size_t) 10) != 0) {
501 (void) fprintf(fp, "Unable to set size %d.\n", i);
502 return -1;
503 }
504
505 /* Print info after truncation */
506 ds_print_info(p_ds, fp, "After setting length");
507
508 /* Compact the string */
509 if (ds_compact(p_ds) != 0) {
510 (void) fprintf(fp, "Unable to compact %d.\n", i);
511 return -1;
512 }
513
514 /* Print info after compaction */
515 ds_print_info(p_ds, fp, "After compacting the string");
516 } /* end of loop over tests */
517
518 ds_free(p_ds); /* free buffer if allocated */
519
520 return 0;
521 } /* end of funtion ds_test */
522
523
524
525 /* Print some info about the DSTRING */
ds_print_info(DSTRING * p_ds,FILE * fp,const char * sz_id)526 static void ds_print_info(DSTRING *p_ds, FILE *fp, const char *sz_id)
527 {
528 (void) fprintf(fp, "%s: length = %zu; "
529 "allocated buffer size = %zu; value = \"%s\"; "
530 "address of active buffer = %p; "
531 "address of stack buffer = %p; "
532 "size of stack buffer = %zu\n",
533 sz_id,
534 ds_get_length(p_ds), ds_get_buf_size(p_ds),
535 ds_get_buf(p_ds), ds_get_buf(p_ds),
536 p_ds->p_stack_buf, p_ds->n_byte_stack_buf);
537 } /* end of function ds_print_info */
538
539
540
541 #endif /* DSTRING_UNIT_TEST */
542
543
544
545