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 ------------------------------------------------------*/
51 /*----- Tunable constants -------------------------------------------------*/
54 * For each format specifier, at least @PUTFSTEP@ bytes are ensured before
55 * writing the formatted result.
58 #define PUTFSTEP 64 /* Buffer size for @putf@ */
60 /*----- Preliminary definitions -------------------------------------------*/
63 # define IF_FLOAT(x) x
68 #if defined(LLONG_MAX) || defined(LONG_LONG_MAX)
69 # define IF_LONGLONG(x) x
71 # define IF_LONGLONG(x)
75 # define IF_INTMAX(x) x
80 #define OUTPUT_FMTTYPES(_) \
82 _(li, unsigned long) \
83 IF_LONGLONG( _(lli, unsigned long long) ) \
86 IF_INTMAX( _(ji, uintmax_t) ) \
92 #define PERCENT_N_FMTTYPES(_) \
99 IF_LONGLONG( _(lln, long long *) ) \
100 IF_INTMAX( _(jn, intmax_t *) )
102 #define FMTTYPES(_) \
104 PERCENT_N_FMTTYPES(_)
108 #define CODE(code, ty) fmt_##code,
117 #define MEMB(code, ty) ty code;
123 DA_DECL(fmtarg_v
, fmtarg
);
137 #define f_len 0x000fu
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
157 DA_DECL(fmtspec_v
, fmtspec
);
159 /*----- Main code ---------------------------------------------------------*/
161 /* --- @dstr_vputf@ --- *
163 * Arguments: @dstr *d@ = pointer to a dynamic string block
164 * @const char *p@ = pointer to @printf@-style format string
165 * @va_list *ap@ = argument handle
167 * Returns: The number of characters written to the string.
169 * Use: As for @dstr_putf@, but may be used as a back-end to user-
170 * supplied functions with @printf@-style interfaces.
173 static void set_arg(fmtarg_v
*av
, size_t i
, int fmt
)
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
);
184 if (DA(av
)[i
].fmt
== fmt_unset
) DA(av
)[i
].fmt
= fmt
;
185 else assert(DA(av
)[i
].fmt
== fmt
);
188 int dstr_vputf(dstr
*d
, const char *p
, va_list *ap
)
193 fmtspec_v sv
= DA_INIT
;
194 fmtarg_v av
= DA_INIT
;
201 /* --- Initial pass through the input, parsing format specifiers --- *
203 * We essentially compile the format string into a vector of @fmtspec@
204 * objects, each of which represents a chunk of literal text followed by a
205 * (possibly imaginary, in the case of the final one) formatting directive.
206 * Output then simply consists of interpreting these specifiers in order.
214 fs
= &DA(&sv
)[DA_LEN(&sv
)];
215 DA_UNSAFE_EXTEND(&sv
, 1);
217 /* --- Find the end of this literal portion --- */
220 while (*p
&& *p
!= '%') p
++;
223 /* --- Some simple cases --- *
225 * We might have reached the end of the string, or maybe a `%%' escape.
228 if (!*p
) { fs
->fmt
= fmt_unset
; fs
->ch
= 0; break; }
230 if (*p
== '%') { fs
->fmt
= fmt_unset
; fs
->ch
= '%'; p
++; continue; }
232 /* --- Pick up initial flags --- */
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
;
246 /* --- Pick up the field width --- */
250 while (isdigit((unsigned char)*p
)) i
= 10*i
+ *p
++ - '0';
252 /* --- Snag: this might have been an argument position indicator --- */
254 if (i
&& *p
== '$' && (!f
|| f
== f_zero
)) {
261 /* --- Set the field width --- *
263 * If @i@ is nonzero here then we have a numeric field width. Otherwise
264 * it might be `*', maybe with an explicit argument number.
270 } else if (*p
== '*') {
272 if (!isdigit((unsigned char)*p
))
276 while (isdigit((unsigned char)*p
)) i
= 10*i
+ *p
++ - '0';
277 assert(*p
== '$'); p
++;
281 set_arg(&av
, i
, fmt_i
); fs
->wd
= i
;
284 /* --- Maybe we have a precision spec --- */
289 if (isdigit((unsigned char)*p
)) {
291 while (isdigit((unsigned char)*p
)) i
= 10*i
+ *p
++ - '0';
293 } else if (*p
!= '*')
297 if (!isdigit((unsigned char)*p
))
301 while (isdigit((unsigned char)*p
)) i
= 10*i
+ *p
++ - '0';
302 assert(*p
== '$'); p
++;
306 set_arg(&av
, i
, fmt_i
); fs
->prec
= i
;
310 /* --- Maybe some length flags --- */
315 if (*p
== 'h') { f
|= len_hh
; p
++; } else f
|= len_h
;
319 IF_LONGLONG( if (*p
== 'l') { f
|= len_ll
; p
++; } else ) f
|= len_l
;
321 case 'L': f
|= len_L
; p
++; break;
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; )
327 /* --- The flags are now ready --- */
331 /* --- At the end, an actual directive --- */
338 case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
340 case len_l
: fs
->fmt
= fmt_li
; break;
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; )
345 default: fs
->fmt
= fmt_i
;
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
;
363 case len_hh
: fs
->fmt
= fmt_hhn
; break;
364 case len_h
: fs
->fmt
= fmt_hn
; break;
365 case len_l
: fs
->fmt
= fmt_ln
; break;
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; )
370 default: fs
->fmt
= fmt_n
;
375 "FATAL dstr_vputf: unknown format specifier `%c'\n", p
[-1]);
379 /* --- Finally sort out the argument --- *
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.
386 if (!(f
& f_posarg
)) fs
->arg
= anext
++;
387 set_arg(&av
, fs
->arg
, fs
->fmt
);
390 /* --- Quick pass over the argument vector to collect the arguments --- */
392 for (fa
= DA(&av
), fal
= fa
+ DA_LEN(&av
); fa
< fal
; fa
++) {
394 #define CASE(code, ty) case fmt_##code: fa->u.code = va_arg(*ap, ty); break;
401 /* --- Final pass through the format string to produce output --- */
404 for (fs
= DA(&sv
), fsl
= fs
+ DA_LEN(&sv
); fs
< fsl
; fs
++) {
407 /* --- Output the literal portion --- */
409 if (fs
->n
) DPUTM(d
, fs
->p
, fs
->n
);
411 /* --- And now the variable portion --- */
413 if (fs
->fmt
== fmt_unset
) {
416 case '%': DPUTC(d
, '%'); break;
425 /* --- Resolve the width and precision --- */
430 wd
= (fs
->f
& f_wdarg
) ?
*(int *)&fa
[fs
->wd
].u
.i
: fs
->wd
;
431 if (wd
< 0) { wd
= -wd
; f
|= f_minus
; }
437 prec
= (fs
->f
& f_precarg
) ?
*(int *)&fa
[fs
->prec
].u
.i
: fs
->prec
;
438 if (prec
< 0) { prec
= 0; f
&= ~f_prec
; }
441 /* --- Write out the flags, width and precision --- */
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');
449 DENSURE(&dd
, PUTFSTEP
);
450 dd
.len
+= sprintf(dd
.buf
+ dd
.len
, "%d", wd
);
454 DENSURE(&dd
, PUTFSTEP
+ 1);
455 dd
.len
+= sprintf(dd
.buf
+ dd
.len
, ".%d", prec
);
458 /* --- Write out the length gadget --- */
461 case len_hh
: DPUTC(&dd
, 'h'); /* fall through */
462 case len_h
: DPUTC(&dd
, 'h'); break;
463 IF_LONGLONG( case len_ll
: DPUTC(&dd
, 'l'); /* fall through */ )
464 case len_l
: DPUTC(&dd
, 'l'); break;
465 case len_z
: DPUTC(&dd
, 'z'); break;
466 case len_t
: DPUTC(&dd
, 't'); break;
467 case len_L
: DPUTC(&dd
, 'L'); break;
468 IF_INTMAX( case len_j
: DPUTC(&dd
, 'j'); break; )
473 /* --- And finally the actually important bit --- */
478 /* --- Make sure we have enough space for the output --- */
481 if (sz
< wd
) sz
= wd
;
482 if (sz
< prec
+ 16) sz
= prec
+ 16;
485 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
488 mx
= ((fs
->f
& f_len
) == len_L ?
489 LDBL_MAX_10_EXP
: DBL_MAX_10_EXP
) + 16;
490 if (sz
< mx
) sz
= mx
;
494 DPUTS(d
, "<no float support>");
499 n
= strlen(fa
[fs
->arg
].u
.s
);
505 #define CASE(code, ty) \
506 case fmt_##code: *fa[fs->arg].u.code = d->len - n; break;
507 PERCENT_N_FMTTYPES(CASE
)
514 /* --- Finally do the output stage --- */
519 # define CASE(code, ty) case fmt_##code: \
520 i = snprintf(d->buf + d->len, sz + 1, dd.buf, fa[fs->arg].u.code); \
523 # define CASE(code, ty) case fmt_##code: \
524 i = sprintf(d->buf + d->len, dd.buf, fa[fs->arg].u.code); \
527 OUTPUT_FMTTYPES(CASE
)
531 assert(0 <= i
&& i
<= sz
); d
->len
+= i
;
534 /* --- We're done --- */
543 /* --- @dstr_putf@ --- *
545 * Arguments: @dstr *d@ = pointer to a dynamic string block
546 * @const char *p@ = pointer to @printf@-style format string
547 * @...@ = argument handle
549 * Returns: The number of characters written to the string.
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.
557 int dstr_putf(dstr
*d
, const char *p
, ...)
562 n
= dstr_vputf(d
, p
, &ap
);
567 /*----- That's all, folks -------------------------------------------------*/