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