chiark / gitweb /
Fixed typo in `NAME' section.
[mLib] / dstr.c
1 /* -*-c-*-
2  *
3  * $Id: dstr.c,v 1.9 1999/07/06 19:16:06 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.9  1999/07/06 19:16:06  mdw
34  * Simplify buffer-growing algorithm.  Just double it each time.
35  *
36  * Revision 1.8  1999/06/01 09:47:52  mdw
37  * Fix nasty bugs in `dstr_vputf'.
38  *
39  * Revision 1.7  1999/05/21 22:14:30  mdw
40  * Take advantage of the new dynamic string macros.
41  *
42  * Revision 1.6  1999/05/21 08:38:33  mdw
43  * Implement some more functions in terms of macros.
44  *
45  * Revision 1.5  1999/05/13 22:47:57  mdw
46  * Misc documentation fixes.  Change `-ise' to `-ize' throughout.
47  *
48  * Revision 1.4  1999/05/06 19:51:35  mdw
49  * Reformatted the LGPL notice a little bit.
50  *
51  * Revision 1.3  1999/05/05 18:50:31  mdw
52  * Change licensing conditions to LGPL.
53  *
54  * Revision 1.2  1998/12/15 23:53:22  mdw
55  * New functions `dstr_putf' and `dstr_vputf' which do `printf'-style
56  * formatting in a safe way.
57  *
58  * Revision 1.1.1.1  1998/06/17 23:44:42  mdw
59  * Initial version of mLib
60  *
61  */
62
63 /*----- Header files ------------------------------------------------------*/
64
65 #include <ctype.h>
66 #include <float.h>
67 #include <math.h>
68 #include <stdarg.h>
69 #include <stdio.h>
70 #include <stdlib.h>
71 #include <string.h>
72
73 #include "alloc.h"
74 #include "dstr.h"
75
76 /*----- Tunable constants -------------------------------------------------*/
77
78 /* --- Buffer expansion parameters --- *
79  *
80  * If the buffer is empty, it is set to @DSTR_INITSZ@ bytes in size.
81  * Otherwise, it's set to the next power of two that's large enough.  This is
82  * memory-hungry, but efficient.
83  */
84
85 #define DSTR_INITSZ 256                 /* Initial buffer size */
86
87 /* --- Parameters for @dstr_putf@ --- *
88  *
89  * For each format specifier, at least @DSTR_PUTFSTEP@ bytes are ensured
90  * before writing the formatted result.
91  */
92
93 #define DSTR_PUTFSTEP 64                /* Buffer size for @putf@ */
94
95 /*----- Main code ---------------------------------------------------------*/
96
97 /* --- @dstr_create@ --- *
98  *
99  * Arguments:   @dstr *d@ = pointer to a dynamic string block
100  *
101  * Returns:     ---
102  *
103  * Use:         Initializes a dynamic string.
104  */
105
106 void dstr_create(dstr *d) { DCREATE(d); }
107
108 /* --- @dstr_destroy@ --- *
109  *
110  * Arguments:   @dstr *d@ = pointer to a dynamic string block
111  *
112  * Returns:     ---
113  *
114  * Use:         Reclaims the space used by a dynamic string.
115  */
116
117 void dstr_destroy(dstr *d) { DDESTROY(d); }
118
119 /* --- @dstr_reset@ --- *
120  *
121  * Arguments:   @dstr *d@ = pointer to a dynaimc string block
122  *
123  * Returns:     ---
124  *
125  * Use:         Resets a string so that new data gets put at the beginning.
126  */
127
128 void dstr_reset(dstr *d) { DRESET(d); }
129
130 /* --- @dstr_ensure@ --- *
131  *
132  * Arguments:   @dstr *d@ = pointer to a dynamic string block
133  *              @size_t sz@ = amount of free space to ensure
134  *
135  * Returns:     ---
136  *
137  * Use:         Ensures that at least @sz@ bytes are available in the
138  *              given string.
139  */
140
141 void dstr_ensure(dstr *d, size_t sz)
142 {
143   size_t rq = d->len + sz;
144   size_t nsz;
145
146   /* --- If we have enough space, just leave it --- */
147
148   if (rq <= d->sz)
149     return;
150
151   /* --- Grow the buffer --- */
152
153   nsz = d->sz;
154
155   if (nsz == 0 && rq < DSTR_INITSZ)
156     nsz = DSTR_INITSZ;
157   else
158     do nsz <<= 1; while (nsz < rq);
159
160   if (d->buf)
161     d->buf = xrealloc(d->buf, nsz);
162   else
163     d->buf = xmalloc(nsz);
164   d->sz = nsz;
165 }
166
167 /* --- @dstr_putc@ --- *
168  *
169  * Arguments:   @dstr *d@ = pointer to a dynamic string block
170  *              @char ch@ = character to append
171  *
172  * Returns:     ---
173  *
174  * Use:         Appends a character to a string.
175  */
176
177 void dstr_putc(dstr *d, char ch) { DPUTC(d, ch); }
178
179 /* --- @dstr_putz@ --- *
180  *
181  * Arguments:   @dstr *d@ = pointer to a dynamic string block
182  *
183  * Returns:     ---
184  *
185  * Use:         Appends a null byte to a string.  The null byte does not
186  *              contribute to the string's length, and will be overwritten
187  *              by subsequent `put' operations.
188  */
189
190 void dstr_putz(dstr *d) { DPUTZ(d); }
191
192 /* --- @dstr_puts@ --- *
193  *
194  * Arguments:   @dstr *d@ = pointer to a dynamic string block
195  *              @const char *s@ = pointer to string to append
196  *
197  * Returns:     ---
198  *
199  * Use:         Appends a character string to a string.  A trailing null
200  *              byte is added, as for @dstr_putz@.
201  */
202
203 void dstr_puts(dstr *d, const char *s) { DPUTS(d, s); }
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:     The number of characters written to the string.
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   dstr dd = DSTR_INIT;
223
224   while (*p) {
225     unsigned f;
226     int wd, prec;
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     DPUTC(&dd, '%');
251     f = 0;
252     sz = DSTR_PUTFSTEP;
253
254     for (;;) {
255       switch (*p) {
256
257         /* --- Various simple flags --- */
258
259         case '+':
260         case '-':
261         case '#':
262         case '0':
263           goto putch;
264         case 'h':
265           f |= f_short;
266           goto putch;
267         case 'l':
268           f |= f_long;
269           goto putch;
270         case 'L':
271           f |= f_Long;
272           goto putch;
273         case 0:
274           goto finished;
275
276         /* --- Field widths and precision specifiers --- */
277
278         {
279           int *ip;
280
281         case '.':
282           DPUTC(&dd, '.');
283           ip = &prec;
284           f |= f_prec;
285           goto getnum;
286         case '*':
287           ip = &wd;
288           f |= f_wd;
289           goto getnum;
290         default:
291           if (isdigit((unsigned char)*p)) {
292             f |= f_wd;
293             ip = &wd;
294             goto getnum;
295           }
296           DPUTC(d, *p);
297           goto formatted;
298         getnum:
299           *ip = 0;
300           if (*p == '*') {
301             *ip = va_arg(ap, int);
302             DENSURE(&dd, DSTR_PUTFSTEP);
303             dd.len += sprintf(dd.buf + dd.len, "%i", *ip);
304           } else {
305             *ip = *p - '0';
306             DPUTC(&dd, *p);
307             p++;
308             while (isdigit((unsigned char)*p)) {
309               DPUTC(&dd, *p);
310               *ip = 10 * *ip + *p++ - '0';
311             }
312           }
313           break;
314         }
315
316         /* --- Output formatting --- */
317
318         case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
319           DPUTC(&dd, *p);
320           DPUTZ(&dd);
321           if ((f & f_prec) && prec + 16 > sz)
322             sz = prec + 16;
323           if ((f & f_wd) && wd + 1> sz)
324             sz = wd + 1;
325           DENSURE(d, sz);
326           if (f & f_long)
327             d->len += sprintf(d->buf + d->len, dd.buf,
328                               va_arg(ap, unsigned long));
329           else
330             d->len += sprintf(d->buf + d->len, dd.buf,
331                               va_arg(ap, unsigned int));
332           goto formatted;
333
334         case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
335           DPUTC(&dd, *p);
336           DPUTZ(&dd);
337           if (*p == 'f') {
338             size_t mx = (f & f_Long ? LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16;
339             if (mx > sz)
340               sz = mx;
341           }
342           if ((f & f_prec) == 0)
343             prec = 6;
344           if ((f & f_prec))
345             sz += prec + 16;        
346           if ((f & f_wd) && wd + 1 > sz)
347             sz = wd + 1;
348           DENSURE(d, sz);
349           if (f & f_Long)
350             d->len += sprintf(d->buf + d->len, dd.buf,
351                               va_arg(ap, long double));
352           else
353             d->len += sprintf(d->buf + d->len, dd.buf,
354                               va_arg(ap, double));
355           goto formatted;
356
357         case 'c':
358           DPUTC(&dd, *p);
359           DPUTZ(&dd);
360           if ((f & f_wd) && wd + 1> sz)
361             sz = wd + 1;
362           DENSURE(d, sz);
363           d->len += sprintf(d->buf + d->len, dd.buf,
364                             va_arg(ap, unsigned char));
365           goto formatted;
366
367         case 's': {
368           const char *s = va_arg(ap, const char *);
369           sz = strlen(s);
370           DPUTC(&dd, *p);
371           DPUTZ(&dd);
372           if (f & f_prec)
373             sz = prec;
374           if ((f & f_wd) && wd > sz)
375             sz = wd;
376           DENSURE(d, sz + 1);
377           d->len += sprintf(d->buf + d->len, dd.buf, s);
378           goto formatted;
379         }
380
381         case 'p':
382           DPUTC(&dd, *p);
383           DPUTZ(&dd);
384           if ((f & f_prec) && prec + 16 > sz)
385             sz = prec + 16;
386           if ((f & f_wd) && wd + 1> sz)
387             sz = wd + 1;
388           DENSURE(d, sz);
389           d->len += sprintf(d->buf + d->len, dd.buf,
390                             va_arg(ap, const void *));
391           goto formatted;
392
393         case 'n':
394           if (f & f_long)
395             *va_arg(ap, long *) = (long)(d->len - n);
396           else if (f & f_short)
397             *va_arg(ap, short *) = (short)(d->len - n);
398           else
399             *va_arg(ap, int *) = (int)(d->len - n);
400           goto formatted;
401
402         /* --- Other random stuff --- */
403
404         putch:
405           DPUTC(&dd, *p);
406           p++;
407           break;
408       }
409     }
410
411   formatted:
412     DRESET(&dd);
413     q = ++p;
414   }
415
416   DPUTM(d, q, p - q);
417 finished:
418   DPUTZ(d);
419   DDESTROY(&dd);
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:     The number of characters written to the string.
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) { DPUTD(d, s); }
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) { DPUTM(d, p, sz); }
471
472 /* --- @dstr_tidy@ --- *
473  *
474  * Arguments:   @dstr *d@ = pointer to a dynamic string block
475  *
476  * Returns:     ---
477  *
478  * Use:         Reduces the amount of memory used by a string.  A trailing
479  *              null byte is added, as for @dstr_putz@.
480  */
481
482 void dstr_tidy(dstr *d)
483 {
484   dstr_putz(d);
485   d->buf = xrealloc(d->buf, d->len + 1);
486   d->sz = d->len + 1;
487 }
488
489 /* --- @dstr_putline@ --- *
490  *
491  * Arguments:   @dstr *d@ = pointer to a dynamic string block
492  *              @FILE *fp@ = a stream to read from
493  *
494  * Returns:     The number of characters read into the buffer, or @EOF@ if
495  *              end-of-file was reached before any characters were read.
496  *
497  * Use:         Appends the next line from the given input stream to the
498  *              string.  A trailing newline is not added; a trailing null
499  *              byte is appended, as for @dstr_putz@.
500  */
501
502 int dstr_putline(dstr *d, FILE *fp)
503 {
504   size_t left = d->sz - d->len;
505   size_t off = d->len;
506   int rd = 0;
507   int ch;
508
509   for (;;) {
510
511     /* --- Make sure there's some buffer space --- */
512
513     if (!left) {
514       dstr_ensure(d, 1);
515       left = d->sz - off;
516     }
517
518     /* --- Read the next byte --- */
519
520     ch = getc(fp);
521
522     /* --- End-of-file when no characters read is special --- */
523
524     if (ch == EOF && !rd)
525       return (EOF);
526
527     /* --- End-of-file or newline ends the loop --- */
528
529     if (ch == EOF || ch == '\n') {
530       d->buf[off] = 0;
531       d->len = off;
532       return rd;
533     }
534
535     /* --- Append the character and continue --- */
536
537     d->buf[off++] = ch;
538     left--; rd++;
539   }
540 }
541
542 /* --- @dstr_write@ --- *
543  *
544  * Arguments:   @dstr *d@ = pointer to a dynamic string block
545  *              @FILE *fp@ = a stream to write on
546  *
547  * Returns:     The number of bytes written (as for @fwrite@).
548  *
549  * Use:         Writes a dynamic string to a file.
550  */
551
552 size_t dstr_write(const dstr *d, FILE *fp) { return (DWRITE(d, fp)); }
553
554 /*----- That's all, folks -------------------------------------------------*/