chiark / gitweb /
Release 2.4.2.
[mLib] / struct / dstr-putf.c
1 /* -*-c-*-
2  *
3  * `printf'-style formatting for dynamic strings
4  *
5  * (c) 1999 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
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.
16  *
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.
21  *
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
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "config.h"
31
32 #include <assert.h>
33 #include <ctype.h>
34 #include <limits.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 #ifdef HAVE_FLOAT_H
41 #  include <float.h>
42 #endif
43
44 #ifdef HAVE_STDINT_H
45 #  include <stdint.h>
46 #endif
47
48 #include "darray.h"
49 #include "dstr.h"
50
51 /*----- Tunable constants -------------------------------------------------*/
52
53 /*
54  * For each format specifier, at least @PUTFSTEP@ bytes are ensured before
55  * writing the formatted result.
56  */
57
58 #define PUTFSTEP 64                     /* Buffer size for @putf@ */
59
60 /*----- Preliminary definitions -------------------------------------------*/
61
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
80 #define OUTPUT_FMTTYPES(_)                                              \
81   _(i, unsigned int)                                                    \
82   _(li, unsigned long)                                                  \
83   IF_LONGLONG( _(lli, unsigned long long) )                             \
84   _(zi, size_t)                                                         \
85   _(ti, ptrdiff_t)                                                      \
86   IF_INTMAX( _(ji, uintmax_t) )                                         \
87   _(s, char *)                                                          \
88   _(p, void *)                                                          \
89   _(f, double)                                                          \
90   _(Lf, long double)
91
92 #define PERCENT_N_FMTTYPES(_)                                           \
93   _(n, int *)                                                           \
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 *) )
101
102 #define FMTTYPES(_)                                                     \
103   OUTPUT_FMTTYPES(_)                                                    \
104   PERCENT_N_FMTTYPES(_)
105
106 enum {
107   fmt_unset = 0,
108 #define CODE(code, ty) fmt_##code,
109   FMTTYPES(CODE)
110 #undef CODE
111   fmt__limit
112 };
113
114 typedef struct {
115   int fmt;
116   union {
117 #define MEMB(code, ty) ty code;
118     FMTTYPES(MEMB)
119 #undef MEMB
120   } u;
121 } fmtarg;
122
123 DA_DECL(fmtarg_v, fmtarg);
124
125 enum {
126   len_std = 0,
127   len_hh,
128   len_h,
129   len_l,
130   len_ll,
131   len_z,
132   len_t,
133   len_j,
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
148 typedef 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
157 DA_DECL(fmtspec_v, fmtspec);
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
165  *              @va_list *ap@ = argument handle
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
173 static 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
188 int dstr_vputf(dstr *d, const char *p, va_list *ap)
189 {
190   size_t n = d->len;
191   size_t sz, mx;
192   dstr dd = DSTR_INIT;
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@
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.
207    */
208
209   anext = 0;
210
211   while (*p) {
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;
222
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; }
231
232     /* --- Pick up initial flags --- */
233
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       }
243       p++;
244     }
245
246     /* --- Pick up the field width --- */
247
248   done_flags:
249     i = 0;
250     while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0';
251
252     /* --- Snag: this might have been an argument position indicator --- */
253
254     if (i && *p == '$' && (!f || f == f_zero)) {
255       f |= f_posarg;
256       fs->arg = i - 1;
257       p++;
258       goto flags;
259     }
260
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 --- */
285
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--;
304         }
305         f |= f_precarg;
306         set_arg(&av, i, fmt_i); fs->prec = i;
307       }
308     }
309
310     /* --- Maybe some length flags --- */
311
312     switch (*p) {
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;
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; )
325     }
326
327     /* --- The flags are now ready --- */
328
329     fs->f = f;
330
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;
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;
346         }
347         break;
348       case 'a': case 'A':
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) {
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;
371         }
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 --- */
402
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();
418       }
419       continue;
420     }
421
422     DRESET(&dd);
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     }
452
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) {
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; )
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;
532   }
533
534   /* --- We're done --- */
535
536   DPUTZ(d);
537   DDESTROY(&dd);
538   DA_DESTROY(&av);
539   DA_DESTROY(&sv);
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
557 int dstr_putf(dstr *d, const char *p, ...)
558 {
559   int n;
560   va_list ap;
561   va_start(ap, p);
562   n = dstr_vputf(d, p, &ap);
563   va_end(ap);
564   return (n);
565 }
566
567 /*----- That's all, folks -------------------------------------------------*/