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