3 * Generalized string formatting
5 * (c) 2023 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 it under
13 * the terms of the GNU Library General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
17 * mLib is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 * 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 Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
28 /*----- Header files ------------------------------------------------------*/
54 /*----- Tunable constants -------------------------------------------------*/
56 /* For each format specifier, at least @STEP@ bytes are ensured before
57 * writing the formatted result.
60 #define STEP 64 /* Buffer size for @vgprintf@ */
62 /*----- Preliminary definitions -------------------------------------------*/
65 # define IF_FLOAT(x) x
70 #if defined(LLONG_MAX) || defined(LONG_LONG_MAX)
71 # define IF_LONGLONG(x) x
73 # define IF_LONGLONG(x)
77 # define IF_INTMAX(x) x
82 #define OUTPUT_FMTTYPES(_) \
84 _(li, unsigned long) \
85 IF_LONGLONG( _(lli, unsigned long long) ) \
88 IF_INTMAX( _(ji, uintmax_t) ) \
94 #define PERCENT_N_FMTTYPES(_) \
101 IF_LONGLONG( _(lln, long long *) ) \
102 IF_INTMAX( _(jn, intmax_t *) )
104 #define FMTTYPES(_) \
106 PERCENT_N_FMTTYPES(_)
110 #define CODE(code, ty) fmt_##code,
119 #define MEMB(code, ty) ty code;
125 DA_DECL(fmtarg_v
, struct fmtarg
);
139 #define f_len 0x000fu
141 #define f_wdarg 0x0020u
142 #define f_prec 0x0040u
143 #define f_precarg 0x0080u
144 #define f_plus 0x0100u
145 #define f_minus 0x0200u
146 #define f_sharp 0x0400u
147 #define f_zero 0x0800u
148 #define f_posarg 0x1000u
159 DA_DECL(fmtspec_v
, struct fmtspec
);
161 /*----- Main code ---------------------------------------------------------*/
163 /* --- @vgprintf@ --- *
165 * Arguments: @const struct gprintf_ops *ops@ = output operations
166 * @void *out@ = context for output operations
167 * @const char *p@ = pointer to @printf@-style format string
168 * @va_list *ap@ = argument handle
170 * Returns: The number of characters written to the string.
172 * Use: As for @gprintf@, but takes a reified argument tail.
175 static void set_arg(fmtarg_v
*av
, size_t i
, int fmt
)
181 DA_ENSURE(av
, i
+ 1 - n
);
182 for (j
= n
; j
<= i
; j
++) DA(av
)[j
].fmt
= fmt_unset
;
183 DA_UNSAFE_EXTEND(av
, i
+ 1 - n
);
186 if (DA(av
)[i
].fmt
== fmt_unset
) DA(av
)[i
].fmt
= fmt
;
187 else assert(DA(av
)[i
].fmt
== fmt
);
190 int vgprintf(const struct gprintf_ops
*ops
, void *out
,
191 const char *p
, va_list *ap
)
195 fmtspec_v sv
= DA_INIT
;
196 fmtarg_v av
= DA_INIT
;
197 struct fmtarg
*fa
, *fal
;
198 struct fmtspec
*fs
, *fsl
;
200 int i
, anext
, tot
= 0;
203 /* --- Initial pass through the input, parsing format specifiers --- *
205 * We essentially compile the format string into a vector of @fmtspec@
206 * objects, each of which represents a chunk of literal text followed by a
207 * (possibly imaginary, in the case of the final one) formatting directive.
208 * Output then simply consists of interpreting these specifiers in order.
216 fs
= &DA(&sv
)[DA_LEN(&sv
)];
217 DA_UNSAFE_EXTEND(&sv
, 1);
219 /* --- Find the end of this literal portion --- */
222 while (*p
&& *p
!= '%') p
++;
225 /* --- Some simple cases --- *
227 * We might have reached the end of the string, or maybe a `%%' escape.
230 if (!*p
) { fs
->fmt
= fmt_unset
; fs
->ch
= 0; break; }
232 if (*p
== '%') { fs
->fmt
= fmt_unset
; fs
->ch
= '%'; p
++; continue; }
234 /* --- Pick up initial flags --- */
239 case '+': f
|= f_plus
; break;
240 case '-': f
|= f_minus
; break;
241 case '#': f
|= f_sharp
; break;
242 case '0': f
|= f_zero
; break;
243 default: goto done_flags
;
248 /* --- Pick up the field width --- */
252 while (ISDIGIT(*p
)) i
= 10*i
+ *p
++ - '0';
254 /* --- Snag: this might have been an argument position indicator --- */
256 if (i
&& *p
== '$' && (!f
|| f
== f_zero
)) {
263 /* --- Set the field width --- *
265 * If @i@ is nonzero here then we have a numeric field width. Otherwise
266 * it might be `*', maybe with an explicit argument number.
272 } else if (*p
== '*') {
278 while (ISDIGIT(*p
)) i
= 10*i
+ *p
++ - '0';
279 assert(*p
== '$'); p
++;
283 set_arg(&av
, i
, fmt_i
); fs
->wd
= i
;
286 /* --- Maybe we have a precision spec --- */
293 while (ISDIGIT(*p
)) i
= 10*i
+ *p
++ - '0';
295 } else if (*p
!= '*')
303 while (ISDIGIT(*p
)) i
= 10*i
+ *p
++ - '0';
304 assert(*p
== '$'); p
++;
308 set_arg(&av
, i
, fmt_i
); fs
->prec
= i
;
312 /* --- Maybe some length flags --- */
317 if (*p
== 'h') { f
|= len_hh
; p
++; } else f
|= len_h
;
321 IF_LONGLONG( if (*p
== 'l') { f
|= len_ll
; p
++; } else ) f
|= len_l
;
323 case 'L': f
|= len_L
; p
++; break;
324 case 'z': f
|= len_z
; p
++; break;
325 case 't': f
|= len_t
; p
++; break;
326 IF_INTMAX( case 'j': f
|= len_j
; p
++; break; )
329 /* --- The flags are now ready --- */
333 /* --- At the end, an actual directive --- */
340 case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
342 case len_l
: fs
->fmt
= fmt_li
; break;
343 case len_z
: fs
->fmt
= fmt_zi
; break;
344 case len_t
: fs
->fmt
= fmt_ti
; break;
345 IF_LONGLONG( case len_ll
: fs
->fmt
= fmt_lli
; break; )
346 IF_INTMAX( case len_j
: fs
->fmt
= fmt_ji
; break; )
347 default: fs
->fmt
= fmt_i
;
351 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
352 fs
->fmt
= (f
&f_len
) == len_L ? fmt_Lf
: fmt_f
;
365 case len_hh
: fs
->fmt
= fmt_hhn
; break;
366 case len_h
: fs
->fmt
= fmt_hn
; break;
367 case len_l
: fs
->fmt
= fmt_ln
; break;
368 case len_z
: fs
->fmt
= fmt_zn
; break;
369 case len_t
: fs
->fmt
= fmt_tn
; break;
370 IF_LONGLONG( case len_ll
: fs
->fmt
= fmt_lln
; break; )
371 IF_INTMAX( case len_j
: fs
->fmt
= fmt_jn
; break; )
372 default: fs
->fmt
= fmt_n
;
377 "FATAL vgprintf: unknown format specifier `%c'\n", p
[-1]);
381 /* --- Finally sort out the argument --- *
383 * If we don't have explicit argument positions then this comes after the
384 * width and precision; and we don't know the type code until we've
385 * parsed the specifier, so this seems the right place to handle it.
388 if (!(f
&f_posarg
)) fs
->arg
= anext
++;
389 set_arg(&av
, fs
->arg
, fs
->fmt
);
392 /* --- Quick pass over the argument vector to collect the arguments --- */
394 for (fa
= DA(&av
), fal
= fa
+ DA_LEN(&av
); fa
< fal
; fa
++) {
396 #define CASE(code, ty) case fmt_##code: fa->u.code = va_arg(*ap, ty); break;
403 /* --- Final pass through the format string to produce output --- */
406 for (fs
= DA(&sv
), fsl
= fs
+ DA_LEN(&sv
); fs
< fsl
; fs
++) {
409 /* --- Output the literal portion --- */
412 n
= ops
->putm(out
, fs
->p
, fs
->n
); if (n
< 0) return (-1);
416 /* --- And now the variable portion --- */
418 if (fs
->fmt
== fmt_unset
) {
423 n
= ops
->putch(out
, '%'); if (n
< 0) return (-1);
434 /* --- Resolve the width and precision --- */
439 wd
= (fs
->f
&f_wdarg
) ?
*(int *)&fa
[fs
->wd
].u
.i
: fs
->wd
;
440 if (wd
< 0) { wd
= -wd
; f
|= f_minus
; }
446 prec
= (fs
->f
&f_precarg
) ?
*(int *)&fa
[fs
->prec
].u
.i
: fs
->prec
;
447 if (prec
< 0) { prec
= 0; f
&= ~f_prec
; }
450 /* --- Write out the flags, width and precision --- */
452 if (f
&f_plus
) DPUTC(&dd
, '+');
453 if (f
&f_minus
) DPUTC(&dd
, '-');
454 if (f
&f_sharp
) DPUTC(&dd
, '#');
455 if (f
&f_zero
) DPUTC(&dd
, '0');
459 dd
.len
+= sprintf(dd
.buf
+ dd
.len
, "%d", wd
);
463 DENSURE(&dd
, STEP
+ 1);
464 dd
.len
+= sprintf(dd
.buf
+ dd
.len
, ".%d", prec
);
467 /* --- Write out the length gadget --- */
470 case len_hh
: DPUTC(&dd
, 'h'); /* fall through */
471 case len_h
: DPUTC(&dd
, 'h'); break;
472 IF_LONGLONG( case len_ll
: DPUTC(&dd
, 'l'); /* fall through */ )
473 case len_l
: DPUTC(&dd
, 'l'); break;
474 case len_z
: DPUTC(&dd
, 'z'); break;
475 case len_t
: DPUTC(&dd
, 't'); break;
476 case len_L
: DPUTC(&dd
, 'L'); break;
477 IF_INTMAX( case len_j
: DPUTC(&dd
, 'j'); break; )
482 /* --- And finally the actually important bit --- */
487 /* --- Make sure we have enough space for the output --- */
490 if (sz
< wd
) sz
= wd
;
491 if (sz
< prec
+ 16) sz
= prec
+ 16;
494 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
497 mx
= ((fs
->f
&f_len
) == len_L ?
498 LDBL_MAX_10_EXP
: DBL_MAX_10_EXP
) + 16;
499 if (sz
< mx
) sz
= mx
;
503 # define MSG "<no float support>"
504 n
= ops
->putm(out
, MSG
, sizeof(MSG
) - 1); if (n
< 0) return (-1);
510 n
= strlen(fa
[fs
->arg
].u
.s
);
516 #define CASE(code, ty) \
517 case fmt_##code: *fa[fs->arg].u.code = tot; break;
518 PERCENT_N_FMTTYPES(CASE
)
525 /* --- Finally do the output stage --- */
528 #define CASE(code, ty) \
530 n = ops->nputf(out, sz, dd.buf, fa[fs->arg].u.code); \
532 OUTPUT_FMTTYPES(CASE
)
536 if (n
< 0) return (-1);
540 /* --- We're done --- */
548 /* --- @gprintf@ --- *
550 * Arguments: @const struct gprintf_ops *ops@ = output operations
551 * @void *out@ = context for output operations
552 * @const char *p@ = pointer to @printf@-style format string
553 * @...@ = argument handle
555 * Returns: The number of characters written to the string.
557 * Use: Formats a @printf@-like message and writes the result using
558 * the given output operations. This is the backend machinery
559 * for @dstr_putf@, for example.
562 int gprintf(const struct gprintf_ops
*ops
, void *out
, const char *p
, ...)
567 va_start(ap
, p
); n
= vgprintf(ops
, out
, p
, &ap
); va_end(ap
);
571 /*----- Utilities ---------------------------------------------------------*/
573 /* --- @gprintf_memputf@ --- *
575 * Arguments: @arena *a@ = memory allocation arena
576 * @char **buf_inout@ = address of output buffer pointer
577 * @size_t *sz_inout@ = address of buffer size
578 * @size_t maxsz@ = buffer size needed for this operation
579 * @const char *p@ = pointer to format string
580 * @va_list *ap@ = captured format-arguments tail
582 * Returns: The formatted length.
584 * Use: Generic utility for mostly implementing the @nputf@ output
585 * function, if you don't have a better option.
587 * On entry, @*buf_inout@ should be null or a buffer pointer,
588 * with @*sz_inout@ either zero or the buffer's size,
589 * respectively. On exit, @*buf_input@ and @*sz_inout@ will be
590 * updated, if necessary, to describe a sufficiently large
591 * buffer, and the formatted string will have been written to
594 * When the buffer is no longer required, free it using
598 size_t gprintf_memputf(arena
*a
, char **buf_inout
, size_t *sz_inout
,
599 size_t maxsz
, const char *p
, va_list ap
)
603 GROWBUF_REPLACE(size_t, a
, *buf_inout
, *sz_inout
, maxsz
, 64, 1);
605 n
= vsnprintf(*buf_inout
, maxsz
+ 1, p
, ap
);
607 n
= vsprintf(*buf_inout
, p
, ap
);
609 assert(0 <= n
&& n
<= maxsz
);
613 /*----- Standard printers -------------------------------------------------*/
615 static int file_putch(void *out
, int ch
)
619 if (putc(ch
, fp
) == EOF
) return (-1);
623 static int file_putm(void *out
, const char *p
, size_t sz
)
627 if (fwrite(p
, 1, sz
, fp
) < sz
) return (-1);
631 static int file_nputf(void *out
, size_t maxsz
, const char *p
, ...)
638 n
= vfprintf(fp
, p
, ap
);
639 va_end(ap
); if (n
< 0) return (-1);
643 const struct gprintf_ops file_printops
=
644 { file_putch
, file_putm
, file_nputf
};
646 /*----- That's all, folks -------------------------------------------------*/