chiark / gitweb /
Use mdwopt from common files distrib.
[mLib] / dstr.c
1 /* -*-c-*-
2  *
3  * $Id: dstr.c,v 1.2 1998/12/15 23:53:22 mdw Exp $
4  *
5  * Handle dynamically growing strings
6  *
7  * (c) 1998 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------*
11  *
12  * This file is part of the mLib utilities library.
13  *
14  * mLib is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * mLib is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with mLib; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------*
30  *
31  * $Log: dstr.c,v $
32  * Revision 1.2  1998/12/15 23:53:22  mdw
33  * New functions `dstr_putf' and `dstr_vputf' which do `printf'-style
34  * formatting in a safe way.
35  *
36  * Revision 1.1.1.1  1998/06/17 23:44:42  mdw
37  * Initial version of mLib
38  *
39  */
40
41 /*----- Header files ------------------------------------------------------*/
42
43 #include <ctype.h>
44 #include <float.h>
45 #include <math.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50
51 #include "alloc.h"
52 #include "dstr.h"
53
54 /*----- Tunable constants -------------------------------------------------*/
55
56 #define DSTR_INITSZ 256                 /* Initial buffer size */
57 #define DSTR_INCSZ 4096                 /* Threshhold for doubling */
58 #define DSTR_PUTFSTEP 64                /* Buffer size for @putf@ */
59
60 /*----- Main code ---------------------------------------------------------*/
61
62 /* --- @dstr_create@ --- *
63  *
64  * Arguments:   @dstr *d@ = pointer to a dynamic string block
65  *
66  * Returns:     ---
67  *
68  * Use:         Initialises a dynamic string.
69  */
70
71 void dstr_create(dstr *d)
72 {
73   d->sz = 0;
74   d->len = 0;
75   d->buf = 0;
76 }
77
78 /* --- @dstr_destroy@ --- *
79  *
80  * Arguments:   @dstr *d@ = pointer to a dynamic string block
81  *
82  * Returns:     ---
83  *
84  * Use:         Reclaims the space used by a dynamic string.
85  */
86
87 void dstr_destroy(dstr *d)
88 {
89   if (d->buf)
90     free(d->buf);
91   d->buf = 0;
92   d->len = 0;
93   d->sz = 0;
94 }
95
96 /* --- @dstr_reset@ --- *
97  *
98  * Arguments:   @dstr *d@ = pointer to a dynaimc string block
99  *
100  * Returns:     ---
101  *
102  * Use:         Resets a string so that new data gets put at the beginning.
103  */
104
105 void dstr_reset(dstr *d)
106 {
107   d->len = 0;
108 }
109
110 /* --- @dstr_ensure@ --- *
111  *
112  * Arguments:   @dstr *d@ = pointer to a dynamic string block
113  *              @size_t sz@ = amount of free space to ensure
114  *
115  * Returns:     ---
116  *
117  * Use:         Ensures that at least @sz@ bytes are available in the
118  *              given string.
119  */
120
121 void dstr_ensure(dstr *d, size_t sz)
122 {
123   size_t rq = d->len + sz;
124   size_t nsz;
125
126   /* --- If we have enough space, just leave it --- */
127
128   if (rq <= d->sz)
129     return;
130
131   /* --- Grow the buffer --- *
132    *
133    * For small buffers, just double the size.  For big buffers, make them
134    * a multiple of some suitably large chunk size.
135    */
136
137   nsz = d->sz;
138
139   do {
140     if (nsz == 0)
141       nsz = DSTR_INITSZ;
142     else if (d->sz < 0x1000)
143       nsz <<= 1;
144     else
145       nsz = (rq + 0x0fff) & ~0x0fff;
146   } while (rq > nsz);
147
148   if (d->buf)
149     d->buf = xrealloc(d->buf, nsz);
150   else
151     d->buf = xmalloc(nsz);
152   d->sz = nsz;
153 }
154
155 /* --- @dstr_putc@ --- *
156  *
157  * Arguments:   @dstr *d@ = pointer to a dynamic string block
158  *              @char ch@ = character to append
159  *
160  * Returns:     ---
161  *
162  * Use:         Appends a character to a string.
163  */
164
165 void dstr_putc(dstr *d, char ch)
166 {
167   DPUTC(d, ch);
168 }
169
170 /* --- @dstr_putz@ --- *
171  *
172  * Arguments:   @dstr *d@ = pointer to a dynamic string block
173  *
174  * Returns:     ---
175  *
176  * Use:         Appends a null byte to a string.  The null byte does not
177  *              contribute to the string's length, and will be overwritten
178  *              by subsequent `put' operations.
179  */
180
181 void dstr_putz(dstr *d)
182 {
183   DPUTZ(d);
184 }
185
186 /* --- @dstr_puts@ --- *
187  *
188  * Arguments:   @dstr *d@ = pointer to a dynamic string block
189  *              @const char *s@ = pointer to string to append
190  *
191  * Returns:     ---
192  *
193  * Use:         Appends a character string to a string.  A trailing null
194  *              byte is added, as for @dstr_putz@.
195  */
196
197 void dstr_puts(dstr *d, const char *s)
198 {
199   DPUTS(d, s);
200 }
201
202 /* --- @dstr_vputf@ --- *
203  *
204  * Arguments:   @dstr *d@ = pointer to a dynamic string block
205  *              @const char *p@ = pointer to @printf@-style format string
206  *              @va_list ap@ = argument handle
207  *
208  * Returns:     ---
209  *
210  * Use:         As for @dstr_putf@, but may be used as a back-end to user-
211  *              supplied functions with @printf@-style interfaces.
212  */
213
214 int dstr_vputf(dstr *d, const char *p, va_list ap)
215 {
216   const char *q = p;
217   size_t n = d->len;
218   size_t sz;
219
220   while (*p) {
221     unsigned f;
222     int wd, prec;
223     dstr dd;
224
225     enum {
226       f_short = 1,
227       f_long = 2,
228       f_Long = 4,
229       f_wd = 8,
230       f_prec = 16
231     };
232
233     /* --- Most stuff gets passed on through --- */
234
235     if (*p != '%') {
236       p++;
237       continue;
238     }
239
240     /* --- Dump out what's between @q@ and @p@ --- */
241
242     DPUTM(d, q, p - q);
243     p++;
244
245     /* --- Sort out the various silly flags and things --- */
246
247     dstr_create(&dd);
248     DPUTC(&dd, '%');
249     f = 0;
250     sz = DSTR_PUTFSTEP;
251
252     for (;;) {
253       switch (*p) {
254
255         /* --- Various simple flags --- */
256
257         case '+':
258         case '-':
259         case '#':
260         case '0':
261           goto putch;
262         case 'h':
263           f |= f_short;
264           goto putch;
265         case 'l':
266           f |= f_long;
267           goto putch;
268         case 'L':
269           f |= f_Long;
270           goto putch;
271         case 0:
272           goto finished;
273
274         /* --- Field widths and precision specifiers --- */
275
276         {
277           int *ip;
278
279         case '.':
280           DPUTC(&dd, '.');
281           ip = &prec;
282           f |= f_prec;
283           goto getnum;
284         case '*':
285           ip = &wd;
286           f |= f_wd;
287           goto getnum;
288         default:
289           if (isdigit((unsigned char)*p)) {
290             f |= f_prec;
291             ip = &wd;
292             goto getnum;
293           }
294           DPUTC(d, *p);
295           goto formatted;
296         getnum:
297           *ip = 0;
298           if (*p == '*') {
299             *ip = va_arg(ap, int);
300             DENSURE(&dd, DSTR_PUTFSTEP);
301             dd.len += sprintf(dd.buf + dd.len, "%i", *ip);
302           } else {
303             *ip = *p + '0';
304             DPUTC(&dd, *p);
305             p++;
306             while (isdigit((unsigned char)*p)) {
307               DPUTC(&dd, *p);
308               *ip = 10 * *ip + *p++ + '0';
309             }
310           }
311           break;
312         }
313
314         /* --- Output formatting --- */
315
316         case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
317           DPUTC(&dd, *p);
318           DPUTZ(&dd);
319           if ((f & f_prec) && prec + 16 > sz)
320             sz = prec + 16;
321           if ((f & f_wd) && wd + 1> sz)
322             sz = wd + 1;
323           DENSURE(d, sz);
324           if (f & f_long)
325             d->len += sprintf(d->buf + d->len, dd.buf,
326                               va_arg(ap, unsigned long));
327           else
328             d->len += sprintf(d->buf + d->len, dd.buf,
329                               va_arg(ap, unsigned int));
330           goto formatted;
331
332         case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
333           DPUTC(&dd, *p);
334           DPUTZ(&dd);
335           if (*p == 'f') {
336             size_t mx = (f & f_Long ? LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16;
337             if (mx > sz)
338               sz = mx;
339           }
340           if ((f & f_prec) == 0)
341             prec = 6;
342           if ((f & f_prec))
343             sz += prec + 16;        
344           if ((f & f_wd) && wd + 1 > sz)
345             sz = wd + 1;
346           DENSURE(d, sz);
347           if (f & f_Long)
348             d->len += sprintf(d->buf + d->len, dd.buf,
349                               va_arg(ap, long double));
350           else
351             d->len += sprintf(d->buf + d->len, dd.buf,
352                               va_arg(ap, double));
353           goto formatted;
354
355         case 'c':
356           DPUTC(&dd, *p);
357           DPUTZ(&dd);
358           if ((f & f_wd) && wd + 1> sz)
359             sz = wd + 1;
360           DENSURE(d, sz);
361           d->len += sprintf(d->buf + d->len, dd.buf,
362                             va_arg(ap, unsigned char));
363           goto formatted;
364
365         case 's': {
366           const char *s = va_arg(ap, const char *);
367           sz = strlen(s);
368           DPUTC(&dd, *p);
369           DPUTZ(&dd);
370           if (f & f_prec)
371             sz = prec;
372           if ((f & f_wd) && wd > sz)
373             sz = wd;
374           DENSURE(d, sz + 1);
375           d->len += sprintf(d->buf + d->len, dd.buf, s);
376           goto formatted;
377         }
378
379         case 'p':
380           DPUTC(&dd, *p);
381           DPUTZ(&dd);
382           if ((f & f_prec) && prec + 16 > sz)
383             sz = prec + 16;
384           if ((f & f_wd) && wd + 1> sz)
385             sz = wd + 1;
386           DENSURE(d, sz);
387           d->len += sprintf(d->buf + d->len, dd.buf,
388                             va_arg(ap, const void *));
389           goto formatted;
390
391         case 'n':
392           if (f & f_long)
393             *va_arg(ap, long *) = (long)(d->len - n);
394           else if (f & f_short)
395             *va_arg(ap, short *) = (short)(d->len - n);
396           else
397             *va_arg(ap, int *) = (int)(d->len - n);
398           goto formatted;
399
400         /* --- Other random stuff --- */
401
402         putch:
403           DPUTC(&dd, *p);
404           p++;
405           break;
406       }
407     }
408
409   formatted:
410     dstr_destroy(&dd);
411     q = ++p;
412   }
413
414   DPUTM(d, q, p - q);
415 finished:
416   DPUTZ(d);
417   return (d->len - n);
418 }
419
420 /* --- @dstr_putf@ --- *
421  *
422  * Arguments:   @dstr *d@ = pointer to a dynamic string block
423  *              @const char *p@ = pointer to @printf@-style format string
424  *              @...@ = argument handle
425  *
426  * Returns:     ---
427  *
428  * Use:         Writes a piece of text to a dynamic string, doing @printf@-
429  *              style substitutions as it goes.  Intended to be robust if
430  *              faced with malicious arguments, but not if the format string
431  *              itself is malicious.
432  */
433
434 int dstr_putf(dstr *d, const char *p, ...)
435 {
436   int n;
437   va_list ap;
438   va_start(ap, p);
439   n = dstr_vputf(d, p, ap);
440   va_end(ap);
441   return (n);
442 }
443
444 /* --- @dstr_putd@ --- *
445  *
446  * Arguments:   @dstr *d@ = pointer to a dynamic string block
447  *              @const dstr *s@ = pointer to a dynamic string to append
448  *
449  * Returns:     ---
450  *
451  * Use:         Appends a dynamic string to a string.  A trailing null
452  *              byte is added, as for @dstr_putz@.
453  */
454
455 void dstr_putd(dstr *d, const dstr *s)
456 {
457   DPUTD(d, s);
458 }
459
460 /* --- @dstr_putm@ --- *
461  *
462  * Arguments:   @dstr *d@ = pointer to a dynamic string block
463  *              @const void *p@ = pointer to a block to append
464  *              @size_t sz@ = size of the block
465  *
466  * Returns:     Appends an arbitrary data block to a string.  No trailing
467  *              null is appended.
468  */
469
470 void dstr_putm(dstr *d, const void *p, size_t sz)
471 {
472   DPUTM(d, p, sz);
473 }
474
475 /* --- @dstr_tidy@ --- *
476  *
477  * Arguments:   @dstr *d@ = pointer to a dynamic string block
478  *
479  * Returns:     ---
480  *
481  * Use:         Reduces the amount of memory used by a string.  A trailing
482  *              null byte is added, as for @dstr_putz@.
483  */
484
485 void dstr_tidy(dstr *d)
486 {
487   dstr_putz(d);
488   d->buf = xrealloc(d->buf, d->len + 1);
489   d->sz = d->len + 1;
490 }
491
492 /* --- @dstr_putline@ --- *
493  *
494  * Arguments:   @dstr *d@ = pointer to a dynamic string block
495  *              @FILE *fp@ = a stream to read from
496  *
497  * Returns:     The number of characters read into the buffer, or @EOF@ if
498  *              end-of-file was reached before any characters were read.
499  *
500  * Use:         Appends the next line from the given input stream to the
501  *              string.  A trailing newline is not added; a trailing null
502  *              byte is appended, as for @dstr_putz@.
503  */
504
505 int dstr_putline(dstr *d, FILE *fp)
506 {
507   size_t left = d->sz - d->len;
508   size_t off = d->len;
509   int rd = 0;
510   int ch;
511
512   for (;;) {
513
514     /* --- Make sure there's some buffer space --- */
515
516     if (!left) {
517       dstr_ensure(d, 1);
518       left = d->sz - off;
519     }
520
521     /* --- Read the next byte --- */
522
523     ch = getc(fp);
524
525     /* --- End-of-file when no characters read is special --- */
526
527     if (ch == EOF && !rd)
528       return (EOF);
529
530     /* --- End-of-file or newline ends the loop --- */
531
532     if (ch == EOF || ch == '\n') {
533       d->buf[off] = 0;
534       d->len = off;
535       return rd;
536     }
537
538     /* --- Append the character and continue --- */
539
540     d->buf[off++] = ch;
541     left--; rd++;
542   }
543 }
544
545 /* --- @dstr_write@ --- *
546  *
547  * Arguments:   @dstr *d@ = pointer to a dynamic string block
548  *              @FILE *fp@ = a stream to write on
549  *
550  * Returns:     The number of bytes written (as for @fwrite@).
551  *
552  * Use:         Writes a dynamic string to a file.
553  */
554
555 size_t dstr_write(dstr *d, FILE *fp)
556 {
557   return (fwrite(d->buf, 1, d->len, fp));
558 }
559
560 /*----- That's all, folks -------------------------------------------------*/