chiark / gitweb /
Boring.
[mLib] / dstr.c
CommitLineData
0875b58f 1/* -*-c-*-
2 *
3ca4c9f8 3 * $Id: dstr.c,v 1.9 1999/07/06 19:16:06 mdw Exp $
0875b58f 4 *
5 * Handle dynamically growing strings
6 *
7 * (c) 1998 Straylight/Edgeware
8 */
9
c846879c 10/*----- Licensing notice --------------------------------------------------*
0875b58f 11 *
12 * This file is part of the mLib utilities library.
13 *
14 * mLib is free software; you can redistribute it and/or modify
c846879c 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 *
0875b58f 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
c846879c 22 * GNU Library General Public License for more details.
23 *
24 * You should have received a copy of the GNU Library General Public
0bd98442 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.
0875b58f 28 */
29
30/*----- Revision history --------------------------------------------------*
31 *
32 * $Log: dstr.c,v $
3ca4c9f8 33 * Revision 1.9 1999/07/06 19:16:06 mdw
34 * Simplify buffer-growing algorithm. Just double it each time.
35 *
0fbb1412 36 * Revision 1.8 1999/06/01 09:47:52 mdw
37 * Fix nasty bugs in `dstr_vputf'.
38 *
0bfb1431 39 * Revision 1.7 1999/05/21 22:14:30 mdw
40 * Take advantage of the new dynamic string macros.
41 *
fb467548 42 * Revision 1.6 1999/05/21 08:38:33 mdw
43 * Implement some more functions in terms of macros.
44 *
96c5fe33 45 * Revision 1.5 1999/05/13 22:47:57 mdw
46 * Misc documentation fixes. Change `-ise' to `-ize' throughout.
47 *
0bd98442 48 * Revision 1.4 1999/05/06 19:51:35 mdw
49 * Reformatted the LGPL notice a little bit.
50 *
c846879c 51 * Revision 1.3 1999/05/05 18:50:31 mdw
52 * Change licensing conditions to LGPL.
53 *
00c7638b 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
0875b58f 60 *
61 */
62
63/*----- Header files ------------------------------------------------------*/
64
00c7638b 65#include <ctype.h>
66#include <float.h>
67#include <math.h>
68#include <stdarg.h>
0875b58f 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
3ca4c9f8 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
0875b58f 85#define DSTR_INITSZ 256 /* Initial buffer size */
3ca4c9f8 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
00c7638b 93#define DSTR_PUTFSTEP 64 /* Buffer size for @putf@ */
0875b58f 94
95/*----- Main code ---------------------------------------------------------*/
96
97/* --- @dstr_create@ --- *
98 *
99 * Arguments: @dstr *d@ = pointer to a dynamic string block
100 *
101 * Returns: ---
102 *
96c5fe33 103 * Use: Initializes a dynamic string.
0875b58f 104 */
105
fb467548 106void dstr_create(dstr *d) { DCREATE(d); }
0875b58f 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
fb467548 117void dstr_destroy(dstr *d) { DDESTROY(d); }
0875b58f 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
fb467548 128void dstr_reset(dstr *d) { DRESET(d); }
0875b58f 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
141void 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
3ca4c9f8 151 /* --- Grow the buffer --- */
0875b58f 152
153 nsz = d->sz;
154
3ca4c9f8 155 if (nsz == 0 && rq < DSTR_INITSZ)
156 nsz = DSTR_INITSZ;
157 else
158 do nsz <<= 1; while (nsz < rq);
0875b58f 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
fb467548 177void dstr_putc(dstr *d, char ch) { DPUTC(d, ch); }
0875b58f 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
fb467548 190void dstr_putz(dstr *d) { DPUTZ(d); }
0875b58f 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
fb467548 203void dstr_puts(dstr *d, const char *s) { DPUTS(d, s); }
0875b58f 204
00c7638b 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 *
96c5fe33 211 * Returns: The number of characters written to the string.
00c7638b 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
217int 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;
0bfb1431 222 dstr dd = DSTR_INIT;
00c7638b 223
224 while (*p) {
225 unsigned f;
226 int wd, prec;
00c7638b 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
00c7638b 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)) {
0fbb1412 292 f |= f_wd;
00c7638b 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 {
0fbb1412 305 *ip = *p - '0';
00c7638b 306 DPUTC(&dd, *p);
307 p++;
308 while (isdigit((unsigned char)*p)) {
309 DPUTC(&dd, *p);
0fbb1412 310 *ip = 10 * *ip + *p++ - '0';
00c7638b 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:
0bfb1431 412 DRESET(&dd);
00c7638b 413 q = ++p;
414 }
415
416 DPUTM(d, q, p - q);
417finished:
418 DPUTZ(d);
0bfb1431 419 DDESTROY(&dd);
00c7638b 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 *
96c5fe33 429 * Returns: The number of characters written to the string.
00c7638b 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
437int 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
0875b58f 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
fb467548 458void dstr_putd(dstr *d, const dstr *s) { DPUTD(d, s); }
0875b58f 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
fb467548 470void dstr_putm(dstr *d, const void *p, size_t sz) { DPUTM(d, p, sz); }
0875b58f 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
482void 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
502int 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
fb467548 552size_t dstr_write(const dstr *d, FILE *fp) { return (DWRITE(d, fp)); }
0875b58f 553
554/*----- That's all, folks -------------------------------------------------*/