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