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