struct/dstr-putf.c: Remove apparently redundant inclusion of <math.h>.
[mLib] / struct / dstr-putf.c
CommitLineData
002eaee3 1/* -*-c-*-
2 *
002eaee3 3 * `printf'-style formatting for dynamic strings
4 *
5 * (c) 1999 Straylight/Edgeware
6 */
7
d4efbcd9 8/*----- Licensing notice --------------------------------------------------*
002eaee3 9 *
10 * This file is part of the mLib utilities library.
11 *
12 * mLib is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Library General Public License as
14 * published by the Free Software Foundation; either version 2 of the
15 * License, or (at your option) any later version.
d4efbcd9 16 *
002eaee3 17 * mLib is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Library General Public License for more details.
d4efbcd9 21 *
002eaee3 22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib; if not, write to the Free
24 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25 * MA 02111-1307, USA.
26 */
27
002eaee3 28/*----- Header files ------------------------------------------------------*/
29
b3a0ac5e
MW
30#include "config.h"
31
eff136f6 32#include <assert.h>
002eaee3 33#include <ctype.h>
5007fea9 34#include <limits.h>
002eaee3 35#include <stdarg.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39
5d45633f 40#ifdef HAVE_FLOAT_H
41# include <float.h>
42#endif
43
5007fea9
MW
44#ifdef HAVE_STDINT_H
45# include <stdint.h>
46#endif
47
eff136f6 48#include "darray.h"
002eaee3 49#include "dstr.h"
50
51/*----- Tunable constants -------------------------------------------------*/
52
53/*
eff136f6
MW
54 * For each format specifier, at least @PUTFSTEP@ bytes are ensured before
55 * writing the formatted result.
002eaee3 56 */
57
eff136f6
MW
58#define PUTFSTEP 64 /* Buffer size for @putf@ */
59
60/*----- Preliminary definitions -------------------------------------------*/
61
5007fea9
MW
62#ifdef HAVE_FLOAT_H
63# define IF_FLOAT(x) x
64#else
65# define IF_FLOAT(x)
66#endif
67
68#if defined(LLONG_MAX) || defined(LONG_LONG_MAX)
69# define IF_LONGLONG(x) x
70#else
71# define IF_LONGLONG(x)
72#endif
73
74#ifdef INTMAX_MAX
75# define IF_INTMAX(x) x
76#else
77# define IF_INTMAX(x)
78#endif
79
eff136f6
MW
80#define OUTPUT_FMTTYPES(_) \
81 _(i, unsigned int) \
82 _(li, unsigned long) \
5007fea9
MW
83 IF_LONGLONG( _(lli, unsigned long long) ) \
84 _(zi, size_t) \
85 _(ti, ptrdiff_t) \
86 IF_INTMAX( _(ji, uintmax_t) ) \
eff136f6
MW
87 _(s, char *) \
88 _(p, void *) \
89 _(f, double) \
90 _(Lf, long double)
91
92#define PERCENT_N_FMTTYPES(_) \
eff136f6 93 _(n, int *) \
5007fea9
MW
94 _(hhn, char *) \
95 _(hn, short *) \
96 _(ln, long *) \
97 _(zn, size_t *) \
98 _(tn, ptrdiff_t *) \
99 IF_LONGLONG( _(lln, long long *) ) \
100 IF_INTMAX( _(jn, intmax_t *) )
eff136f6
MW
101
102#define FMTTYPES(_) \
103 OUTPUT_FMTTYPES(_) \
104 PERCENT_N_FMTTYPES(_)
105
106enum {
107 fmt_unset = 0,
108#define CODE(code, ty) fmt_##code,
109 FMTTYPES(CODE)
110#undef CODE
111 fmt__limit
112};
113
114typedef struct {
115 int fmt;
116 union {
117#define MEMB(code, ty) ty code;
118 FMTTYPES(MEMB)
119#undef MEMB
120 } u;
121} fmtarg;
122
123DA_DECL(fmtarg_v, fmtarg);
124
125enum {
126 len_std = 0,
5007fea9 127 len_hh,
eff136f6
MW
128 len_h,
129 len_l,
130 len_ll,
5007fea9
MW
131 len_z,
132 len_t,
133 len_j,
eff136f6
MW
134 len_L
135};
136
137#define f_len 0x000fu
138#define f_wd 0x0010u
139#define f_wdarg 0x0020u
140#define f_prec 0x0040u
141#define f_precarg 0x0080u
142#define f_plus 0x0100u
143#define f_minus 0x0200u
144#define f_sharp 0x0400u
145#define f_zero 0x0800u
146#define f_posarg 0x1000u
147
148typedef struct {
149 const char *p;
150 size_t n;
151 unsigned f;
152 int fmt, ch;
153 int wd, prec;
154 int arg;
155} fmtspec;
156
157DA_DECL(fmtspec_v, fmtspec);
002eaee3 158
159/*----- Main code ---------------------------------------------------------*/
160
161/* --- @dstr_vputf@ --- *
162 *
163 * Arguments: @dstr *d@ = pointer to a dynamic string block
164 * @const char *p@ = pointer to @printf@-style format string
5a18a126 165 * @va_list *ap@ = argument handle
002eaee3 166 *
167 * Returns: The number of characters written to the string.
168 *
169 * Use: As for @dstr_putf@, but may be used as a back-end to user-
170 * supplied functions with @printf@-style interfaces.
171 */
172
eff136f6
MW
173static void set_arg(fmtarg_v *av, size_t i, int fmt)
174{
175 size_t j, n;
176
177 n = DA_LEN(av);
178 if (i >= n) {
179 DA_ENSURE(av, i + 1 - n);
180 for (j = n; j <= i; j++) DA(av)[j].fmt = fmt_unset;
181 DA_UNSAFE_EXTEND(av, i + 1 - n);
182 }
183
184 if (DA(av)[i].fmt == fmt_unset) DA(av)[i].fmt = fmt;
185 else assert(DA(av)[i].fmt == fmt);
186}
187
5a18a126 188int dstr_vputf(dstr *d, const char *p, va_list *ap)
002eaee3 189{
002eaee3 190 size_t n = d->len;
eff136f6 191 size_t sz, mx;
002eaee3 192 dstr dd = DSTR_INIT;
eff136f6
MW
193 fmtspec_v sv = DA_INIT;
194 fmtarg_v av = DA_INIT;
195 fmtarg *fa, *fal;
196 fmtspec *fs, *fsl;
197 unsigned f;
198 int i, anext;
199 int wd, prec;
200
201 /* --- Initial pass through the input, parsing format specifiers --- *
202 *
203 * We essentially compile the format string into a vector of @fmtspec@
67ce8094 204 * objects, each of which represents a chunk of literal text followed by a
eff136f6
MW
205 * (possibly imaginary, in the case of the final one) formatting directive.
206 * Output then simply consists of interpreting these specifiers in order.
207 */
208
209 anext = 0;
002eaee3 210
211 while (*p) {
eff136f6
MW
212 f = 0;
213 DA_ENSURE(&sv, 1);
214 fs = &DA(&sv)[DA_LEN(&sv)];
215 DA_UNSAFE_EXTEND(&sv, 1);
216
217 /* --- Find the end of this literal portion --- */
218
219 fs->p = p;
220 while (*p && *p != '%') p++;
221 fs->n = p - fs->p;
002eaee3 222
eff136f6
MW
223 /* --- Some simple cases --- *
224 *
225 * We might have reached the end of the string, or maybe a `%%' escape.
226 */
227
228 if (!*p) { fs->fmt = fmt_unset; fs->ch = 0; break; }
229 p++;
230 if (*p == '%') { fs->fmt = fmt_unset; fs->ch = '%'; p++; continue; }
002eaee3 231
eff136f6 232 /* --- Pick up initial flags --- */
002eaee3 233
eff136f6
MW
234 flags:
235 for (;;) {
236 switch (*p) {
237 case '+': f |= f_plus; break;
238 case '-': f |= f_minus; break;
239 case '#': f |= f_sharp; break;
240 case '0': f |= f_zero; break;
241 default: goto done_flags;
242 }
002eaee3 243 p++;
002eaee3 244 }
245
eff136f6 246 /* --- Pick up the field width --- */
002eaee3 247
eff136f6
MW
248 done_flags:
249 i = 0;
250 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
002eaee3 251
eff136f6 252 /* --- Snag: this might have been an argument position indicator --- */
002eaee3 253
eff136f6
MW
254 if (i && *p == '$' && (!f || f == f_zero)) {
255 f |= f_posarg;
256 fs->arg = i - 1;
257 p++;
258 goto flags;
259 }
002eaee3 260
eff136f6
MW
261 /* --- Set the field width --- *
262 *
263 * If @i@ is nonzero here then we have a numeric field width. Otherwise
264 * it might be `*', maybe with an explicit argument number.
265 */
266
267 if (i) {
268 f |= f_wd;
269 fs->wd = i;
270 } else if (*p == '*') {
271 p++;
272 if (!isdigit((unsigned char)*p))
273 i = anext++;
274 else {
275 i = *p++ - '0';
276 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
277 assert(*p == '$'); p++;
278 assert(i > 0); i--;
279 }
280 f |= f_wd | f_wdarg;
281 set_arg(&av, i, fmt_i); fs->wd = i;
282 }
283
284 /* --- Maybe we have a precision spec --- */
002eaee3 285
eff136f6
MW
286 if (*p == '.') {
287 p++;
288 f |= f_prec;
289 if (isdigit((unsigned char)*p)) {
290 i = *p++ - '0';
291 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
292 fs->prec = i;
293 } else if (*p != '*')
294 fs->prec = 0;
295 else {
296 p++;
297 if (!isdigit((unsigned char)*p))
298 i = anext++;
299 else {
300 i = *p++ - '0';
301 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
302 assert(*p == '$'); p++;
303 assert(i > 0); i--;
002eaee3 304 }
eff136f6
MW
305 f |= f_precarg;
306 set_arg(&av, i, fmt_i); fs->prec = i;
307 }
308 }
002eaee3 309
eff136f6
MW
310 /* --- Maybe some length flags --- */
311
312 switch (*p) {
5007fea9
MW
313 case 'h':
314 p++;
315 if (*p == 'h') { f |= len_hh; p++; } else f |= len_h;
316 break;
317 case 'l':
318 p++;
319 IF_LONGLONG( if (*p == 'l') { f |= len_ll; p++; } else ) f |= len_l;
320 break;
eff136f6 321 case 'L': f |= len_L; p++; break;
5007fea9
MW
322 case 'z': f |= len_z; p++; break;
323 case 't': f |= len_t; p++; break;
324 IF_INTMAX( case 'j': f |= len_j; p++; break; )
eff136f6
MW
325 }
326
327 /* --- The flags are now ready --- */
328
329 fs->f = f;
002eaee3 330
eff136f6
MW
331 /* --- At the end, an actual directive --- */
332
333 fs->ch = *p;
334 switch (*p++) {
335 case '%':
336 fs->fmt = fmt_unset;
337 break;
338 case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
339 switch (f & f_len) {
340 case len_l: fs->fmt = fmt_li; break;
5007fea9
MW
341 case len_z: fs->fmt = fmt_zi; break;
342 case len_t: fs->fmt = fmt_ti; break;
343 IF_LONGLONG( case len_ll: fs->fmt = fmt_lli; break; )
344 IF_INTMAX( case len_j: fs->fmt = fmt_ji; break; )
eff136f6
MW
345 default: fs->fmt = fmt_i;
346 }
347 break;
5007fea9 348 case 'a': case 'A':
eff136f6
MW
349 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
350 fs->fmt = (f & f_len) == len_L ? fmt_Lf : fmt_f;
351 break;
352 case 'c':
353 fs->fmt = fmt_i;
354 break;
355 case 's':
356 fs->fmt = fmt_s;
357 break;
358 case 'p':
359 fs->fmt = fmt_p;
360 break;
361 case 'n':
362 switch (f & f_len) {
5007fea9 363 case len_hh: fs->fmt = fmt_hhn; break;
eff136f6
MW
364 case len_h: fs->fmt = fmt_hn; break;
365 case len_l: fs->fmt = fmt_ln; break;
5007fea9
MW
366 case len_z: fs->fmt = fmt_zn; break;
367 case len_t: fs->fmt = fmt_tn; break;
368 IF_LONGLONG( case len_ll: fs->fmt = fmt_lln; break; )
369 IF_INTMAX( case len_j: fs->fmt = fmt_jn; break; )
eff136f6 370 default: fs->fmt = fmt_n;
002eaee3 371 }
eff136f6
MW
372 break;
373 default:
374 fprintf(stderr,
375 "FATAL dstr_vputf: unknown format specifier `%c'\n", p[-1]);
376 abort();
377 }
378
379 /* --- Finally sort out the argument --- *
380 *
381 * If we don't have explicit argument positions then this comes after the
382 * width and precision; and we don't know the type code until we've
383 * parsed the specifier, so this seems the right place to handle it.
384 */
385
386 if (!(f & f_posarg)) fs->arg = anext++;
387 set_arg(&av, fs->arg, fs->fmt);
388 }
389
390 /* --- Quick pass over the argument vector to collect the arguments --- */
391
392 for (fa = DA(&av), fal = fa + DA_LEN(&av); fa < fal; fa++) {
393 switch (fa->fmt) {
394#define CASE(code, ty) case fmt_##code: fa->u.code = va_arg(*ap, ty); break;
395 FMTTYPES(CASE)
396#undef CASE
397 default: abort();
398 }
399 }
400
401 /* --- Final pass through the format string to produce output --- */
002eaee3 402
eff136f6
MW
403 fa = DA(&av);
404 for (fs = DA(&sv), fsl = fs + DA_LEN(&sv); fs < fsl; fs++) {
405 f = fs->f;
406
407 /* --- Output the literal portion --- */
408
409 if (fs->n) DPUTM(d, fs->p, fs->n);
410
411 /* --- And now the variable portion --- */
412
413 if (fs->fmt == fmt_unset) {
414 switch (fs->ch) {
415 case 0: break;
416 case '%': DPUTC(d, '%'); break;
417 default: abort();
002eaee3 418 }
eff136f6 419 continue;
002eaee3 420 }
421
002eaee3 422 DRESET(&dd);
eff136f6
MW
423 DPUTC(&dd, '%');
424
425 /* --- Resolve the width and precision --- */
426
427 if (!(f & f_wd))
428 wd = 0;
429 else {
430 wd = (fs->f & f_wdarg) ? *(int *)&fa[fs->wd].u.i : fs->wd;
431 if (wd < 0) { wd = -wd; f |= f_minus; }
432 }
433
434 if (!(f & f_prec))
435 prec = 0;
436 else {
437 prec = (fs->f & f_precarg) ? *(int *)&fa[fs->prec].u.i : fs->prec;
438 if (prec < 0) { prec = 0; f &= ~f_prec; }
439 }
440
441 /* --- Write out the flags, width and precision --- */
442
443 if (f & f_plus) DPUTC(&dd, '+');
444 if (f & f_minus) DPUTC(&dd, '-');
445 if (f & f_sharp) DPUTC(&dd, '#');
446 if (f & f_zero) DPUTC(&dd, '0');
447
448 if (f & f_wd) {
449 DENSURE(&dd, PUTFSTEP);
450 dd.len += sprintf(dd.buf + dd.len, "%d", wd);
451 }
393cf1d9 452
eff136f6
MW
453 if (f & f_prec) {
454 DENSURE(&dd, PUTFSTEP + 1);
455 dd.len += sprintf(dd.buf + dd.len, ".%d", prec);
456 }
457
458 /* --- Write out the length gadget --- */
459
460 switch (f & f_len) {
5007fea9 461 case len_hh: DPUTC(&dd, 'h'); /* fall through */
eff136f6 462 case len_h: DPUTC(&dd, 'h'); break;
5007fea9 463 IF_LONGLONG( case len_ll: DPUTC(&dd, 'l'); /* fall through */ )
eff136f6 464 case len_l: DPUTC(&dd, 'l'); break;
5007fea9
MW
465 case len_z: DPUTC(&dd, 'z'); break;
466 case len_t: DPUTC(&dd, 't'); break;
eff136f6 467 case len_L: DPUTC(&dd, 'L'); break;
5007fea9 468 IF_INTMAX( case len_j: DPUTC(&dd, 'j'); break; )
eff136f6
MW
469 case len_std: break;
470 default: abort();
471 }
472
473 /* --- And finally the actually important bit --- */
474
475 DPUTC(&dd, fs->ch);
476 DPUTZ(&dd);
477
478 /* --- Make sure we have enough space for the output --- */
479
480 sz = PUTFSTEP;
481 if (sz < wd) sz = wd;
482 if (sz < prec + 16) sz = prec + 16;
483 switch (fs->ch) {
484 case 'a': case 'A':
485 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
486#ifdef HAVE_FLOAT_H
487 if (fs->ch == 'f') {
488 mx = ((fs->f & f_len) == len_L ?
489 LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16;
490 if (sz < mx) sz = mx;
491 }
492 break;
493#else
494 DPUTS(d, "<no float support>");
495 continue;
496#endif
497 case 's':
498 if (!(f & f_prec)) {
499 n = strlen(fa[fs->arg].u.s);
500 if (sz < n) sz = n;
501 }
502 break;
503 case 'n':
504 switch (fs->fmt) {
505#define CASE(code, ty) \
506 case fmt_##code: *fa[fs->arg].u.code = d->len - n; break;
507 PERCENT_N_FMTTYPES(CASE)
508#undef CASE
509 default: abort();
510 }
511 continue;
512 }
513
514 /* --- Finally do the output stage --- */
515
516 DENSURE(d, sz + 1);
517 switch (fs->fmt) {
518#ifdef HAVE_SNPRINTF
519# define CASE(code, ty) case fmt_##code: \
520 i = snprintf(d->buf + d->len, sz + 1, dd.buf, fa[fs->arg].u.code); \
521 break;
522#else
523# define CASE(code, ty) case fmt_##code: \
524 i = sprintf(d->buf + d->len, dd.buf, fa[fs->arg].u.code); \
525 break;
526#endif
527 OUTPUT_FMTTYPES(CASE)
528#undef CASE
529 default: abort();
530 }
531 assert(0 <= i && i <= sz); d->len += i;
002eaee3 532 }
533
eff136f6
MW
534 /* --- We're done --- */
535
002eaee3 536 DPUTZ(d);
537 DDESTROY(&dd);
44ea419e
MW
538 DA_DESTROY(&av);
539 DA_DESTROY(&sv);
002eaee3 540 return (d->len - n);
541}
542
543/* --- @dstr_putf@ --- *
544 *
545 * Arguments: @dstr *d@ = pointer to a dynamic string block
546 * @const char *p@ = pointer to @printf@-style format string
547 * @...@ = argument handle
548 *
549 * Returns: The number of characters written to the string.
550 *
551 * Use: Writes a piece of text to a dynamic string, doing @printf@-
552 * style substitutions as it goes. Intended to be robust if
553 * faced with malicious arguments, but not if the format string
554 * itself is malicious.
555 */
556
557int dstr_putf(dstr *d, const char *p, ...)
558{
559 int n;
560 va_list ap;
561 va_start(ap, p);
5a18a126 562 n = dstr_vputf(d, p, &ap);
002eaee3 563 va_end(ap);
564 return (n);
565}
566
567/*----- That's all, folks -------------------------------------------------*/