X-Git-Url: https://git.distorted.org.uk/~mdw/mLib/blobdiff_plain/e744e21a5aa1e4e5820999ac1e6776fa8fe76dd8..5007fea9956f473c7b5522260994180dcd7cd9e6:/struct/dstr-putf.c diff --git a/struct/dstr-putf.c b/struct/dstr-putf.c index ba715b1..9ac8cf9 100644 --- a/struct/dstr-putf.c +++ b/struct/dstr-putf.c @@ -29,7 +29,9 @@ #include "config.h" +#include #include +#include #include #include #include @@ -40,16 +42,120 @@ # include #endif +#ifdef HAVE_STDINT_H +# include +#endif + +#include "darray.h" #include "dstr.h" /*----- Tunable constants -------------------------------------------------*/ /* - * For each format specifier, at least @DSTR_PUTFSTEP@ bytes are ensured - * before writing the formatted result. + * For each format specifier, at least @PUTFSTEP@ bytes are ensured before + * writing the formatted result. */ -#define DSTR_PUTFSTEP 64 /* Buffer size for @putf@ */ +#define PUTFSTEP 64 /* Buffer size for @putf@ */ + +/*----- Preliminary definitions -------------------------------------------*/ + +#ifdef HAVE_FLOAT_H +# define IF_FLOAT(x) x +#else +# define IF_FLOAT(x) +#endif + +#if defined(LLONG_MAX) || defined(LONG_LONG_MAX) +# define IF_LONGLONG(x) x +#else +# define IF_LONGLONG(x) +#endif + +#ifdef INTMAX_MAX +# define IF_INTMAX(x) x +#else +# define IF_INTMAX(x) +#endif + +#define OUTPUT_FMTTYPES(_) \ + _(i, unsigned int) \ + _(li, unsigned long) \ + IF_LONGLONG( _(lli, unsigned long long) ) \ + _(zi, size_t) \ + _(ti, ptrdiff_t) \ + IF_INTMAX( _(ji, uintmax_t) ) \ + _(s, char *) \ + _(p, void *) \ + _(f, double) \ + _(Lf, long double) + +#define PERCENT_N_FMTTYPES(_) \ + _(n, int *) \ + _(hhn, char *) \ + _(hn, short *) \ + _(ln, long *) \ + _(zn, size_t *) \ + _(tn, ptrdiff_t *) \ + IF_LONGLONG( _(lln, long long *) ) \ + IF_INTMAX( _(jn, intmax_t *) ) + +#define FMTTYPES(_) \ + OUTPUT_FMTTYPES(_) \ + PERCENT_N_FMTTYPES(_) + +enum { + fmt_unset = 0, +#define CODE(code, ty) fmt_##code, + FMTTYPES(CODE) +#undef CODE + fmt__limit +}; + +typedef struct { + int fmt; + union { +#define MEMB(code, ty) ty code; + FMTTYPES(MEMB) +#undef MEMB + } u; +} fmtarg; + +DA_DECL(fmtarg_v, fmtarg); + +enum { + len_std = 0, + len_hh, + len_h, + len_l, + len_ll, + len_z, + len_t, + len_j, + len_L +}; + +#define f_len 0x000fu +#define f_wd 0x0010u +#define f_wdarg 0x0020u +#define f_prec 0x0040u +#define f_precarg 0x0080u +#define f_plus 0x0100u +#define f_minus 0x0200u +#define f_sharp 0x0400u +#define f_zero 0x0800u +#define f_posarg 0x1000u + +typedef struct { + const char *p; + size_t n; + unsigned f; + int fmt, ch; + int wd, prec; + int arg; +} fmtspec; + +DA_DECL(fmtspec_v, fmtspec); /*----- Main code ---------------------------------------------------------*/ @@ -65,217 +171,369 @@ * supplied functions with @printf@-style interfaces. */ +static void set_arg(fmtarg_v *av, size_t i, int fmt) +{ + size_t j, n; + + n = DA_LEN(av); + if (i >= n) { + DA_ENSURE(av, i + 1 - n); + for (j = n; j <= i; j++) DA(av)[j].fmt = fmt_unset; + DA_UNSAFE_EXTEND(av, i + 1 - n); + } + + if (DA(av)[i].fmt == fmt_unset) DA(av)[i].fmt = fmt; + else assert(DA(av)[i].fmt == fmt); +} + int dstr_vputf(dstr *d, const char *p, va_list *ap) { - const char *q = p; size_t n = d->len; - size_t sz; + size_t sz, mx; dstr dd = DSTR_INIT; + fmtspec_v sv = DA_INIT; + fmtarg_v av = DA_INIT; + fmtarg *fa, *fal; + fmtspec *fs, *fsl; + unsigned f; + int i, anext; + int wd, prec; + + /* --- Initial pass through the input, parsing format specifiers --- * + * + * We essentially compile the format string into a vector of @fmtspec@ + * objects, each of which represnts a chunk of literal text followed by a + * (possibly imaginary, in the case of the final one) formatting directive. + * Output then simply consists of interpreting these specifiers in order. + */ + + anext = 0; while (*p) { - unsigned f; - int wd, prec; + f = 0; + DA_ENSURE(&sv, 1); + fs = &DA(&sv)[DA_LEN(&sv)]; + DA_UNSAFE_EXTEND(&sv, 1); -#define f_short 1u -#define f_long 2u -#define f_Long 4u -#define f_wd 8u -#define f_prec 16u + /* --- Find the end of this literal portion --- */ - /* --- Most stuff gets passed on through --- */ + fs->p = p; + while (*p && *p != '%') p++; + fs->n = p - fs->p; + + /* --- Some simple cases --- * + * + * We might have reached the end of the string, or maybe a `%%' escape. + */ + + if (!*p) { fs->fmt = fmt_unset; fs->ch = 0; break; } + p++; + if (*p == '%') { fs->fmt = fmt_unset; fs->ch = '%'; p++; continue; } - if (*p != '%') { + /* --- Pick up initial flags --- */ + + flags: + for (;;) { + switch (*p) { + case '+': f |= f_plus; break; + case '-': f |= f_minus; break; + case '#': f |= f_sharp; break; + case '0': f |= f_zero; break; + default: goto done_flags; + } p++; - continue; } - /* --- Dump out what's between @q@ and @p@ --- */ + /* --- Pick up the field width --- */ - DPUTM(d, q, p - q); - p++; + done_flags: + i = 0; + while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0'; - /* --- Sort out the various silly flags and things --- */ + /* --- Snag: this might have been an argument position indicator --- */ - DPUTC(&dd, '%'); - f = 0; - sz = DSTR_PUTFSTEP; + if (i && *p == '$' && (!f || f == f_zero)) { + f |= f_posarg; + fs->arg = i - 1; + p++; + goto flags; + } - for (;;) { - switch (*p) { + /* --- Set the field width --- * + * + * If @i@ is nonzero here then we have a numeric field width. Otherwise + * it might be `*', maybe with an explicit argument number. + */ - /* --- Various simple flags --- */ - - case '+': - case '-': - case '#': - case '0': - goto putch; - case 'h': - f |= f_short; - goto putch; - case 'l': - f |= f_long; - goto putch; - case 'L': - f |= f_Long; - goto putch; - case 0: - goto finished; - - /* --- Field widths and precision specifiers --- */ - - { - int *ip; - - case '.': - DPUTC(&dd, '.'); - ip = ≺ - f |= f_prec; - p++; - goto getnum; - case '*': - ip = &wd; - f |= f_wd; - goto getnum; - default: - if (isdigit((unsigned char)*p)) { - f |= f_wd; - ip = &wd; - goto getnum; - } - DPUTC(d, *p); - goto formatted; - getnum: - *ip = 0; - if (*p == '*') { - *ip = va_arg(*ap, int); - DENSURE(&dd, DSTR_PUTFSTEP); - dd.len += sprintf(dd.buf + dd.len, "%i", *ip); - p++; - } else { - *ip = *p - '0'; - DPUTC(&dd, *p); - p++; - while (isdigit((unsigned char)*p)) { - DPUTC(&dd, *p); - *ip = 10 * *ip + *p++ - '0'; - } - } - break; + if (i) { + f |= f_wd; + fs->wd = i; + } else if (*p == '*') { + p++; + if (!isdigit((unsigned char)*p)) + i = anext++; + else { + i = *p++ - '0'; + while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0'; + assert(*p == '$'); p++; + assert(i > 0); i--; + } + f |= f_wd | f_wdarg; + set_arg(&av, i, fmt_i); fs->wd = i; + } + + /* --- Maybe we have a precision spec --- */ + + if (*p == '.') { + p++; + f |= f_prec; + if (isdigit((unsigned char)*p)) { + i = *p++ - '0'; + while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0'; + fs->prec = i; + } else if (*p != '*') + fs->prec = 0; + else { + p++; + if (!isdigit((unsigned char)*p)) + i = anext++; + else { + i = *p++ - '0'; + while (isdigit((unsigned char)*p)) i = 10*i + *p++ - '0'; + assert(*p == '$'); p++; + assert(i > 0); i--; } + f |= f_precarg; + set_arg(&av, i, fmt_i); fs->prec = i; + } + } - /* --- Output formatting --- */ - - case 'd': case 'i': case 'x': case 'X': case 'o': case 'u': - DPUTC(&dd, *p); - DPUTZ(&dd); - if ((f & f_prec) && prec + 16 > sz) - sz = prec + 16; - if ((f & f_wd) && wd + 1> sz) - sz = wd + 1; - DENSURE(d, sz); - if (f & f_long) - d->len += sprintf(d->buf + d->len, dd.buf, - va_arg(*ap, unsigned long)); - else - d->len += sprintf(d->buf + d->len, dd.buf, - va_arg(*ap, unsigned int)); - goto formatted; - - case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': -#ifdef HAVE_FLOAT_H - DPUTC(&dd, *p); - DPUTZ(&dd); - if (*p == 'f') { - size_t mx = (f & f_Long ? LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16; - if (mx > sz) - sz = mx; - } - if (!(f & f_prec)) - prec = 6; - else - sz += prec + 16; - if ((f & f_wd) && wd + 1 > sz) - sz = wd + 1; - DENSURE(d, sz); - if (f & f_Long) - d->len += sprintf(d->buf + d->len, dd.buf, - va_arg(*ap, long double)); - else - d->len += sprintf(d->buf + d->len, dd.buf, - va_arg(*ap, double)); - goto formatted; -#else - DPUTS(d, ""); -#endif + /* --- Maybe some length flags --- */ + + switch (*p) { + case 'h': + p++; + if (*p == 'h') { f |= len_hh; p++; } else f |= len_h; + break; + case 'l': + p++; + IF_LONGLONG( if (*p == 'l') { f |= len_ll; p++; } else ) f |= len_l; + break; + case 'L': f |= len_L; p++; break; + case 'z': f |= len_z; p++; break; + case 't': f |= len_t; p++; break; + IF_INTMAX( case 'j': f |= len_j; p++; break; ) + } - case 'c': - DPUTC(&dd, *p); - DPUTZ(&dd); - if ((f & f_wd) && wd + 1> sz) - sz = wd + 1; - DENSURE(d, sz); - d->len += sprintf(d->buf + d->len, dd.buf, - va_arg(*ap, unsigned)); - goto formatted; - - case 's': { - const char *s = va_arg(*ap, const char *); - sz = strlen(s); - DPUTC(&dd, *p); - DPUTZ(&dd); - if (f & f_prec) - sz = prec; - if ((f & f_wd) && wd > sz) - sz = wd; - DENSURE(d, sz + 1); - d->len += sprintf(d->buf + d->len, dd.buf, s); - goto formatted; + /* --- The flags are now ready --- */ + + fs->f = f; + + /* --- At the end, an actual directive --- */ + + fs->ch = *p; + switch (*p++) { + case '%': + fs->fmt = fmt_unset; + break; + case 'd': case 'i': case 'x': case 'X': case 'o': case 'u': + switch (f & f_len) { + case len_l: fs->fmt = fmt_li; break; + case len_z: fs->fmt = fmt_zi; break; + case len_t: fs->fmt = fmt_ti; break; + IF_LONGLONG( case len_ll: fs->fmt = fmt_lli; break; ) + IF_INTMAX( case len_j: fs->fmt = fmt_ji; break; ) + default: fs->fmt = fmt_i; + } + break; + case 'a': case 'A': + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + fs->fmt = (f & f_len) == len_L ? fmt_Lf : fmt_f; + break; + case 'c': + fs->fmt = fmt_i; + break; + case 's': + fs->fmt = fmt_s; + break; + case 'p': + fs->fmt = fmt_p; + break; + case 'n': + switch (f & f_len) { + case len_hh: fs->fmt = fmt_hhn; break; + case len_h: fs->fmt = fmt_hn; break; + case len_l: fs->fmt = fmt_ln; break; + case len_z: fs->fmt = fmt_zn; break; + case len_t: fs->fmt = fmt_tn; break; + IF_LONGLONG( case len_ll: fs->fmt = fmt_lln; break; ) + IF_INTMAX( case len_j: fs->fmt = fmt_jn; break; ) + default: fs->fmt = fmt_n; } + break; + default: + fprintf(stderr, + "FATAL dstr_vputf: unknown format specifier `%c'\n", p[-1]); + abort(); + } + + /* --- Finally sort out the argument --- * + * + * If we don't have explicit argument positions then this comes after the + * width and precision; and we don't know the type code until we've + * parsed the specifier, so this seems the right place to handle it. + */ + + if (!(f & f_posarg)) fs->arg = anext++; + set_arg(&av, fs->arg, fs->fmt); + } + + /* --- Quick pass over the argument vector to collect the arguments --- */ + + for (fa = DA(&av), fal = fa + DA_LEN(&av); fa < fal; fa++) { + switch (fa->fmt) { +#define CASE(code, ty) case fmt_##code: fa->u.code = va_arg(*ap, ty); break; + FMTTYPES(CASE) +#undef CASE + default: abort(); + } + } + + /* --- Final pass through the format string to produce output --- */ + + fa = DA(&av); + for (fs = DA(&sv), fsl = fs + DA_LEN(&sv); fs < fsl; fs++) { + f = fs->f; - case 'p': - DPUTC(&dd, *p); - DPUTZ(&dd); - if ((f & f_prec) && prec + 16 > sz) - sz = prec + 16; - if ((f & f_wd) && wd + 1> sz) - sz = wd + 1; - DENSURE(d, sz); - d->len += sprintf(d->buf + d->len, dd.buf, - va_arg(*ap, const void *)); - goto formatted; - - case 'n': - if (f & f_long) - *va_arg(*ap, long *) = (long)(d->len - n); - else if (f & f_short) - *va_arg(*ap, short *) = (short)(d->len - n); - else - *va_arg(*ap, int *) = (int)(d->len - n); - goto formatted; - - /* --- Other random stuff --- */ - - putch: - DPUTC(&dd, *p); - p++; - break; + /* --- Output the literal portion --- */ + + if (fs->n) DPUTM(d, fs->p, fs->n); + + /* --- And now the variable portion --- */ + + if (fs->fmt == fmt_unset) { + switch (fs->ch) { + case 0: break; + case '%': DPUTC(d, '%'); break; + default: abort(); } + continue; } - formatted: DRESET(&dd); - q = ++p; + DPUTC(&dd, '%'); + + /* --- Resolve the width and precision --- */ + + if (!(f & f_wd)) + wd = 0; + else { + wd = (fs->f & f_wdarg) ? *(int *)&fa[fs->wd].u.i : fs->wd; + if (wd < 0) { wd = -wd; f |= f_minus; } + } + + if (!(f & f_prec)) + prec = 0; + else { + prec = (fs->f & f_precarg) ? *(int *)&fa[fs->prec].u.i : fs->prec; + if (prec < 0) { prec = 0; f &= ~f_prec; } + } + + /* --- Write out the flags, width and precision --- */ -#undef f_short -#undef f_long -#undef f_Long -#undef f_wd -#undef f_prec + if (f & f_plus) DPUTC(&dd, '+'); + if (f & f_minus) DPUTC(&dd, '-'); + if (f & f_sharp) DPUTC(&dd, '#'); + if (f & f_zero) DPUTC(&dd, '0'); + + if (f & f_wd) { + DENSURE(&dd, PUTFSTEP); + dd.len += sprintf(dd.buf + dd.len, "%d", wd); + } + + if (f & f_prec) { + DENSURE(&dd, PUTFSTEP + 1); + dd.len += sprintf(dd.buf + dd.len, ".%d", prec); + } + + /* --- Write out the length gadget --- */ + + switch (f & f_len) { + case len_hh: DPUTC(&dd, 'h'); /* fall through */ + case len_h: DPUTC(&dd, 'h'); break; + IF_LONGLONG( case len_ll: DPUTC(&dd, 'l'); /* fall through */ ) + case len_l: DPUTC(&dd, 'l'); break; + case len_z: DPUTC(&dd, 'z'); break; + case len_t: DPUTC(&dd, 't'); break; + case len_L: DPUTC(&dd, 'L'); break; + IF_INTMAX( case len_j: DPUTC(&dd, 'j'); break; ) + case len_std: break; + default: abort(); + } + + /* --- And finally the actually important bit --- */ + + DPUTC(&dd, fs->ch); + DPUTZ(&dd); + + /* --- Make sure we have enough space for the output --- */ + + sz = PUTFSTEP; + if (sz < wd) sz = wd; + if (sz < prec + 16) sz = prec + 16; + switch (fs->ch) { + case 'a': case 'A': + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': +#ifdef HAVE_FLOAT_H + if (fs->ch == 'f') { + mx = ((fs->f & f_len) == len_L ? + LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16; + if (sz < mx) sz = mx; + } + break; +#else + DPUTS(d, ""); + continue; +#endif + case 's': + if (!(f & f_prec)) { + n = strlen(fa[fs->arg].u.s); + if (sz < n) sz = n; + } + break; + case 'n': + switch (fs->fmt) { +#define CASE(code, ty) \ + case fmt_##code: *fa[fs->arg].u.code = d->len - n; break; + PERCENT_N_FMTTYPES(CASE) +#undef CASE + default: abort(); + } + continue; + } + + /* --- Finally do the output stage --- */ + + DENSURE(d, sz + 1); + switch (fs->fmt) { +#ifdef HAVE_SNPRINTF +# define CASE(code, ty) case fmt_##code: \ + i = snprintf(d->buf + d->len, sz + 1, dd.buf, fa[fs->arg].u.code); \ + break; +#else +# define CASE(code, ty) case fmt_##code: \ + i = sprintf(d->buf + d->len, dd.buf, fa[fs->arg].u.code); \ + break; +#endif + OUTPUT_FMTTYPES(CASE) +#undef CASE + default: abort(); + } + assert(0 <= i && i <= sz); d->len += i; } - DPUTM(d, q, p - q); -finished: + /* --- We're done --- */ + DPUTZ(d); DDESTROY(&dd); return (d->len - n);