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