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 ------------------------------------------------------*/
47 /*----- Tunable constants -------------------------------------------------*/
50 * For each format specifier, at least @PUTFSTEP@ bytes are ensured before
51 * writing the formatted result.
54 #define PUTFSTEP 64 /* Buffer size for @putf@ */
56 /*----- Preliminary definitions -------------------------------------------*/
58 #define OUTPUT_FMTTYPES(_) \
60 _(li, unsigned long) \
66 #define PERCENT_N_FMTTYPES(_) \
77 #define CODE(code, ty) fmt_##code,
86 #define MEMB(code, ty) ty code;
92 DA_DECL(fmtarg_v, fmtarg);
102 #define f_len 0x000fu
104 #define f_wdarg 0x0020u
105 #define f_prec 0x0040u
106 #define f_precarg 0x0080u
107 #define f_plus 0x0100u
108 #define f_minus 0x0200u
109 #define f_sharp 0x0400u
110 #define f_zero 0x0800u
111 #define f_posarg 0x1000u
122 DA_DECL(fmtspec_v, fmtspec);
124 /*----- Main code ---------------------------------------------------------*/
126 /* --- @dstr_vputf@ --- *
128 * Arguments: @dstr *d@ = pointer to a dynamic string block
129 * @const char *p@ = pointer to @printf@-style format string
130 * @va_list *ap@ = argument handle
132 * Returns: The number of characters written to the string.
134 * Use: As for @dstr_putf@, but may be used as a back-end to user-
135 * supplied functions with @printf@-style interfaces.
138 static void set_arg(fmtarg_v *av, size_t i, int fmt)
144 DA_ENSURE(av, i + 1 - n);
145 for (j = n; j <= i; j++) DA(av)[j].fmt = fmt_unset;
146 DA_UNSAFE_EXTEND(av, i + 1 - n);
149 if (DA(av)[i].fmt == fmt_unset) DA(av)[i].fmt = fmt;
150 else assert(DA(av)[i].fmt == fmt);
153 int dstr_vputf(dstr *d, const char *p, va_list *ap)
158 fmtspec_v sv = DA_INIT;
159 fmtarg_v av = DA_INIT;
166 /* --- Initial pass through the input, parsing format specifiers --- *
168 * We essentially compile the format string into a vector of @fmtspec@
169 * objects, each of which represnts a chunk of literal text followed by a
170 * (possibly imaginary, in the case of the final one) formatting directive.
171 * Output then simply consists of interpreting these specifiers in order.
179 fs = &DA(&sv)[DA_LEN(&sv)];
180 DA_UNSAFE_EXTEND(&sv, 1);
182 /* --- Find the end of this literal portion --- */
185 while (*p && *p != '%') p++;
188 /* --- Some simple cases --- *
190 * We might have reached the end of the string, or maybe a `%%' escape.
193 if (!*p) { fs->fmt = fmt_unset; fs->ch = 0; break; }
195 if (*p == '%') { fs->fmt = fmt_unset; fs->ch = '%'; p++; continue; }
197 /* --- Pick up initial flags --- */
202 case '+': f |= f_plus; break;
203 case '-': f |= f_minus; break;
204 case '#': f |= f_sharp; break;
205 case '0': f |= f_zero; break;
206 default: goto done_flags;
211 /* --- Pick up the field width --- */
215 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
217 /* --- Snag: this might have been an argument position indicator --- */
219 if (i && *p == '$' && (!f || f == f_zero)) {
226 /* --- Set the field width --- *
228 * If @i@ is nonzero here then we have a numeric field width. Otherwise
229 * it might be `*', maybe with an explicit argument number.
235 } else if (*p == '*') {
237 if (!isdigit((unsigned char)*p))
241 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
242 assert(*p == '$'); p++;
246 set_arg(&av, i, fmt_i); fs->wd = i;
249 /* --- Maybe we have a precision spec --- */
254 if (isdigit((unsigned char)*p)) {
256 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
258 } else if (*p != '*')
262 if (!isdigit((unsigned char)*p))
266 while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
267 assert(*p == '$'); p++;
271 set_arg(&av, i, fmt_i); fs->prec = i;
275 /* --- Maybe some length flags --- */
278 case 'h': f |= len_h; p++; break;
279 case 'l': f |= len_l; p++; break;
280 case 'L': f |= len_L; p++; break;
283 /* --- The flags are now ready --- */
287 /* --- At the end, an actual directive --- */
294 case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
296 case len_l: fs->fmt = fmt_li; break;
297 default: fs->fmt = fmt_i;
300 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
301 fs->fmt = (f & f_len) == len_L ? fmt_Lf : fmt_f;
314 case len_h: fs->fmt = fmt_hn; break;
315 case len_l: fs->fmt = fmt_ln; break;
316 default: fs->fmt = fmt_n;
321 "FATAL dstr_vputf: unknown format specifier `%c'\n", p[-1]);
325 /* --- Finally sort out the argument --- *
327 * If we don't have explicit argument positions then this comes after the
328 * width and precision; and we don't know the type code until we've
329 * parsed the specifier, so this seems the right place to handle it.
332 if (!(f & f_posarg)) fs->arg = anext++;
333 set_arg(&av, fs->arg, fs->fmt);
336 /* --- Quick pass over the argument vector to collect the arguments --- */
338 for (fa = DA(&av), fal = fa + DA_LEN(&av); fa < fal; fa++) {
340 #define CASE(code, ty) case fmt_##code: fa->u.code = va_arg(*ap, ty); break;
347 /* --- Final pass through the format string to produce output --- */
350 for (fs = DA(&sv), fsl = fs + DA_LEN(&sv); fs < fsl; fs++) {
353 /* --- Output the literal portion --- */
355 if (fs->n) DPUTM(d, fs->p, fs->n);
357 /* --- And now the variable portion --- */
359 if (fs->fmt == fmt_unset) {
362 case '%': DPUTC(d, '%'); break;
371 /* --- Resolve the width and precision --- */
376 wd = (fs->f & f_wdarg) ? *(int *)&fa[fs->wd].u.i : fs->wd;
377 if (wd < 0) { wd = -wd; f |= f_minus; }
383 prec = (fs->f & f_precarg) ? *(int *)&fa[fs->prec].u.i : fs->prec;
384 if (prec < 0) { prec = 0; f &= ~f_prec; }
387 /* --- Write out the flags, width and precision --- */
389 if (f & f_plus) DPUTC(&dd, '+');
390 if (f & f_minus) DPUTC(&dd, '-');
391 if (f & f_sharp) DPUTC(&dd, '#');
392 if (f & f_zero) DPUTC(&dd, '0');
395 DENSURE(&dd, PUTFSTEP);
396 dd.len += sprintf(dd.buf + dd.len, "%d", wd);
400 DENSURE(&dd, PUTFSTEP + 1);
401 dd.len += sprintf(dd.buf + dd.len, ".%d", prec);
404 /* --- Write out the length gadget --- */
407 case len_h: DPUTC(&dd, 'h'); break;
408 case len_l: DPUTC(&dd, 'l'); break;
409 case len_L: DPUTC(&dd, 'L'); break;
414 /* --- And finally the actually important bit --- */
419 /* --- Make sure we have enough space for the output --- */
422 if (sz < wd) sz = wd;
423 if (sz < prec + 16) sz = prec + 16;
426 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
429 mx = ((fs->f & f_len) == len_L ?
430 LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16;
431 if (sz < mx) sz = mx;
435 DPUTS(d, "<no float support>");
440 n = strlen(fa[fs->arg].u.s);
446 #define CASE(code, ty) \
447 case fmt_##code: *fa[fs->arg].u.code = d->len - n; break;
448 PERCENT_N_FMTTYPES(CASE)
455 /* --- Finally do the output stage --- */
460 # define CASE(code, ty) case fmt_##code: \
461 i = snprintf(d->buf + d->len, sz + 1, dd.buf, fa[fs->arg].u.code); \
464 # define CASE(code, ty) case fmt_##code: \
465 i = sprintf(d->buf + d->len, dd.buf, fa[fs->arg].u.code); \
468 OUTPUT_FMTTYPES(CASE)
472 assert(0 <= i && i <= sz); d->len += i;
475 /* --- We're done --- */
482 /* --- @dstr_putf@ --- *
484 * Arguments: @dstr *d@ = pointer to a dynamic string block
485 * @const char *p@ = pointer to @printf@-style format string
486 * @...@ = argument handle
488 * Returns: The number of characters written to the string.
490 * Use: Writes a piece of text to a dynamic string, doing @printf@-
491 * style substitutions as it goes. Intended to be robust if
492 * faced with malicious arguments, but not if the format string
493 * itself is malicious.
496 int dstr_putf(dstr *d, const char *p, ...)
501 n = dstr_vputf(d, p, &ap);
506 /*----- That's all, folks -------------------------------------------------*/