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