3 * `printf'-style formatting for dynamic strings
5 * (c) 1999 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
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.
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.
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,
28 /*----- Header files ------------------------------------------------------*/
52 /*----- Tunable constants -------------------------------------------------*/
55 * For each format specifier, at least @PUTFSTEP@ bytes are ensured before
56 * writing the formatted result.
59 #define PUTFSTEP 64 /* Buffer size for @putf@ */
61 /*----- Preliminary definitions -------------------------------------------*/
64 # define IF_FLOAT(x) x
69 #if defined(LLONG_MAX) || defined(LONG_LONG_MAX)
70 # define IF_LONGLONG(x) x
72 # define IF_LONGLONG(x)
76 # define IF_INTMAX(x) x
81 #define OUTPUT_FMTTYPES(_) \
83 _(li, unsigned long) \
84 IF_LONGLONG( _(lli, unsigned long long) ) \
87 IF_INTMAX( _(ji, uintmax_t) ) \
93 #define PERCENT_N_FMTTYPES(_) \
100 IF_LONGLONG( _(lln, long long *) ) \
101 IF_INTMAX( _(jn, intmax_t *) )
103 #define FMTTYPES(_) \
105 PERCENT_N_FMTTYPES(_)
109 #define CODE(code, ty) fmt_##code,
118 #define MEMB(code, ty) ty code;
124 DA_DECL(fmtarg_v, fmtarg);
138 #define f_len 0x000fu
140 #define f_wdarg 0x0020u
141 #define f_prec 0x0040u
142 #define f_precarg 0x0080u
143 #define f_plus 0x0100u
144 #define f_minus 0x0200u
145 #define f_sharp 0x0400u
146 #define f_zero 0x0800u
147 #define f_posarg 0x1000u
158 DA_DECL(fmtspec_v, fmtspec);
160 /*----- Main code ---------------------------------------------------------*/
162 /* --- @dstr_vputf@ --- *
164 * Arguments: @dstr *d@ = pointer to a dynamic string block
165 * @const char *p@ = pointer to @printf@-style format string
166 * @va_list *ap@ = argument handle
168 * Returns: The number of characters written to the string.
170 * Use: As for @dstr_putf@, but may be used as a back-end to user-
171 * supplied functions with @printf@-style interfaces.
174 static void set_arg(fmtarg_v *av, size_t i, int fmt)
180 DA_ENSURE(av, i + 1 - n);
181 for (j = n; j <= i; j++) DA(av)[j].fmt = fmt_unset;
182 DA_UNSAFE_EXTEND(av, i + 1 - n);
185 if (DA(av)[i].fmt == fmt_unset) DA(av)[i].fmt = fmt;
186 else assert(DA(av)[i].fmt == fmt);
189 int dstr_vputf(dstr *d, const char *p, va_list *ap)
194 fmtspec_v sv = DA_INIT;
195 fmtarg_v av = DA_INIT;
202 /* --- Initial pass through the input, parsing format specifiers --- *
204 * We essentially compile the format string into a vector of @fmtspec@
205 * objects, each of which represnts a chunk of literal text followed by a
206 * (possibly imaginary, in the case of the final one) formatting directive.
207 * Output then simply consists of interpreting these specifiers in order.
215 fs = &DA(&sv)[DA_LEN(&sv)];
216 DA_UNSAFE_EXTEND(&sv, 1);
218 /* --- Find the end of this literal portion --- */
221 while (*p && *p != '%') p++;
224 /* --- Some simple cases --- *
226 * We might have reached the end of the string, or maybe a `%%' escape.
229 if (!*p) { fs->fmt = fmt_unset; fs->ch = 0; break; }
231 if (*p == '%') { fs->fmt = fmt_unset; fs->ch = '%'; p++; continue; }
233 /* --- Pick up initial flags --- */
238 case '+': f |= f_plus; break;
239 case '-': f |= f_minus; break;
240 case '#': f |= f_sharp; break;
241 case '0': f |= f_zero; break;
242 default: goto done_flags;
247 /* --- Pick up the field width --- */
251 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
253 /* --- Snag: this might have been an argument position indicator --- */
255 if (i && *p == '$' && (!f || f == f_zero)) {
262 /* --- Set the field width --- *
264 * If @i@ is nonzero here then we have a numeric field width. Otherwise
265 * it might be `*', maybe with an explicit argument number.
271 } else if (*p == '*') {
273 if (!isdigit((unsigned char)*p))
277 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
278 assert(*p == '$'); p++;
282 set_arg(&av, i, fmt_i); fs->wd = i;
285 /* --- Maybe we have a precision spec --- */
290 if (isdigit((unsigned char)*p)) {
292 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
294 } else if (*p != '*')
298 if (!isdigit((unsigned char)*p))
302 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
303 assert(*p == '$'); p++;
307 set_arg(&av, i, fmt_i); fs->prec = i;
311 /* --- Maybe some length flags --- */
316 if (*p == 'h') { f |= len_hh; p++; } else f |= len_h;
320 IF_LONGLONG( if (*p == 'l') { f |= len_ll; p++; } else ) f |= len_l;
322 case 'L': f |= len_L; p++; break;
323 case 'z': f |= len_z; p++; break;
324 case 't': f |= len_t; p++; break;
325 IF_INTMAX( case 'j': f |= len_j; p++; break; )
328 /* --- The flags are now ready --- */
332 /* --- At the end, an actual directive --- */
339 case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
341 case len_l: fs->fmt = fmt_li; break;
342 case len_z: fs->fmt = fmt_zi; break;
343 case len_t: fs->fmt = fmt_ti; break;
344 IF_LONGLONG( case len_ll: fs->fmt = fmt_lli; break; )
345 IF_INTMAX( case len_j: fs->fmt = fmt_ji; break; )
346 default: fs->fmt = fmt_i;
350 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
351 fs->fmt = (f & f_len) == len_L ? fmt_Lf : fmt_f;
364 case len_hh: fs->fmt = fmt_hhn; break;
365 case len_h: fs->fmt = fmt_hn; break;
366 case len_l: fs->fmt = fmt_ln; break;
367 case len_z: fs->fmt = fmt_zn; break;
368 case len_t: fs->fmt = fmt_tn; break;
369 IF_LONGLONG( case len_ll: fs->fmt = fmt_lln; break; )
370 IF_INTMAX( case len_j: fs->fmt = fmt_jn; break; )
371 default: fs->fmt = fmt_n;
376 "FATAL dstr_vputf: unknown format specifier `%c'\n", p[-1]);
380 /* --- Finally sort out the argument --- *
382 * If we don't have explicit argument positions then this comes after the
383 * width and precision; and we don't know the type code until we've
384 * parsed the specifier, so this seems the right place to handle it.
387 if (!(f & f_posarg)) fs->arg = anext++;
388 set_arg(&av, fs->arg, fs->fmt);
391 /* --- Quick pass over the argument vector to collect the arguments --- */
393 for (fa = DA(&av), fal = fa + DA_LEN(&av); fa < fal; fa++) {
395 #define CASE(code, ty) case fmt_##code: fa->u.code = va_arg(*ap, ty); break;
402 /* --- Final pass through the format string to produce output --- */
405 for (fs = DA(&sv), fsl = fs + DA_LEN(&sv); fs < fsl; fs++) {
408 /* --- Output the literal portion --- */
410 if (fs->n) DPUTM(d, fs->p, fs->n);
412 /* --- And now the variable portion --- */
414 if (fs->fmt == fmt_unset) {
417 case '%': DPUTC(d, '%'); break;
426 /* --- Resolve the width and precision --- */
431 wd = (fs->f & f_wdarg) ? *(int *)&fa[fs->wd].u.i : fs->wd;
432 if (wd < 0) { wd = -wd; f |= f_minus; }
438 prec = (fs->f & f_precarg) ? *(int *)&fa[fs->prec].u.i : fs->prec;
439 if (prec < 0) { prec = 0; f &= ~f_prec; }
442 /* --- Write out the flags, width and precision --- */
444 if (f & f_plus) DPUTC(&dd, '+');
445 if (f & f_minus) DPUTC(&dd, '-');
446 if (f & f_sharp) DPUTC(&dd, '#');
447 if (f & f_zero) DPUTC(&dd, '0');
450 DENSURE(&dd, PUTFSTEP);
451 dd.len += sprintf(dd.buf + dd.len, "%d", wd);
455 DENSURE(&dd, PUTFSTEP + 1);
456 dd.len += sprintf(dd.buf + dd.len, ".%d", prec);
459 /* --- Write out the length gadget --- */
462 case len_hh: DPUTC(&dd, 'h'); /* fall through */
463 case len_h: DPUTC(&dd, 'h'); break;
464 IF_LONGLONG( case len_ll: DPUTC(&dd, 'l'); /* fall through */ )
465 case len_l: DPUTC(&dd, 'l'); break;
466 case len_z: DPUTC(&dd, 'z'); break;
467 case len_t: DPUTC(&dd, 't'); break;
468 case len_L: DPUTC(&dd, 'L'); break;
469 IF_INTMAX( case len_j: DPUTC(&dd, 'j'); break; )
474 /* --- And finally the actually important bit --- */
479 /* --- Make sure we have enough space for the output --- */
482 if (sz < wd) sz = wd;
483 if (sz < prec + 16) sz = prec + 16;
486 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
489 mx = ((fs->f & f_len) == len_L ?
490 LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16;
491 if (sz < mx) sz = mx;
495 DPUTS(d, "<no float support>");
500 n = strlen(fa[fs->arg].u.s);
506 #define CASE(code, ty) \
507 case fmt_##code: *fa[fs->arg].u.code = d->len - n; break;
508 PERCENT_N_FMTTYPES(CASE)
515 /* --- Finally do the output stage --- */
520 # define CASE(code, ty) case fmt_##code: \
521 i = snprintf(d->buf + d->len, sz + 1, dd.buf, fa[fs->arg].u.code); \
524 # define CASE(code, ty) case fmt_##code: \
525 i = sprintf(d->buf + d->len, dd.buf, fa[fs->arg].u.code); \
528 OUTPUT_FMTTYPES(CASE)
532 assert(0 <= i && i <= sz); d->len += i;
535 /* --- We're done --- */
544 /* --- @dstr_putf@ --- *
546 * Arguments: @dstr *d@ = pointer to a dynamic string block
547 * @const char *p@ = pointer to @printf@-style format string
548 * @...@ = argument handle
550 * Returns: The number of characters written to the string.
552 * Use: Writes a piece of text to a dynamic string, doing @printf@-
553 * style substitutions as it goes. Intended to be robust if
554 * faced with malicious arguments, but not if the format string
555 * itself is malicious.
558 int dstr_putf(dstr *d, const char *p, ...)
563 n = dstr_vputf(d, p, &ap);
568 /*----- That's all, folks -------------------------------------------------*/