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