From e63124bc579bfd97cfe2f620ddd84df9f20477d8 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Thu, 25 May 2023 08:45:34 +0100 Subject: [PATCH] @@@ so much mess --- hash/t/hash-test.c | 41 +-- struct/Makefile.am | 2 +- struct/buf-float.c | 326 +++++++++++++++++ struct/buf-putf.c | 151 ++++++++ struct/buf.c | 149 +++++++- struct/buf.h | 229 +++++++++++- struct/dstr-putf.c | 507 ++------------------------ struct/dstr.h | 3 + t/template-canonify | 82 +++++ test/Makefile.am | 2 + test/bench.c | 4 +- test/bench.h | 10 +- test/t/tvec-test.c | 203 ++++++++--- test/tests.at | 162 ++++++--- test/tvec-bench.c | 275 ++++++++++++++ test/tvec-core.c | 426 ++++++++++------------ test/tvec-main.c | 18 +- test/tvec-output.c | 372 +++++++------------ test/tvec-remote.c | 297 +++++++++++++++ test/tvec-types.c | 893 +++++++++++++++++++++++++++++++++++----------- test/tvec.3 | 24 ++ test/tvec.h | 535 ++++++++++++++++++++++----- utils/Makefile.am | 5 + utils/gprintf.c | 599 +++++++++++++++++++++++++++++++ utils/gprintf.h | 95 +++++ utils/t/bits-test.c | 14 +- utils/t/versioncmp-test.c | 14 +- 27 files changed, 4021 insertions(+), 1417 deletions(-) create mode 100644 struct/buf-float.c create mode 100644 struct/buf-putf.c create mode 100755 t/template-canonify create mode 100644 test/tvec-bench.c create mode 100644 test/tvec-remote.c create mode 100644 test/tvec.3 create mode 100644 utils/gprintf.c create mode 100644 utils/gprintf.h diff --git a/hash/t/hash-test.c b/hash/t/hash-test.c index 6298b82..1033c59 100644 --- a/hash/t/hash-test.c +++ b/hash/t/hash-test.c @@ -81,28 +81,30 @@ static void test_unihash(const struct tvec_reg *in, struct tvec_reg *out, static void bench_unihash(const struct tvec_reg *in, struct tvec_reg *out, void *ctx) { unihash_hash(ctx, 0, in[RM].v.bytes.p, in[RM].v.bytes.sz); } - -static int setup_unihash(const struct tvec_reg *in, struct tvec_reg *out, - const union tvec_misc *arg, void *ctx) +static int setup_unihash(struct tvec_state *tv, + const struct tvec_env *env, void *pctx, void *ctx) { unihash_setkey(ctx, 0); return (0); } +static const struct tvec_env unihash_benchenv = + { sizeof(unihash_info), setup_unihash, 0, 0 }; -static int run_step(struct tvec_state *tv) +static void run_step(struct tvec_state *tv, tvec_testfn *fn, void *ctx) { static const size_t steps[] = { 1, 5, 6, 7, 8, 23 }; struct step step; size_t i; - tv->test->fn(tv->in, tv->out, 0); + fn(tv->in, tv->out, 0); tvec_check(tv, "whole buffer"); for (i = 0; i < N(steps); i++) { step.s = steps[i]; - tv->test->fn(tv->in, tv->out, &step); + fn(tv->in, tv->out, &step); tvec_check(tv, "step = %lu", (unsigned long)steps[i]); } - return (0); } +static const struct tvec_env step_testenv = { 0, 0, 0, 0, run_step, 0, 0 }; + static const struct tvec_regdef unihash_regs[] = { { "k", RK, &tvty_uint, 0, { &tvrange_u32 } }, { "m", RM, &tvty_bytes, 0 }, @@ -121,24 +123,17 @@ static const struct tvec_regdef bench_regs[] = { { 0, 0, 0, 0 } }; -static const struct tvec_bench crc32_bench = { - 1, -1, RM, - 0, 0, 0, 0, { 0 } -}; - -static const struct tvec_bench unihash_bench = { - 1, -1, RM, - sizeof(unihash_info), setup_unihash, 0, 0, { 0 } -}; +static const struct tvec_bench crc32_bench = + { TVEC_BENCHINIT, 1, -1, RM, 0 }; +static const struct tvec_bench unihash_bench = + { TVEC_BENCHINIT, 1, -1, RM, &unihash_benchenv }; static const struct tvec_test tests[] = { - { "crc32",crc32_regs, 0, run_step, test_crc32 }, - { "unihash", unihash_regs, 0, run_step, test_unihash }, - { "crc32-bench", bench_regs, 0, - tvec_bench, bench_crc32, { &crc32_bench } }, - { "unihash-bench", bench_regs, 0, - tvec_bench, bench_unihash, { &unihash_bench } }, - { 0, 0, 0, 0, 0 } + { "crc32", crc32_regs, &step_testenv, test_crc32 }, + { "unihash", unihash_regs, &step_testenv, test_unihash }, + { "crc32-bench", bench_regs, &crc32_bench._env, bench_crc32 }, + { "unihash-bench", bench_regs, &unihash_bench._env, bench_unihash }, + { 0, 0, 0, 0 } }; static const struct tvec_info testinfo = diff --git a/struct/Makefile.am b/struct/Makefile.am index 6365023..7996231 100644 --- a/struct/Makefile.am +++ b/struct/Makefile.am @@ -43,7 +43,7 @@ t_dstr_putf_t_LDFLAGS = -static ## Buffers. pkginclude_HEADERS += buf.h -libstruct_la_SOURCES += buf.c buf-dstr.c +libstruct_la_SOURCES += buf.c buf-dstr.c buf-float.c buf-putf.c LIBMANS += buf.3 ## Dynamic arrays. diff --git a/struct/buf-float.c b/struct/buf-float.c new file mode 100644 index 0000000..502b96b --- /dev/null +++ b/struct/buf-float.c @@ -0,0 +1,326 @@ +/* -*-c-*- + * + * Encoding and decoding floating-point values + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include + +#include "bits.h" +#include "buf.h" + +/*----- Formatting primitives ---------------------------------------------*/ + +/* We use the IEEE 754 `binary64' format. Briefly: + * + * * The top bit is the sign %$s$%: 0 encodes %$s = +1$%, and 1 encodes + * %$s = -1$%.. The format is signed-magnitude, so everything else is + * the same for positive and negative numbers. + * + * * The next eleven bits are the biased exponent %$e$%. + * + * * The remaining 52 bits are the significand %$m$%. + * + * If %$0 < e < 2047$% then the encoding represents the normal number + * %$s \cdot (1 + m/2^{52}) \cdot 2^{e-1023}$%. + * + * If %$e = 0$% and %$m = 0$% then the encoding represents positive or + * negative zero. + * + * If %$e = 0$% and %$m \ne 0$% then the encoding represents a subnormal + * number %$s \cdot m/2^{52} \cdot 2^{-1022}$%. + * + * If %$e = 2047$% and %$m = 0$% then the encoding represents positive or + * negative infinity. + * + * If %$e = 2047$% and %$m \ne 0$% then the encoding represents a NaN. If + * the most significant bit of %$m$% is set then this is a quiet NaN; + * otherwise it's a signalling NaN. + */ + +/* --- @f64_to_k64@ --- * + * + * Arguments: @double x@ = a floating-point number + * + * Returns: A 64-bit encoding of @x@. + * + * Use: Encodes @x@ as a `binary64' value. See `buf_putf64' for the + * caveats. + */ + +static kludge64 f64_to_k64(double x) +{ + kludge64 k; + uint32 lo, hi, t; + int e; double m; + + /* Some machinery before we start. */ + +#ifdef isnan +# define NANP(x) isnan(x) +#else +# define NANP(x) (!((x) == (x))) +#endif + +#ifdef isinf +# define INFP(x) isinf(x) +#else +# define INFP(x) ((x) > DBL_MAX || (x) < -DBL_MAX) +#endif + +#ifdef signbit +# define NEGP(x) signbit(x) +#else +# define NEGP(x) ((x) < 0) /* incorrect for negative zero! */ +#endif + + if (NANP(x)) { + /* A NaN. */ + hi = 0x7ff80000; lo = 0; + } else if (INFP(x)) { + /* Positive or negative infinity. */ + hi = NEGP(x) ? 0xfff00000 : 0x7ff00000; lo = 0; + } else if (x == 0) { + /* Positive or negative zero. */ + hi = NEGP(x) ? 0x80000000 : 0; lo = 0; + } else { + /* A normal or subnormal number. Now we have to do some actual work. */ + + /* Let's get the sign dealt with so we don't have to worry about it any + * more. + */ + if (!NEGP(x)) hi = 0; + else { x = -x; hi = 0x80000000; } + + /* Now we start on the value. The first thing to do is to split off the + * exponent. Our number will be %$m \cdot 2^e$%, with %$1/2 \le m < 1$%. + */ + m = frexp(x, &e); + + /* If our number is too big, we'll round it to infinity. This will + * happen if %$x \ge 2^{1024}$%, i.e., if %$e > 1024$%. + */ + if (e > 1024) + { hi |= 0x7ff00000; lo = 0; } + else { + /* Our number is sufficiently small that we can represent it at least + * approximately (though maybe we'll have to flush it to zero). The + * next step, then, is to pull the significand bits out. + */ + + /* Determine the correct exponent to store. We're not going to bias it + * yet, but this is where we deal with subnormal numbers. Our number + * is normal if %$x \ge 2^{-1022}$%, i.e., %$e > -1022$%. In this + * case, there's an implicit bit which we'll clear. Otherwise, if it's + * subnormal, we'll scale our floating-point number so that the + * significand will look right when we extract it, and adjust the + * exponent so that, when we're finally done, it will have the correct + * sentinel value. + */ + if (e > -1022) m -= 0.5; + else { m = ldexp(m, 1021 + e); e = -1022; } + + /* Now we pull out the 53 bits of the significand. This will, in + * general, leave a tail which we address through rounding. Scale it + * up so that we end up with %$0 \le m' < 2$%; then we round up if + * %$m > 1$%, or if %$m = 1$% and the low bit of the significand is + * set. + */ + t = ldexp(m, 21); m -= ldexp(t, -21); + lo = ldexp(m, 53); m -= ldexp(lo, -53); + m = ldexp(m, 54); + + /* Round the number if necessary. */ + if (lo&1 ? m >= 1.0 : m > 1) + { lo = U32(lo + 1); if (!lo) t++; } + + /* Now we just put the pieces together. Note that our %$e$% is one + * greater than it should be, because our implicit bit should have + * been the unit bit not the 1/2 bit. + */ + hi |= ((uint32)(e + 1022) << 20) | t; + } + } + + /* Convert to external format and go home. */ + SET64(k, hi, lo); return (k); + +#undef NANP +#undef INFP +#undef NEGP +} + +/* --- @k64_to_f64@ --- * + * + * Arguments: @double *x_out@ = where to put the result + * @kludge64 k@ = a 64-bit encoding of a floating-point value + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Decodes @k@ as a `binary64' value. See `buf_getf64' for the + * caveats. + */ + +static int k64_to_f64(double *x_out, kludge64 k) +{ + uint32 lo, hi, t; + int s, e; double x; + + /* We're using the IEEE 754 `binary64' format: see `float_to_k64' above. */ + + /* Pick the encoded number apart. */ + hi = HI64(k); lo = LO64(k); + s = (hi >> 31)&1; e = (hi >> 20)&0x07ff; t = hi&0x000fffff; + + /* Deal with various special cases. */ + if (e == 2047) { + /* Maximum exponent indicates (positive or negative) infinity or NaN. */ + + if (t || lo) { + /* It's a NaN. We're not going to be picky about which one. If we + * can't represent it then we'll just have to fail. + */ + +#ifdef NAN + x = NAN; +#else + return (-1); +#endif + } else { + /* It's an infinity. If we don't have one of those to hand, then pick + * something really big. + */ + +#ifdef INFINITY + x = s ? -INFINITY : INFINITY; +#else + x = s ? -DBL_MAX : DBL_MAX; +#endif + } + } else { + /* It's a finite number, though maybe it's weird in some way. */ + + if (e == 0) { + /* Minimum exponent indicates zero or a subnormal number. The + * subnormal exponent is a sentinel value that shouldn't be taken + * literally, so we should fix that. If the number is actually zero + * then the exponent won't matter much so don't bother checking. + */ + + e = 1; + } else { + /* It's a normal number. In which case there's an implicit bit which + * we can now set. + */ + + t |= 0x00100000; + } + + /* All that remains is to stuff the significant and exponent into a + * floating point number. We'll have to do this in pieces, and we'll + * lean on the floating-point machinery to do rounding correctly. + */ + x = ldexp(t, e - 1043) + ldexp(lo, e - 1075); + if (s) x = -x; + } + + /* And we're done. */ + *x_out = x; return (0); +} + +/*----- External functions ------------------------------------------------*/ + +/* --- @buf_putf64{,b,l} --- * + * + * Arguments: @buf *b@ = a buffer to write to + * @double x@ = a number to write + * + * Returns: Zero on success, @-1@ on failure (and the buffer is broken). + * + * On C89, this function can't detect negative zero so these + * will be silently written as positive zero. + * + * This function doesn't distinguish NaNs. Any NaN is written + * as a quiet NaN with all payload bits zero. + * + * A finite value with too large a magnitude to be represented + * is rounded to the appropriate infinity. Other finite values + * are rounded as necessary, in the usual IEEE 754 round-to- + * nearest-or-even way. + */ + +int buf_putf64(buf *b, double x) + { return (buf_putk64(b, f64_to_k64(x))); } +int buf_putf64b(buf *b, double x) + { return (buf_putk64b(b, f64_to_k64(x))); } +int buf_putf64l(buf *b, double x) + { return (buf_putk64l(b, f64_to_k64(x))); } + +/* --- @buf_getf64{,b,l} --- * + * + * Arguments: @buf *b@ = a buffer to read from + * @double *x_out@ = where to put the result + * + * Returns: Zero on success, @-1@ on failure (and the buffer is broken). + * + * If the system supports NaNs, then any encoded NaN is returned + * as the value of @NAN@ in @@; otherwise, this function + * reports failure. + * + * In general, values are rounded to the nearest available + * value, in the way that the system usually rounds. If the + * system doesn't support infinities, then any encoded infinity + * is reported as the largest-possible-magnitude finite value + * instead. + */ + +int buf_getf64(buf *b, double *x_out) +{ + kludge64 k; + + if (buf_getk64(b, &k)) return (-1); + if (k64_to_f64(x_out, k)) { b->f |= BF_BROKEN; return (-1); } + return (0); +} +int buf_getf64b(buf *b, double *x_out) +{ + kludge64 k; + + if (buf_getk64b(b, &k)) return (-1); + if (k64_to_f64(x_out, k)) { b->f |= BF_BROKEN; return (-1); } + return (0); +} +int buf_getf64l(buf *b, double *x_out) +{ + kludge64 k; + + if (buf_getk64l(b, &k)) return (-1); + if (k64_to_f64(x_out, k)) { b->f |= BF_BROKEN; return (-1); } + return (0); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/struct/buf-putf.c b/struct/buf-putf.c new file mode 100644 index 0000000..5b465f2 --- /dev/null +++ b/struct/buf-putf.c @@ -0,0 +1,151 @@ +/* -*-c-*- + * + * Format a string to a buffer + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include + +#include "buf.h" +#include "gprintf.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @buf_vputstrf@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer + * @const char *p@ = pointer to @printf@-style format string + * @va_list *ap@ = argument handle + * + * Returns: The number of characters written to the string, or @-1@ on + * failure. + * + * Use: As for @buf_putstrf@, but may be used as a back-end to user- + * supplied functions with @printf@-style interfaces. + */ + +static int putch(void *out, int ch) + { buf *b = out; return (buf_putbyte(b, ch)); } + +static int putm(void *out, const char *p, size_t sz) + { buf *b = out; return (buf_put(b, p, sz)); } + +static int nputf(void *out, size_t maxsz, const char *p, ...) +{ + buf *b = out; + va_list ap; + int n; + + va_start(ap, p); + if (BENSURE(b, maxsz + 1)) return (-1); +#ifdef HAVE_SNPRINTF + n = vsnprintf((char *)BCUR(b), maxsz + 1, p, ap); +#else + n = vsprintf((char *)BCUR(b), p, ap); +#endif + assert(0 <= n && n <= maxsz); + va_end(ap); b->p += n; return (n); +} + +static int putbuf(void *out, const char *p, size_t sz) + { buf *b = out; b->p += sz; return (0); } + +const struct gprintf_ops buf_printops = + { putch, putm, nputf }; + +int buf_vputstrf(buf *b, const char *p, va_list *ap) + { return (vgprintf(&buf_printops, b, p, ap)); } + +/* --- @buf_putstrf@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer + * @const char *p@ = pointer to @printf@-style format string + * @...@ = argument handle + * + * Returns: The number of characters written to the string, or @-1@ on + * failure. + * + * Use: Format a string to a buffer. The resulting output is not + * null-terminated. + */ + +int buf_putstrf(buf *b, const char *p, ...) +{ + va_list ap; + int n; + + va_start(ap, p); n = buf_vputstrf(b, p, &ap); va_end(ap); + return (n); +} + +/* --- @buf_{,v}putstrf{8,{16,24,32,64}{,b,l},z}@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer + * @const char *p@ = pointer to @printf@-style format string + * @va_list *ap@ = argument handle + * + * Returns: The number of characters written to the string, or @-1@ on + * failure. + * + * Use: As for @buf_putstr@, but using a format string. + */ + +#define BUF_DEF_VPUTSTRF_(n, W, w) \ + int buf_vputstrf##w(buf *b, const char *p, va_list *ap) \ + { \ + size_t mk; \ + int nn; \ + \ + BUF_ENCLOSE##W(b, mk) nn = buf_vputstrf(b, p, ap); \ + return (BOK(b) ? nn : -1); \ + } +DOUINTCONV(BUF_DEF_VPUTSTRF_) +BUF_DOKLUDGESUFFIXES(BUF_DEF_VPUTSTRF_) +#undef BUF_DEF_VPUTSTRF_ + +int buf_vputstrfz(buf *b, const char *p, va_list *ap) +{ + int nn; + + BUF_ENCLOSEZ(b) nn = buf_vputstrf(b, p, ap); + return (BOK(b) ? nn : -1); +} + +#define BUF_DEF_PUTSTRF_(n, W, w) \ + int buf_putstrf##w(buf *b, const char *p, ...) \ + { \ + va_list ap; \ + int nn; \ + \ + va_start(ap, p); nn = buf_vputstrf##w(b, p, &ap); va_end(ap); \ + return (nn); \ + } +BUF_DOSUFFIXES(BUF_DEF_PUTSTRF_) +#undef BUF_DEF_PUTSTRF_ + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/struct/buf.c b/struct/buf.c index 11b1425..00163bf 100644 --- a/struct/buf.c +++ b/struct/buf.c @@ -53,6 +53,52 @@ void buf_init(buf *b, void *p, size_t sz) b->f = 0; } +/* --- @dbuf_init@ --- * + * + * Arguments: @dbuf *db@ = pointer to a dynamic buffer block + * + * Returns: --- + * + * Use: Initializes a dynamic buffer. The buffer is initially empty, + * and ready for writing. + */ + +void dbuf_init(dbuf *db) +{ + db->_b.base = db->_b.p = db->_b.limit = 0; db->_b.f = BF_ALLOC | BF_WRITE; + db->a = &arena_stdlib; db->sz = 0; +} + +/* --- @dbuf_reset@ --- * + * + * Arguments: @dbuf *db@ = pointer to a buffer block + * + * Returns: --- + * + * Use: Resets a buffer so that it can be written again. + */ + +void dbuf_reset(dbuf *db) +{ + db->_b.p = db->_b.base; db->_b.limit = db->_b.base + db->sz; + db->_b.f = (db->_b.f&~BF_BROKEN) | BF_WRITE; +} + +/* --- @dbuf_destroy@ --- * + * + * Arguments: @dbuf *db@ = pointer to a buffer block + * + * Returns: --- + * + * Use: Release all of the resources held by a dynamic buffer. + */ + +void dbuf_destroy(dbuf *db) +{ + if (db->_b.base) x_free(db->a, db->_b.base); + dbuf_init(db); +} + /* --- @buf_break@ --- * * * Arguments: @buf *b@ = pointer to a buffer block @@ -76,8 +122,8 @@ int buf_break(buf *b) { b->f |= BF_BROKEN; return (-1); } void buf_flip(buf *b) { - b->limit = b->p; - b->p = b->base; + b->limit = b->p; b->p = b->base; + b->f &= ~BF_WRITE; } /* --- @buf_ensure@ --- * @@ -92,6 +138,39 @@ void buf_flip(buf *b) int buf_ensure(buf *b, size_t sz) { return (BENSURE(b, sz)); } +/* --- @buf_tryextend@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer block + * @size_t sz@ = size of data wanted + * + * Returns: Zero if it worked, nonzero if the buffer won't grow. + * + * Use: Extend the buffer so that at least @sz@ bytes are available. + * This only works if the buffer is allocated. + */ + +int buf_tryextend(buf *b, size_t sz) +{ + dbuf *db; + size_t newsz, len; + + if (~b->f&(BF_ALLOC | BF_WRITE)) + { b->f |= BF_BROKEN; return (-1); } + db = (dbuf *)b; + len = BLEN(&db->_b); sz += len; + if (db->sz >= sz) + newsz = db->sz; + else { + newsz = db->sz ? 2*db->sz : 64; + while (newsz < sz) { assert(newsz < ((size_t)-1)/2); newsz *= 2; } + if (!db->_b.base) db->_b.base = x_alloc(db->a, newsz); + else db->_b.base = x_realloc(db->a, db->_b.base, newsz, db->sz); + db->_b.p = db->_b.base + len; db->sz = newsz; + } + db->_b.limit = db->_b.base + newsz; + return (0); +} + /* --- @buf_get@ --- * * * Arguments: @buf *b@ = pointer to a buffer block @@ -315,6 +394,44 @@ void *buf_getmemz(buf *b, size_t *nn) return (buf_get(b, *nn)); } +#ifndef HAVE_UINT64 + +static void *getmem_k64(buf *b, size_t *nn_out, kludge64 k) +{ + kludge64 szmax; + size_t n; + + ASSIGN64(szmax, (size_t)-1); + if (CMP64(k, >, szmax)) { buf_break(b); return (-1); } + n = GET64(size_t, k); *nn_out = n; return (buf_get(b, n)); +} + +void *buf_getmem64(buf *b, size_t *nn) +{ + kludge64 k; + + if (buf_getk64(b, &k)) return (-1); + return (getmem_k64(b, nn, k)); +} + +void *buf_getmem64b(buf *b, size_t *nn) +{ + kludge64 k; + + if (buf_getk64b(b, &k)) return (-1); + return (getmem_k64(b, nn, k)); +} + +void *buf_getmem64l(buf *b, size_t *nn) +{ + kludge64 k; + + if (buf_getk64l(b, &k)) return (-1); + return (getmem_k64(b, nn, k)); +} + +#endif + /* --- @buf_putmem{8,{16,24,32,64}{,l,b},z} --- * * * Arguments: @buf *b@ = pointer to a buffer block @@ -340,6 +457,34 @@ void *buf_getmemz(buf *b, size_t *nn) } DOUINTCONV(BUF_PUTMEM_) +#ifndef HAVE_UINT64 + +void *buf_putmem64(buf *b, const void *p, size_t n) +{ + kludge64 k; + + ASSIGN64(k, n); if (buf_putk64(b, k) || buf_put(b, p, n)) return (-1); + return (0); +} + +void *buf_putmem64b(buf *b, const void *p, size_t n) +{ + kludge64 k; + + ASSIGN64(k, n); if (buf_putk64b(b, k) || buf_put(b, p, n)) return (-1); + return (0); +} + +void *buf_putmem64l(buf *b, const void *p, size_t n) +{ + kludge64 k; + + ASSIGN64(k, n); if (buf_putk64l(b, k) || buf_put(b, p, n)) return (-1); + return (0); +} + +#endif + int buf_putmemz(buf *b, const void *p, size_t n) { octet *q; diff --git a/struct/buf.h b/struct/buf.h index 97dbd10..389d038 100644 --- a/struct/buf.h +++ b/struct/buf.h @@ -34,12 +34,17 @@ /*----- Header files ------------------------------------------------------*/ +#include #include #ifndef MLIB_BITS_H # include "bits.h" #endif +#ifndef MLIB_CONTROL_H +# include "control.h" +#endif + #ifndef MLIB_DSTR_H # include "dstr.h" #endif @@ -62,6 +67,19 @@ typedef struct buf { } buf; #define BF_BROKEN 1u /* Buffer is broken */ +#define BF_ALLOC 2u /* Buffer is dynamic */ +#define BF_WRITE 4u /* Currently writing to buffer */ + +typedef struct dbuf { + buf _b; + arena *a; /* Allocation arena */ + size_t sz; /* Allocated size */ +} dbuf; + +#define DBUF_INIT { { 0, 0, 0, BF_ALLOC | BF_WRITE}, &arena_stdlib, 0 } +#define DBUF_BUF(db) (&(db)->_b) + +extern const struct gprintf_ops buf_printops; /*----- Useful macros -----------------------------------------------------*/ @@ -78,13 +96,31 @@ typedef struct buf { #if GCC_VERSION_P(8, 0) # define BENSURE(b, sz) \ MUFFLE_WARNINGS_EXPR(GCC_WARNING("-Wint-in-bool-context"), \ - (BBAD(b) ? -1 : (sz) > BLEFT(b) ? (b)->f |= BF_BROKEN, -1 : 0)) + (BBAD(b) ? -1 : (sz) > BLEFT(b) ? buf_tryextend(b, sz) : 0)) #else # define BENSURE(b, sz) \ - (BBAD(b) ? -1 : (sz) > BLEFT(b) ? (b)->f |= BF_BROKEN, -1 : 0) + (BBAD(b) ? -1 : (sz) > BLEFT(b) ? buf_tryextend(b, sz) : 0) #endif -#define BUF_DOSUFFIXES(_) DOUINTCONV(_) _(z, z, z) +#define DBBASE(db) BBASE(DBUF_BUF(db)) +#define DBLIM(db) BLIM(DBUF_BUF(db)) +#define DBCUR(db) BCUR(DBUF_BUF(db)) +#define DBSZ(db) BSZ(DBUF_BUF(db)) +#define DBLEN(db) BLEN(DBUF_BUF(db)) +#define DBLEFT(db) BLEFT(DBUF_BUF(db)) +#define DBSTEP(db, sz) BSTEP(DBUF_BUF(db), (sz)) +#define DBBAD(db) BBAD(DBUF_BUF(db)) +#define DBOK(db) BOK(DBUF_BUF(db)) +#define DBENSURE(b, sz) BENSURE(DBUF_BUF(db), (sz)) + +#ifdef HAVE_UINT64 +# define BUF_DOKLUDGESUFFIXES(_) +#else +# define BUF_DOKLUDGESUFFIXES(_) \ + _(64, 64, 64) _(64, 64_B, 64b) _(64, 64_L, 64l) +#endif + +#define BUF_DOSUFFIXES(_) DOUINTCONV(_) BUF_DOKLUDGESUFFIXES(_) _(z, z, z) /*----- Functions provided ------------------------------------------------*/ @@ -101,6 +137,40 @@ typedef struct buf { extern void buf_init(buf */*b*/, void */*p*/, size_t /*sz*/); +/* --- @dbuf_init@ --- * + * + * Arguments: @dbuf *db@ = pointer to a dynamic buffer block + * + * Returns: --- + * + * Use: Initializes a dynamic buffer. The buffer is initially empty, + * and ready for writing. + */ + +extern void dbuf_init(dbuf */*db*/); + +/* --- @dbuf_reset@ --- * + * + * Arguments: @dbuf *db@ = pointer to a buffer block + * + * Returns: --- + * + * Use: Resets a buffer so that it can be written again. + */ + +extern void dbuf_reset(dbuf */*db*/); + +/* --- @dbuf_destroy@ --- * + * + * Arguments: @dbuf *db@ = pointer to a buffer block + * + * Returns: --- + * + * Use: Release all of the resources held by a dynamic buffer. + */ + +extern void dbuf_destroy(dbuf */*db*/); + /* --- @buf_break@ --- * * * Arguments: @buf *b@ = pointer to a buffer block @@ -136,6 +206,19 @@ extern void buf_flip(buf */*b*/); extern int buf_ensure(buf */*b*/, size_t /*sz*/); +/* --- @buf_tryextend@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer block + * @size_t sz@ = size of data wanted + * + * Returns: Zero if it worked, nonzero if the buffer won't grow. + * + * Use: Extend the buffer so that at least @sz@ bytes are available. + * This only works if the buffer is allocated. + */ + +extern int buf_tryextend(buf */*b*/, size_t /*sz*/); + /* --- @buf_get@ --- * * * Arguments: @buf *b@ = pointer to a buffer block @@ -346,6 +429,146 @@ BUF_DOSUFFIXES(BUF_DECL_PUTDSTR_) extern int buf_putstr##w(buf */*b*/, const char */*p*/); BUF_DOSUFFIXES(BUF_DECL_PUTSTR_) +/* --- @buf_putf64{,b,l} --- * + * + * Arguments: @buf *b@ = a buffer to write to + * @double x@ = a number to write + * + * Returns: Zero on success, @-1@ on failure (and the buffer is broken). + * + * On C89, this function can't detect negative zero so these + * will be silently written as positive zero. + * + * This function doesn't distinguish NaNs. Any NaN is written + * as a quiet NaN with all payload bits zero. + * + * A finite value with too large a magnitude to be represented + * is rounded to the appropriate infinity. Other finite values + * are rounded as necessary, in the usual IEEE 754 round-to- + * nearest-or-even way. + */ + +extern int buf_putf64(buf */*b*/, double /*x*/); +extern int buf_putf64b(buf */*b*/, double /*x*/); +extern int buf_putf64l(buf */*b*/, double /*x*/); + +/* --- @buf_getf64{,b,l} --- * + * + * Arguments: @buf *b@ = a buffer to read from + * @double *x_out@ = where to put the result + * + * Returns: Zero on success, @-1@ on failure (and the buffer is broken). + * + * If the system supports NaNs, then any encoded NaN is returned + * as the value of @NAN@ in @@; otherwise, this function + * reports failure. + * + * In general, values are rounded to the nearest available + * value, in the way that the system usually rounds. If the + * system doesn't support infinities, then any encoded infinity + * is reported as the largest-possible-magnitude finite value + * instead. + */ + +extern int buf_getf64(buf */*b*/, double *x_/*out*/); +extern int buf_getf64b(buf */*b*/, double *x_/*out*/); +extern int buf_getf64l(buf */*b*/, double *x_/*out*/); + +#define BUF_ENCLOSETAG(tag, buf, mk, check, poke, lensz) \ + MC_BEFORE(tag##__save, \ + { (mk) = BLEN(buf); \ + if (!BENSURE(buf, lensz)) (buf)->p += (lensz); }) \ + MC_AFTER(tag##__poke, \ + { size_t _delta = BLEN(buf) - (mk) + (lensz); \ + assert(check); \ + if (BOK(buf)) poke((buf)->base + (mk), _delta); }) + +#define BUF_ENCLOSEZTAG(tag, buf) \ + MC_AFTER(tag##__zero, { buf_putbyte(buf, 0); }) + +#define BUF_ENCLOSENATIVETAG(tag, buf, mk, W) \ + BUF_ENCLOSETAG(tag, buf, mk, (_delta <= MASK##W), STORE##W, SZ_##W) + +#define BUF_STORESZK64(p, sz) \ + do { kludge64 _k; ASSIGN64(_k, (sz)); STORE64_((p), _k); } while (0) +#define BUF_STORESZK64_B(p, sz) \ + do { kludge64 _k; ASSIGN64(_k, (sz)); STORE64_B_((p), _k); } while (0) +#define BUF_STORESZK64_L(p, sz) \ + do { kludge64 _k; ASSIGN64(_k, (sz)); STORE64_L_((p), _k); } while (0) +#define BUF_ENCLOSEK64TAG(tag, buf, mk, W) \ + BUF_ENCLOSE(tag, buf, mk, 1, BUF_STORESZK##W, 8) + +#define BUF_ENCLOSE8(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 8) +#define BUF_ENCLOSE16(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 16) +#define BUF_ENCLOSE16_B(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 16_B) +#define BUF_ENCLOSE16_L(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 16_L) +#define BUF_ENCLOSE24(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 24) +#define BUF_ENCLOSE24_B(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 24_B) +#define BUF_ENCLOSE24_L(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 24_L) +#define BUF_ENCLOSE32(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 32) +#define BUF_ENCLOSE32_B(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 32_B) +#define BUF_ENCLOSE32_L(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 32_L) +#ifdef HAVE_UINT64 +# define BUF_ENCLOSE64(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 64) +# define BUF_ENCLOSE64_B(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 64_B) +# define BUF_ENCLOSE64_L(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 64_L) +#else +# define BUF_ENCLOSE64(buf, mk) BUF_ENCLOSEK64TAG(encl, buf, mk, 64) +# define BUF_ENCLOSE64_B(buf, mk) BUF_ENCLOSEK64TAG(encl, buf, mk, 64_B) +# define BUF_ENCLOSE64_L(buf, mk) BUF_ENCLOSEK64TAG(encl, buf, mk, 64_L) +#endif +#define BUF_ENCLOSEZ(buf) BUF_ENCLOSEZTAG(encl, buf) + +/* --- @buf_vputstrf@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer + * @const char *p@ = pointer to @printf@-style format string + * @va_list *ap@ = argument handle + * + * Returns: The number of characters written to the string, or @-1@ on + * failure. + * + * Use: As for @buf_putstrf@, but may be used as a back-end to user- + * supplied functions with @printf@-style interfaces. + */ + +extern int buf_vputstrf(buf */*b*/, const char */*p*/, va_list */*ap*/); + +/* --- @buf_putstrf@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer + * @const char *p@ = pointer to @printf@-style format string + * @...@ = argument handle + * + * Returns: The number of characters written to the string, or @-1@ on + * failure. + * + * Use: Format a string to a buffer. The resulting output is not + * null-terminated. + */ + +extern PRINTF_LIKE(2, 3) int buf_putstrf(buf */*b*/, const char */*p*/, ...); + +/* --- @buf_{,v}putstrf{8,{16,24,32,64}{,b,l},z}@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer + * @const char *p@ = pointer to @printf@-style format string + * @va_list *ap@ = argument handle + * + * Returns: The number of characters written to the string, or @-1@ on + * failure. + * + * Use: As for @buf_putstr@, but using a format string. + */ + +#define BUF_DECL_PUTSTRF_(n, W, w) \ + extern int buf_vputstrf##w(buf */*b*/, \ + const char */*p*/, va_list */*ap*/); \ + extern PRINTF_LIKE(2, 3) \ + int buf_putstrf##w(buf */*b*/, const char */*p*/, ...); +BUF_DOSUFFIXES(BUF_DECL_PUTSTRF_) +#undef BUF_DECL_PUTSTRF_ + /*----- That's all, folks -------------------------------------------------*/ #ifdef __cplusplus diff --git a/struct/dstr-putf.c b/struct/dstr-putf.c index 270f9a3..aa492e0 100644 --- a/struct/dstr-putf.c +++ b/struct/dstr-putf.c @@ -27,135 +27,12 @@ /*----- Header files ------------------------------------------------------*/ -#include "config.h" - -#include -#include -#include #include +#include #include -#include -#include - -#ifdef HAVE_FLOAT_H -# include -#endif -#ifdef HAVE_STDINT_H -# include -#endif - -#include "darray.h" #include "dstr.h" -#include "macros.h" - -/*----- Tunable constants -------------------------------------------------*/ - -/* - * For each format specifier, at least @PUTFSTEP@ bytes are ensured before - * writing the formatted result. - */ - -#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); +#include "gprintf.h" /*----- Main code ---------------------------------------------------------*/ @@ -171,375 +48,33 @@ DA_DECL(fmtspec_v, fmtspec); * 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); - } +static int putch(void *out, int ch) + { dstr *d = out; DPUTC(d, ch); return (0); } +static int putm(void *out, const char *p, size_t sz) + { dstr *d = out; DPUTM(d, p, sz); return (0); } - 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) +static int nputf(void *out, size_t maxsz, const char *p, ...) { - size_t n = d->len; - 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 represents 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) { - f = 0; - DA_ENSURE(&sv, 1); - fs = &DA(&sv)[DA_LEN(&sv)]; - DA_UNSAFE_EXTEND(&sv, 1); - - /* --- Find the end of this literal portion --- */ - - 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; } - - /* --- 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++; - } - - /* --- Pick up the field width --- */ - - done_flags: - i = 0; - while (ISDIGIT(*p)) i = 10*i + *p++ - '0'; - - /* --- Snag: this might have been an argument position indicator --- */ - - if (i && *p == '$' && (!f || f == f_zero)) { - f |= f_posarg; - fs->arg = i - 1; - p++; - goto flags; - } - - /* --- 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. - */ - - if (i) { - f |= f_wd; - fs->wd = i; - } else if (*p == '*') { - p++; - if (!ISDIGIT(*p)) - i = anext++; - else { - i = *p++ - '0'; - while (ISDIGIT(*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(*p)) { - i = *p++ - '0'; - while (ISDIGIT(*p)) i = 10*i + *p++ - '0'; - fs->prec = i; - } else if (*p != '*') - fs->prec = 0; - else { - p++; - if (!ISDIGIT(*p)) - i = anext++; - else { - i = *p++ - '0'; - while (ISDIGIT(*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; - } - } - - /* --- 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; ) - } - - /* --- 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; - - /* --- 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; - } - - DRESET(&dd); - 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 --- */ - - 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 --- */ + dstr *d = out; + va_list ap; + int n; - DENSURE(d, sz + 1); - switch (fs->fmt) { + va_start(ap, p); + DENSURE(d, maxsz + 1); #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; + n = vsnprintf(d->buf + d->len, maxsz + 1, p, ap); #else -# define CASE(code, ty) case fmt_##code: \ - i = sprintf(d->buf + d->len, dd.buf, fa[fs->arg].u.code); \ - break; + n = vsprintf(d->buf + d->len, p, ap); #endif - OUTPUT_FMTTYPES(CASE) -#undef CASE - default: abort(); - } - assert(0 <= i && i <= sz); d->len += i; - } + assert(0 <= n && n <= maxsz); + va_end(ap); d->len += n; return (n); +} - /* --- We're done --- */ +const struct gprintf_ops dstr_printops = + { putch, putm, nputf }; - DPUTZ(d); - DDESTROY(&dd); - DA_DESTROY(&av); - DA_DESTROY(&sv); - return (d->len - n); -} +int dstr_vputf(dstr *d, const char *p, va_list *ap) + { int n = vgprintf(&dstr_printops, d, p, ap); DPUTZ(d); return (n); } /* --- @dstr_putf@ --- * * diff --git a/struct/dstr.h b/struct/dstr.h index 1c43cb7..fedc48c 100644 --- a/struct/dstr.h +++ b/struct/dstr.h @@ -47,6 +47,7 @@ #include #include #include +#include #ifndef MLIB_ALLOC_H # include "alloc.h" @@ -71,6 +72,8 @@ typedef struct dstr { #define DSTR_INIT { 0, 0, 0, &arena_stdlib } /* How to initialize one */ +extern const struct gprintf_ops dstr_printops; + /*----- Functions provided ------------------------------------------------*/ /* --- @dstr_create@ --- * diff --git a/t/template-canonify b/t/template-canonify new file mode 100755 index 0000000..a82acde --- /dev/null +++ b/t/template-canonify @@ -0,0 +1,82 @@ +#! /usr/bin/python + +import os as OS +import re as RX +import sys as SYS +if SYS.version_info >= (3,): + from io import StringIO + xrange = range +else: + from cStringIO import StringIO + +PROG = OS.path.basename(SYS.argv[0]) +USAGE = "usage: %s TEMPLATE INPUT TPLOUT OUTPUT" % PROG +## +## The TEMPLATE contains text containing placeholders of the form +## +## ={TAG:PAT} +## +## TAG is some string, and PAT is a Python regular expression. The TEMPLATE +## matches the INPUT if there is some way to replace each placeholder by a +## string matching its PAT such that the two are equal. +## +## The TPLOUT file is a copy of the TEMPLATE file, with each placeholder +## replaced by ={TAG}. If the TEMPLATE matches the INPUT, then the OUTPUT +## equals TPLOUT; otherwise, the program tries to replace as many portions of +## the INPUT which match placeholder PATs as it can, but it doesn't currently +## do an especially good job. + +if len(SYS.argv) != 5: + SYS.stderr.write("%s\n" % USAGE) + SYS.exit(2) +_, tplfn, infn, toutfn, outfn = SYS.argv + +tfin = open(tplfn, "r"); tfout = open(toutfn, "w") +fin = open(infn, "r"); fout = open(outfn, "w") + +R_PLCH = RX.compile(r""" + = \{ ([^}:]+) : ((?: [^\\}]+ | \\.)*) \} +""", RX.S | RX.X) + +while True: + t = tfin.readline(); l = fin.readline() + if not (t or l): break + + lit = []; tag = []; pat = []; pos = 0 + for m in R_PLCH.finditer(t): + lit.append(t[pos:m.start()]) + tag.append(m.group(1)) + pat.append(m.group(2)) + pos = m.end() + lit.append(t[pos:]) + n = len(pat) + + skelsio = StringIO(); gensio = StringIO(); xctsio = StringIO() + gensio.write("^"); xctsio.write("^") + for i in xrange(n): + q = RX.escape(lit[i]) + skelsio.write("%s={%s}" % (lit[i], tag[i])) + xctsio.write(q + pat[i]) + gensio.write(q + "(.*)") + q = lit[n] + skelsio.write(lit[n]); skel = skelsio.getvalue() + xctsio.write(q); xctsio.write("$"); xct = xctsio.getvalue() + gensio.write(q); gensio.write("$"); gen = gensio.getvalue() + + tfout.write(skel) + if not l: continue + + if RX.match(xct, l): fout.write(skel); continue + + m = RX.match(gen, l) + if not m: fout.write(l); continue + sio = StringIO() + for i in xrange(n): + sio.write(lit[i]) + if RX.match("^%s$" % pat[i], m.group(i + 1)): sio.write("={%s}" % tag[i]) + else: sio.write("!{%s:%s}" % (tag[i], m.group(i + 1))) + sio.write(lit[n]) + fout.write(sio.getvalue()) + +tfin.close(); tfout.close() +fin.close(); fout.close() diff --git a/test/Makefile.am b/test/Makefile.am index cf1a198..2f7a041 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -43,10 +43,12 @@ LIBMANS += testrig.3 ## New `tvec' testing framework. pkginclude_HEADERS += tvec.h +libtest_la_SOURCES += tvec-bench.c libtest_la_SOURCES += tvec-core.c libtest_la_SOURCES += tvec-output.c libtest_la_SOURCES += tvec-types.c libtest_la_SOURCES += tvec-main.c +#LIBMANS += tvec.3 check_PROGRAMS += t/tvec.t t_tvec_t_SOURCES = t/tvec-test.c diff --git a/test/bench.c b/test/bench.c index df8c4ef..d1a00ea 100644 --- a/test/bench.c +++ b/test/bench.c @@ -459,7 +459,7 @@ end: } int bench_measure(struct bench_timing *t_out, struct bench_state *b, - bench_fn *fn, void *p) + double base, bench_fn *fn, void *p) { struct bench_timer *tm = b->tm; struct bench_time t0, t1; @@ -485,7 +485,7 @@ int bench_measure(struct bench_timing *t_out, struct bench_state *b, else debug(" %g s (%g cy) per op; %g ops/s", t_out->t/n, t_out->cy/n, n/t_out->t); - t_out->n = n; return (0); + t_out->n = n*base; return (0); } /*----- That's all, folks -------------------------------------------------*/ diff --git a/test/bench.h b/test/bench.h index f9472cd..2dbce0f 100644 --- a/test/bench.h +++ b/test/bench.h @@ -51,8 +51,7 @@ struct bench_time { struct bench_timing { unsigned f; - unsigned long n; - double t, cy; + double n, t, cy; }; struct bench_timer { const struct bench_timerops *ops; }; @@ -75,15 +74,16 @@ typedef void bench_fn(unsigned long /*n*/, void */*p*/); extern struct bench_timer *bench_createtimer(void); -extern void bench_init(struct bench_state *b, struct bench_timer *tm); +extern void bench_init(struct bench_state */*b*/, + struct bench_timer */*tm*/); -extern void bench_destroy(struct bench_state *b); +extern void bench_destroy(struct bench_state */*b*/); extern int bench_calibrate(struct bench_state */*b*/); extern int bench_measure(struct bench_timing */*t_out*/, struct bench_state */*b*/, - bench_fn */*fn*/, void */*p*/); + double /*base*/, bench_fn */*fn*/, void */*p*/); /*----- That's all, folks -------------------------------------------------*/ diff --git a/test/t/tvec-test.c b/test/t/tvec-test.c index 8b27f7f..f2ed3da 100644 --- a/test/t/tvec-test.c +++ b/test/t/tvec-test.c @@ -31,27 +31,6 @@ /*----- Register definitions ----------------------------------------------*/ -enum { - /* Standard outputs. */ - RRC, /* return code from deserialize */ - - /* Output registers, one for each register type. */ - RI, RU, RIE, RUE, RPE, RF, RSTR, RBY, RBUF, - - /* Additional diagnostic outputs. */ - RSER, /* serialized data */ - - NROUT, - - /* Some additional inputs. */ - RSAB = NROUT, /* which register to sabotage */ - - NREG, - - /* Single register for copy tests. */ - RV = 0 -}; - static const struct tvec_iassoc ienum_assocs[] = { { "less", -1 }, { "equal", 0 }, @@ -66,6 +45,13 @@ static const struct tvec_uassoc uenum_assocs[] = { { 0 } }; +static const struct tvec_fassoc fenum_assocs[] = { + { "e", 2.718281828459045 }, + { "pi", 3.141592653589793 }, + { "tau", 6.283185307179586 }, + { 0 } +}; + static const struct tvec_passoc penum_assocs[] = { { "alice", &uenum_assocs[0] }, { "bob", &uenum_assocs[1] }, @@ -73,19 +59,28 @@ static const struct tvec_passoc penum_assocs[] = { { 0 } }; -#if __STDC_VERSION__ >= 199901 +#if __STDC_VERSION__x >= 199901 # define DSGINIT(x) x #else # define DSGINIT(x) #endif -static DSGINIT(const) struct tvec_enuminfo - ienum_info = { "order", TVMISC_INT, - DSGINIT({ .i = { ienum_assocs COMMA &tvrange_i16 } }) }, - uenum_info = { "fruit", TVMISC_UINT, - DSGINIT({ .u = { uenum_assocs COMMA &tvrange_u16 } }) }, - penum_info = { "player", TVMISC_PTR, - DSGINIT({ .p = { penum_assocs } }) }; +static const struct tvec_floatinfo fenum_fltinfo = + { TVFF_ABSDELTA, -10, +10, 1e-3 }; + +#define DEFENUM(tag, ty, slot) \ + static const struct tvec_##slot##enuminfo slot##enum_info = \ + { { slot##enum_NAME, TVMISC_##tag }, slot##enum_assocs slot##enum_ARGS }; +#define ienum_NAME "order" +#define ienum_ARGS , &tvrange_i16 +#define uenum_NAME "fruit" +#define uenum_ARGS , &tvrange_u16 +#define fenum_NAME "const" +#define fenum_ARGS , &fenum_fltinfo +#define penum_NAME "actor" +#define penum_ARGS +TVEC_MISCSLOTS(DEFENUM) +#undef DEFENUM static const struct tvec_flag attr_flags[] = { { "black-fg", 0x07, 0x00 }, @@ -116,37 +111,70 @@ static const struct tvec_flag attr_flags[] = { static const struct tvec_flaginfo attr_info = { "attr", attr_flags, &tvrange_u16 }; +static const struct tvec_floatinfo fltish_info = + { TVFF_RELDELTA, -1.0, +1.0, 1e-6 }; + static const struct tvec_urange range_32 = { 0, 31 }; #define TYPEREGS(_) \ _(int, RI, int, p, &tvrange_i16) \ _(uint, RU, uint, p, &tvrange_u16) \ + _(float, RFP, float, p, 0) \ + _(fltish, RFISH, float, p, &fltish_info) \ + _(char, RCH, char, p, 0) \ _(ienum, RIE, enum, p, &ienum_info) \ _(uenum, RUE, enum, p, &uenum_info) \ + _(fenum, RFE, enum, p, &fenum_info) \ _(penum, RPE, enum, p, &penum_info) \ _(flags, RF, flags, p, &attr_info) \ _(string, RSTR, string, p, &range_32) \ _(bytes, RBY, bytes, p, &tvrange_byte) \ _(buffer, RBUF, buffer, p, &tvrange_u16) +enum { + /* Output registers, one for each register type. */ +#define DEFREG(name, i, ty, argslot, argval) i, + TYPEREGS(DEFREG) +#undef DEFREG + NSER, + + /* Standard outputs. */ + RRC = NSER, /* return code from deserialize */ + + /* Additional diagnostic outputs. */ + RSER, /* serialized data */ + + NROUT, + + /* Some additional inputs. */ + RSAB = NROUT, /* which register to sabotage */ + + NREG, + + /* Single register for copy tests. */ + RV = 0 +}; + /*----- Serialization test ------------------------------------------------*/ struct test_context { struct tvec_state *tv; }; -static int capture_state_and_run(struct tvec_state *tv) -{ - struct test_context tctx; +static int capture_setup(struct tvec_state *tv, + const struct tvec_env *env, void *pctx, void *ctx) + { struct test_context *tctx = ctx; tctx->tv = tv; return (0); } - tctx.tv = tv; tv->test->fn(tv->in, tv->out, &tctx); +static void capture_run(struct tvec_state *tv, tvec_testfn *fn, void *ctx) +{ if (!(tv->in[RRC].f&TVRF_LIVE)) { - tv->in[RRC].v.i = 0; - tv->in[RRC].f |= TVRF_LIVE; tv->out[RRC].f |= TVRF_LIVE; + tv->in[RRC].f |= TVRF_LIVE; tv->in[RRC].v.i = 0; + tv->out[RRC].f |= TVRF_LIVE; } - tvec_check(tv, 0); - return (0); + fn(tv->in, tv->out, ctx); tvec_check(tv, 0); } +static const struct tvec_env capture_testenv = + { sizeof(struct test_context), capture_setup, 0, 0, capture_run, 0, 0 }; static void test_serialization (const struct tvec_reg *in, struct tvec_reg *out, void *ctx) @@ -155,17 +183,21 @@ static void test_serialization struct tvec_state *tv = tctx->tv; const struct tvec_regdef *rd; union tvec_regval *rv; - void *p; size_t sz; + dbuf b = DBUF_INIT; - if (tvec_serialize(tv->in, tv->test->regs, - NROUT, sizeof(struct tvec_reg), &p, &sz)) - { out[RRC].v.i = -1; return; } + if (tvec_serialize(tv->in, DBUF_BUF(&b), tv->test->regs, + NSER, sizeof(struct tvec_reg))) + { out[NSER].v.i = -1; goto end; } + tvec_allocbytes(&out[RSER].v, DBLEN(&b)); + memcpy(out[RSER].v.bytes.p, DBBASE(&b), DBLEN(&b)); out[RSER].f |= TVRF_LIVE; - out[RSER].v.bytes.p = p; out[RSER].v.bytes.sz = sz; + buf_flip(DBUF_BUF(&b)); - if (tvec_deserialize(tv->out, tv->test->regs, - NROUT, sizeof(struct tvec_reg), p, sz)) - { out[RRC].v.i = -1; return; } + if (tvec_deserialize(tv->out, DBUF_BUF(&b), tv->test->regs, + NSER, sizeof(struct tvec_reg))) + { out[RRC].v.i = -2; goto end; } + if (BLEFT(&b._b)) + { out[RRC].v.i = -3; goto end; } if (in[RSAB].f&TVRF_LIVE) { for (rd = tv->test->regs; rd->name; rd++) @@ -173,12 +205,19 @@ static void test_serialization rv = &out[rd->i].v; if (rd->ty == &tvty_int || (rd->ty == &tvty_enum && - ((struct tvec_enuminfo *)rd->arg.p)->mv == TVMISC_INT)) + ((const struct tvec_enuminfo *)rd->arg.p)->mv == TVMISC_INT)) rv->i ^= 1; else if (rd->ty == &tvty_uint || rd->ty == &tvty_flags || (rd->ty == &tvty_enum && - ((struct tvec_enuminfo *)rd->arg.p)->mv == TVMISC_INT)) + ((const struct tvec_enuminfo *)rd->arg.p)->mv == + TVMISC_INT)) rv->u ^= 1; + else if (rd->ty == &tvty_enum && + ((const struct tvec_enuminfo *)rd->arg.p)->mv == TVMISC_PTR) + rv->p = rv->p + ? 0 + : (/*unconst*/ void *) + ((const struct tvec_penuminfo *)rd->arg.p)->av[0].p; else if (rd->ty == &tvty_string) { if (rv->str.sz) rv->str.p[0] ^= 1; } else if (rd->ty == &tvty_bytes) @@ -187,9 +226,11 @@ static void test_serialization } out[RRC].v.i = 0; +end: + dbuf_destroy(&b); } -DSGINIT(static) const struct tvec_regdef test_regs[] = { +static DSGINIT(const) struct tvec_regdef test_regs[] = { #define DEFREG(name, i, ty, argslot, argval) \ { #name, i, &tvty_##ty, TVRF_OPT, \ DSGINIT({ .argslot = argval }) }, @@ -226,8 +267,12 @@ static void test_copy_bytes #define test_copy_uint test_copy_simple #define test_copy_ienum test_copy_simple #define test_copy_uenum test_copy_simple +#define test_copy_fenum test_copy_simple #define test_copy_penum test_copy_simple +#define test_copy_char test_copy_simple #define test_copy_flags test_copy_simple +#define test_copy_float test_copy_simple +#define test_copy_fltish test_copy_simple #define test_copy_buffer test_copy_bytes #define SINGLEREG(name, i, ty, argslot, argval) \ @@ -238,14 +283,63 @@ static void test_copy_bytes TYPEREGS(SINGLEREG) #undef SINGLEREG +struct singlectx { + unsigned f; +#define SF_SHOW 1u +}; + +static int single_setup(struct tvec_state *tv, const struct tvec_env *env, + void *pctx, void *ctx) + { struct singlectx *s = ctx; s->f = 0; return (0); } + +static int single_set(struct tvec_state *tv, const char *name, + const struct tvec_env *env, void *ctx) +{ + struct singlectx *s = ctx; + union tvec_regval rv; + static const struct tvec_regdef rd = + { "@show", -1, &tvty_enum, 0, { &tvenum_bool } }; + + if (STRCMP(name, ==, "@show")) { + if (tvty_enum.parse(&rv, &rd, tv)) return (-1); + if (s) { + if (rv.i) s->f |= SF_SHOW; + else s->f &= ~SF_SHOW; + } + return (1); + } else + return (0); +} + +static void single_run(struct tvec_state *tv, tvec_testfn *fn, void *ctx) +{ + struct singlectx *s = ctx; + unsigned f = s->f; + + fn(tv->in, tv->out, 0); + if (tvec_checkregs(tv)) { tvec_fail(tv, 0); f |= SF_SHOW; } + if (f&SF_SHOW) tvec_mismatch(tv, TVMF_IN | TVMF_OUT); +} + +static void single_after(struct tvec_state *tv, void *ctx) + { struct singlectx *s = ctx; s->f = 0; } + +static const struct tvec_env single_testenv = + { sizeof(struct singlectx), + single_setup, + single_set, + 0, + single_run, + single_after, + 0 }; + /*----- Front end ---------------------------------------------------------*/ static const struct tvec_test tests[] = { - { "types", test_regs, 0, capture_state_and_run, - test_serialization }, + { "types", test_regs, &capture_testenv, test_serialization }, #define DEFCOPY(name, i, ty, argslot, argval) \ - { #name, name##_regs, 0, tvec_runtest, test_copy_##name }, + { #name, name##_regs, &single_testenv, test_copy_##name }, TYPEREGS(DEFCOPY) #undef DEFCOPY @@ -259,12 +353,9 @@ static const struct tvec_info testinfo = { int main(int argc, char *argv[]) { -#if __STDC_VERSION__ < 199901 -# define POKE(tag, ty, slot) \ - slot##enum_info.u.slot.av = slot##enum_assocs; \ - TVEC_MISCSLOTS(POKE) -# undef POKE +#if __STDC_VERSION__x < 199901 # define POKE(name, i, ty, argslot, argval) \ + test_regs[i].arg.argslot = argval; \ name##_regs->arg.argslot = argval; TYPEREGS(POKE) # undef POKE diff --git a/test/tests.at b/test/tests.at index 7088199..30c898a 100644 --- a/test/tests.at +++ b/test/tests.at @@ -25,108 +25,182 @@ ### MA 02111-1307, USA. ###-------------------------------------------------------------------------- -### tvec +### Preliminaries. -dnl test_filter(CMD, SED, RC, STDOUT, STDERR) -m4_define([test_filter], [ -AT_CHECK([$1], [$3], [stdout-nolog], [stderr-nolog]) -##mv stdout rawout; mv stderr rawerr -AT_CHECK([sed "AS_ESCAPE([$2])" stdout], [0], [$4]) -AT_CHECK([sed "AS_ESCAPE([$2])" stderr], [0], [$5])]) +dnl padding_string(STRING, N, [PAD]) +m4_define([padding_string], +[m4_if([m4_expr([m4_len([$1]) > $2])], [1], [], +[m4_for([i], m4_len([$1]), [($2) - 1], [1], [m4_default([$3], [ ])])])]) -dnl mismatch_filter -m4_define([mismatch_filter], - [s/\(@%:@<@<:@0-9a-zA-Z_-@:>@*\) @<:@^>@:>@*>/\1 ...>/g]) +dnl left_pad(STRING, N, [PAD]) +dnl right_pad(STRING, N, [PAD]) +m4_define([left_pad], [padding_string([$1], [$2], [$3])$1]) +m4_define([right_pad], [$1[]padding_string([$1], [$2], [$3])]) + +dnl check_template(CMD, RC, STDOUT, STDERR) +m4_define([check_template], [ +AT_CHECK([$1], [$2], [stdout], [stderr-nolog]) +AT_DATA([expout.tpl], [$3]) +$PYTHON $abs_srcdir/template-canonify expout.tpl stdout expout stdout.found +AT_DATA([experr.tpl], [$4]) +$PYTHON $abs_srcdir/template-canonify experr.tpl stderr experr stderr.found +AT_CHECK([cat stdout.found; cat stderr.found >&2], [0], [expout], [experr])]) dnl test_parse(TY, IN, OUT) m4_define([test_parse], [ AT_DATA([tv], [;;; -*-conf-*- -[[$1]] +@<:@$1@:>@ $1 = $2 -@status = ? +@show = t ]) -test_filter([BUILDDIR/t/tvec.t -fh tv], [mismatch_filter], [1], -[tv:3: `$1' FAILED - actual status = `.' - expected status = `?' - matched $1 = $3 -$1: 1/1 FAILED -FAILED 1 out of 1 test in 1 out of 1 group +check_template([BUILDDIR/t/tvec.t -fh tv], [0], +[left_pad([matched $1], [17]) = $3 +$1: ok +PASSED all 1 test in 1 group ])]) dnl test_parserr(TY, IN, LNO, ERR) m4_define([test_parserr], [ AT_DATA([tv], [;;; -*-conf-*- -[[$1]] +@<:@$1@:>@ $1 = $2 -@status = ? ]) -AT_CHECK([BUILDDIR/t/tvec.t -fh tv], [2], [], +check_template([BUILDDIR/t/tvec.t -fh tv], [2], +[tv:$3: $4 +tv:={N:\d+}: required register `$1' not set in test `$1' +$1: skipped: no tests to run +PASSED 0 tests in 0 groups (1 skipped) +ERRORS found in input; tests may not have run correctly +], [tvec.t: tv:$3: $4 +tvec.t: tv:={N:\d+}: required register `$1' not set in test `$1' ])]) +###-------------------------------------------------------------------------- AT_SETUP(tvec type-int) -test_parse([int], [4], [4 ; = 0x04]) -test_parse([int], [ 17; comment], [17 ; = 0x11]) + +test_parse([int], [4], [4 ; = 0x04 = '\x04']) +test_parse([int], [ 17; comment], [17 ; = 0x11 = '\x11']) + +test_parse([int], [0x234], [564 ; = 0x0234]) +test_parse([int], [033], [27 ; = 0x1b = '\e']) + +test_parse([int], [ +192], [192 ; = 0xc0 = '\xc0']) +test_parse([int], [ -192], [-192 ; = -0xc0]) + test_parserr([int], [17 : badness], [3], [syntax error: expected end-of-line but found `:']) test_parserr([int], [17: badness], [3], [syntax error: expected end-of-line but found `:']) -test_parse([int], [0x234], [564 ; = 0x0234]) -test_parse([int], [033], [27 ; = 0x1b]) -test_parse([int], [ +192], [192 ; = 0xc0]) -test_parse([int], [ -192], [-192 ; = -0xc0]) + test_parserr([int], [xyzzy], [3], [syntax error: expected signed integer but found `x']) test_parserr([int], [-splat], [3], [syntax error: expected signed integer but found `s']) + test_parserr([int], [0xq], [3], [syntax error: expected end-of-line but found `x']) test_parserr([int], [0x], [3], [syntax error: expected end-of-line but found `x']) + test_parserr([int], [], [3], - [syntax error: expected signed integer but found ]) + [syntax error: expected signed integer but found @%:@]) + test_parserr([int], [123456], [3], - [integer 123456 out of range (must be in [[-32768 .. 32767]])]) + [integer 123456 out of range (must be in @<:@-32768 .. 32767@:>@)]) + AT_CLEANUP +###-------------------------------------------------------------------------- AT_SETUP(tvec type-uint) -test_parse([uint], [4], [4 ; = 0x04]) -test_parse([uint], [ 17; comment], [17 ; = 0x11]) + +test_parse([uint], [4], [4 ; = 0x04 = '\x04']) +test_parse([uint], [ 17; comment], [17 ; = 0x11 = '\x11']) + +test_parse([uint], [0x234], [564 ; = 0x0234]) +test_parse([uint], [033], [27 ; = 0x1b = '\e']) + test_parserr([uint], [17 : badness], [3], [syntax error: expected end-of-line but found `:']) test_parserr([uint], [17: badness], [3], [syntax error: expected end-of-line but found `:']) -test_parse([uint], [0x234], [564 ; = 0x0234]) -test_parse([uint], [033], [27 ; = 0x1b]) + test_parserr([uint], [ +192], [3], [syntax error: expected unsigned integer but found `+']) test_parserr([uint], [ -192], [3], [syntax error: expected unsigned integer but found `-']) + test_parserr([uint], [xyzzy], [3], [syntax error: expected unsigned integer but found `x']) + test_parserr([uint], [0xq], [3], [syntax error: expected end-of-line but found `x']) test_parserr([uint], [0x], [3], [syntax error: expected end-of-line but found `x']) + test_parserr([uint], [], [3], - [syntax error: expected unsigned integer but found ]) + [syntax error: expected unsigned integer but found @%:@]) + test_parserr([uint], [123456], [3], - [integer 123456 out of range (must be in [[0 .. 65535]])]) + [integer 123456 out of range (must be in @<:@0 .. 65535@:>@)]) + AT_CLEANUP +###-------------------------------------------------------------------------- +AT_SETUP([tvec type-float]) + +test_parse([float], [1.234], [1.234]) + +AT_CLEANUP + +###-------------------------------------------------------------------------- AT_SETUP([tvec type-enum]) -test_parse([ienum], [less], [less ; = -1 = -0x01]) -test_parse([ienum], [+1], [greater ; = 1 = 0x01]) -test_parse([ienum], [17], [17 ; = 0x11]) -test_parse([uenum], [banana], [banana ; = 1 = 0x01]) -test_parse([uenum], [clementine], [clementine ; = 2 = 0x02]) -test_parse([uenum], [17], [17 ; = 0x11]) -test_parse([penum], [carol], [carol ; = @%:@]) -test_parse([penum], [alice], [alice ; = @%:@]) + +test_parse([ienum], [less], [less ; = -1 = -0x01 = @%:@eof]) +test_parse([ienum], [+1], [greater ; = 1 = 0x01 = '\x01']) +test_parse([ienum], [17], [17 ; = 0x11 = '\x11']) + +test_parse([uenum], [banana], [banana ; = 1 = 0x01 = '\x01']) +test_parse([uenum], [clementine], [clementine ; = 2 = 0x02 = '\x02']) +test_parse([uenum], [17], [17 ; = 0x11 = '\x11']) + +test_parse([penum], [carol], [carol ; = @%:@@:>@*}>]) +test_parse([penum], [alice], [alice ; = @%:@@:>@*}>]) test_parse([penum], [@%:@nil], [@%:@nil]) + +AT_CLEANUP + +###-------------------------------------------------------------------------- +AT_SETUP([tvec serialize]) + +AT_DATA([tv], +[@<:@types@:>@ + +int = -2 +uint = 7 +float = 6.28 +fltish = 0.1 +char = x +ienum = greater +uenum = banana +fenum = tau +penum = alice +flags = red-fg | white-bg | bright +string = "Hello, world!" +bytes = + 2923be84 e16cd6ae 529049f1 f1bbe9eb + b3a6db3c 870c3e99 245e0d1c 06b747de + b3124dc8 43bb8ba6 1f035a7d 0938251f + 5dd4cbfc 96f5453b 130d890a 1cdbae32 + 209a50ee 407836fd 124932f6 9e7d49dc + ad4f14f2 444066d0 6bc430b7 323ba122 + f622919d e18b1fda b0ca9902 b9729d49 + 2c807ec5 99d5e980 b2eac9cc 53bf67d6 +]) +AT_CHECK([BUILDDIR/t/tvec.t -fh tv], [0], [ignore]) + AT_CLEANUP ###----- That's all, folks -------------------------------------------------- diff --git a/test/tvec-bench.c b/test/tvec-bench.c new file mode 100644 index 0000000..ab21b9c --- /dev/null +++ b/test/tvec-bench.c @@ -0,0 +1,275 @@ +/* -*-c-*- + * + * Benchmarking in the test-vector framework + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "bench.h" +#include "tvec.h" + +/*----- Data structures ---------------------------------------------------*/ + +struct benchrun { + struct tvec_state *tv; + const struct tvec_env *env; + void *ctx; + unsigned long *n; + const struct tvec_reg *in; struct tvec_reg *out; + tvec_testfn *fn; +}; + +/*----- Global variables --------------------------------------------------*/ + +struct bench_state *tvec_benchstate; + +/*----- Benchmarking ------------------------------------------------------*/ + +static void normalize(double *x_inout, const char **unit_out, double scale) +{ + static const char + *const nothing = "", + *const big[] = { "k", "M", "G", "T", "P", "E", 0 }, + *const little[] = { "m", "µ", "n", "p", "f", "a", 0 }; + const char *const *u; + double x = *x_inout; + + if (x < 1) + for (u = little, x *= scale; x < 1 && u[1]; u++, x *= scale); + else if (x >= scale) + for (u = big, x /= scale; x >= scale && u[1]; u++, x /= scale); + else + u = ¬hing; + + *x_inout = x; *unit_out = *u; +} + +int tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, + void *pctx, void *ctx) +{ + struct tvec_benchctx *bc = ctx; + const struct tvec_bench *b = (const struct tvec_bench *)env; + const struct tvec_env *subenv = b->env; + struct bench_timer *bt; + + bc->b = b; bc->bst = 0; bc->subctx = 0; + + if (!b->bst || !*b->bst) { + bt = bench_createtimer(); if (!bt) goto fail_timer; + bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt); + if (b->bst) *b->bst = bc->bst; + } else if (!(*b->bst)->tm) + goto fail_timer; + else + bc->bst = *b->bst; + bc->dflt_target = bc->bst->target_s; + + if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz); + if (subenv && subenv->setup && subenv->setup(tv, subenv, bc, bc->subctx)) + { xfree(bc->subctx); bc->subctx = 0; return (-1); } + +end: + return (0); +fail_timer: + tvec_skipgroup(tv, "failed to create timer"); goto end; +} + +int tvec_benchset(struct tvec_state *tv, const char *var, + const struct tvec_env *env, void *ctx) +{ + struct tvec_benchctx *bc = ctx; + const struct tvec_bench *b = (const struct tvec_bench *)env; + const struct tvec_env *subenv = b->env; + union tvec_regval rv; + static const struct tvec_floatinfo fi = { TVFF_NOMAX, 0.0, 0.0, 0.0 }; + static const struct tvec_regdef rd = + { "@target", -1, &tvty_float, 0, { &fi } }; + + if (STRCMP(var, ==, "@target")) { + if (tvty_float.parse(&rv, &rd, tv)) return (-1); + if (bc) bc->bst->target_s = rv.f; + return (1); + } else if (subenv && subenv->set) + return (subenv->set(tv, var, subenv, bc->subctx)); + else + return (0); +} + +int tvec_benchbefore(struct tvec_state *tv, void *ctx) +{ + struct tvec_benchctx *bc = ctx; + const struct tvec_bench *b = bc->b; + const struct tvec_env *subenv = b->env; + + if (subenv && subenv->before) return (subenv->before(tv, bc->subctx)); + else return (0); +} + +void tvec_benchafter(struct tvec_state *tv, void *ctx) +{ + struct tvec_benchctx *bc = ctx; + const struct tvec_bench *b = bc->b; + const struct tvec_env *subenv = b->env; + + bc->bst->target_s = bc->dflt_target; + if (subenv && subenv->after) subenv->after(tv, bc->subctx); +} + +void tvec_benchteardown(struct tvec_state *tv, void *ctx) +{ + struct tvec_benchctx *bc = ctx; + const struct tvec_bench *b; + const struct tvec_env *subenv; + + if (!bc) return; + b = bc->b; subenv = b->env; + if (subenv && subenv->teardown && bc->subctx) + subenv->teardown(tv, bc->subctx); + if (bc->bst) { + if (b->bst) bc->bst->target_s = bc->dflt_target; + else { bench_destroy(bc->bst); xfree(bc->bst); } + } +} + +static void benchloop_outer_direct(unsigned long n, void *p) +{ + struct benchrun *r = p; + tvec_testfn *fn = r->fn; void *ctx = r->ctx; + const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out; + + while (n--) fn(in, out, ctx); +} + +static void benchloop_inner_direct(unsigned long n, void *p) + { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); } + +static void benchloop_outer_indirect(unsigned long n, void *p) +{ + struct benchrun *r = p; + struct tvec_state *tv = r->tv; + void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run; + tvec_testfn *fn = r->fn; void *ctx = r->ctx; + + while (n--) run(tv, fn, ctx); +} + +static void benchloop_inner_indirect(unsigned long n, void *p) + { struct benchrun *r = p; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); } + +void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) +{ + struct tvec_benchctx *bc = ctx; + const struct tvec_bench *b = bc->b; + const struct tvec_env *subenv = b->env; + const struct tvec_regdef *rd; + struct tvec_output *o = tv->output; + struct bench_timing tm; + struct benchrun r; + bench_fn *loopfn; + unsigned unit; + dstr d = DSTR_INIT; + double base; + unsigned f = 0; +#define f_any 1u + + r.tv = tv; r.env = subenv; r.ctx = bc->subctx; + r.in = tv->in; r.out = tv->out; r.fn = fn; + + if (b->riter >= 0) { + r.n = &TVEC_REG(tv, in, b->riter)->v.u; + loopfn = subenv && subenv->run ? + benchloop_inner_indirect : benchloop_inner_direct; + } else { + r.n = 0; + loopfn = subenv && subenv->run ? + benchloop_outer_indirect : benchloop_outer_direct; + } + + base = b->niter; + if (b->rbuf < 0) unit = TVBU_OP; + else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, b->rbuf)->v.bytes.sz; } + + for (rd = tv->test->regs; rd->name; rd++) + if (rd->f&TVRF_ID) { + if (f&f_any) dstr_puts(&d, ", "); + else f |= f_any; + dstr_putf(&d, "%s = ", rd->name); + rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, + TVSF_COMPACT, &dstr_printops, &d); + } + + o->ops->bbench(o, d.buf, unit); + if (bench_measure(&tm, bc->bst, base, loopfn, &r)) + o->ops->ebench(o, d.buf, unit, 0); + else + o->ops->ebench(o, d.buf, unit, &tm); + + dstr_destroy(&d); + +#undef f_any +} + +void tvec_benchreport(const struct gprintf_ops *gops, void *go, + unsigned unit, const struct bench_timing *tm) +{ + double scale, x, n = tm->n; + const char *u, *what, *whats; + + if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; } + + assert(tm->f&BTF_TIMEOK); + + switch (unit) { + case TVBU_OP: + gprintf(gops, go, "%.0f iterations ", n); + what = "op"; whats = "ops"; scale = 1000; + break; + case TVBU_BYTE: + x = n; normalize(&x, &u, 1024); gprintf(gops, go, "%.3f %sB ", x, u); + what = whats = "B"; scale = 1024; + break; + default: + abort(); + } + + x = tm->t; normalize(&x, &u, 1000); + gprintf(gops, go, "in %.3f %ss", x, u); + if (tm->f&BTF_CYOK) { + x = tm->cy; normalize(&x, &u, 1000); + gprintf(gops, go, " (%.3f %scy)", x, u); + } + gprintf(gops, go, ": "); + + x = n/tm->t; normalize(&x, &u, scale); + gprintf(gops, go, "%.3f %s%s/s", x, u, whats); + x = tm->t/n; normalize(&x, &u, 1000); + gprintf(gops, go, ", %.3f %ss/%s", x, u, what); + if (tm->f&BTF_CYOK) { + x = tm->cy/n; normalize(&x, &u, 1000); + gprintf(gops, go, " (%.3f %scy/%s)", x, u, what); + } +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec-core.c b/test/tvec-core.c index a25d653..9ec5d10 100644 --- a/test/tvec-core.c +++ b/test/tvec-core.c @@ -33,7 +33,6 @@ #include #include "alloc.h" -#include "bench.h" #include "tvec.h" /*----- Output ------------------------------------------------------------*/ @@ -70,11 +69,11 @@ int tvec_syntax_v(struct tvec_state *tv, int ch, char found[8]; switch (ch) { - case EOF: strcpy(found, ""); break; - case '\n': strcpy(found, ""); ungetc(ch, tv->fp); break; + case EOF: strcpy(found, "#"); break; + case '\n': strcpy(found, "#"); ungetc(ch, tv->fp); break; default: if (isprint(ch)) sprintf(found, "`%c'", ch); - else sprintf(found, "<#x%02x>", ch); + else sprintf(found, "#<\\x%02x>", ch); break; } dstr_vputf(&d, expect, ap); @@ -123,92 +122,43 @@ void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap) set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap); } -void tvec_mismatch(struct tvec_state *tv) - { tv->output->ops->mismatch(tv->output); } +void tvec_dumpreg(struct tvec_state *tv, + unsigned disp, const union tvec_regval *r, + const struct tvec_regdef *rd) + { tv->output->ops->dumpreg(tv->output, disp, r, rd); } -void tvec_write(struct tvec_state *tv, const char *p, ...) +void tvec_mismatch(struct tvec_state *tv, unsigned f) { - va_list ap; - va_start(ap, p); tvec_write_v(tv, p, &ap); va_end(ap); -} -void tvec_write_v(struct tvec_state *tv, const char *p, va_list *ap) -{ - dstr d = DSTR_INIT; - - dstr_vputf(&d, p, ap); tv->output->ops->write(tv->output, d.buf, d.len); - DDESTROY(&d); -} - -/*----- Serialization and deserialization ---------------------------------*/ - -int tvec_serialize(const struct tvec_reg *rv, - const struct tvec_regdef *regs, - unsigned nr, size_t regsz, - void **p_out, size_t *sz_out) -{ - void *p = 0; buf b; - unsigned char *bitmap; - size_t i, nbits, bitsz, sz; - const struct tvec_regdef *rd; - const struct tvec_reg *r; - int rc; - - for (rd = regs, nbits = 0, sz = 0; rd->name; rd++, nbits++) { - if (rd->i >= nr) continue; - r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; - sz += rd->ty->measure(&r->v, rd); - } - bitsz = (nbits + 7)/8; sz += bitsz; - - p = xmalloc(sz); buf_init(&b, p, sz); - bitmap = buf_get(&b, bitsz); assert(bitmap); memset(bitmap, 0, bitsz); - for (rd = regs, i = 0; rd->name; rd++, i++) { - if (rd->i >= nr) continue; - r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; - bitmap[rd->i/8] |= 1 << rd->i%8; - if (rd->ty->tobuf(&b, &r->v, rd)) { rc = -1; goto end; } - } - - if (BBAD(&b)) { rc = -1; goto end; } - *p_out = p; *sz_out = BLEN(&b); p = 0; rc = 0; -end: - xfree(p); - return (rc); -} - -int tvec_deserialize(struct tvec_reg *rv, - const struct tvec_regdef *regs, - unsigned nr, size_t regsz, - const void *p, size_t sz) -{ - buf b; - const unsigned char *bitmap; - size_t i, nbits, bitsz; const struct tvec_regdef *rd; - struct tvec_reg *r; - int rc; - - for (rd = regs, nbits = 0; rd->name; rd++, nbits++); - bitsz = (nbits + 7)/8; sz += bitsz; + const struct tvec_reg *rin, *rout; - buf_init(&b, (/*unconst*/ void *)p, sz); - bitmap = buf_get(&b, bitsz); if (!bitmap) { rc = -1; goto end; } - for (rd = regs, i = 0; rd->name; rd++, i++) { - if (rd->i >= nr) continue; - if (!(bitmap[rd->i/8]&(1 << rd->i%8))) continue; - r = TVEC_GREG(rv, rd->i, regsz); - if (rd->ty->frombuf(&b, &r->v, rd)) { rc = -1; goto end; } - r->f |= TVRF_LIVE; + for (rd = tv->test->regs; rd->name; rd++) { + if (rd->i >= tv->nrout) { + if (!(f&TVMF_IN)) continue; + rin = TVEC_REG(tv, in, rd->i); + tvec_dumpreg(tv, TVRD_INPUT, rin->f&TVRF_LIVE ? &rin->v : 0, rd); + } else { + if (!(f&TVMF_OUT)) continue; + rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i); + if (!(rin->f&TVRF_LIVE)) + tvec_dumpreg(tv, TVRD_OUTPUT, rout->f&TVRF_LIVE ? &rout->v : 0, rd); + else if ((rout->f&TVRF_LIVE) && rd->ty->eq(&rin->v, &rout->v, rd)) + tvec_dumpreg(tv, TVRD_MATCH, &rin->v, rd); + else { + tvec_dumpreg(tv, TVRD_FOUND, rout->f&TVRF_LIVE ? &rout->v : 0, rd); + tvec_dumpreg(tv, TVRD_EXPECT, &rin->v, rd); + } + } } - - if (BBAD(&b)) { rc = -1; goto end; } - rc = 0; -end: - return (rc); } /*----- Main machinery ----------------------------------------------------*/ +struct groupstate { + void *ctx; +}; +#define GROUPSTATE_INIT { 0 } + void tvec_skipspc(struct tvec_state *tv) { int ch; @@ -291,7 +241,7 @@ int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims, int ch; ch = getc(tv->fp); - if (ch == '\n' || ch == EOF || ch == ';' || + if (!ch || ch == '\n' || ch == EOF || ch == ';' || (delims && strchr(delims, ch))) { if (expect) return (tvec_syntax(tv, ch, expect, ap)); else { ungetc(ch, tv->fp); return (-1); } @@ -300,11 +250,27 @@ int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims, do { DPUTC(d, ch); ch = getc(tv->fp); - } while (ch != EOF && !isspace(ch) && (!delims || !strchr(delims, ch))); + } while (ch && ch != EOF && !isspace(ch) && + (!delims || !strchr(delims, ch))); DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp); return (0); } +void tvec_resetoutputs(struct tvec_state *tv) +{ + const struct tvec_regdef *rd; + struct tvec_reg *r; + + for (rd = tv->test->regs; rd->name; rd++) { + assert(rd->i < tv->nreg); + if (rd->i >= tv->nrout) continue; + r = TVEC_REG(tv, out, rd->i); + rd->ty->release(&r->v, rd); + rd->ty->init(&r->v, rd); + r->f = TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE; + } +} + static void init_registers(struct tvec_state *tv) { const struct tvec_regdef *rd; @@ -316,7 +282,6 @@ static void init_registers(struct tvec_state *tv) if (rd->i < tv->nrout) { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; } } - tv->expst = '.'; } static void release_registers(struct tvec_state *tv) @@ -332,42 +297,35 @@ static void release_registers(struct tvec_state *tv) } } -void tvec_check(struct tvec_state *tv, const char *detail, ...) -{ - va_list ap; - va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap); -} -void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap) +int tvec_checkregs(struct tvec_state *tv) { const struct tvec_regdef *rd; const struct tvec_reg *rin, *rout; - unsigned f = 0; -#define f_mismatch 1u - if (tv->expst != tv->st) f |= f_mismatch; for (rd = tv->test->regs; rd->name; rd++) { if (rd->i >= tv->nrout) continue; rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i); if (!rin->f&TVRF_LIVE) continue; - if (!rd->ty->eq(&rin->v, &rout->v, rd)) f |= f_mismatch; + if (!(rout->f&TVRF_LIVE) || !rd->ty->eq(&rin->v, &rout->v, rd)) + return (-1); } - if (!(f&f_mismatch)) return; - - tvec_fail_v(tv, detail, ap); - tvec_mismatch(tv); - -#undef f_mismatch + return (0); } -int tvec_runtest(struct tvec_state *tv) +void tvec_check(struct tvec_state *tv, const char *detail, ...) +{ + va_list ap; + va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap); +} +void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap) { - tv->test->fn(tv->in, tv->out, (/*unconst*/ void *)tv->test->arg.p); - tvec_check(tv, 0); return (0); + if (tvec_checkregs(tv)) + { tvec_fail_v(tv, detail, ap); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); } } static void begin_test(struct tvec_state *tv) { - tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->st = '.'; + tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->output->ops->btest(tv->output); } @@ -382,38 +340,56 @@ void tvec_endtest(struct tvec_state *tv) tv->f &= ~TVSF_OPEN; } -static void check(struct tvec_state *tv) +static void check(struct tvec_state *tv, struct groupstate *g) { + const struct tvec_test *t = tv->test; + const struct tvec_env *env; const struct tvec_regdef *rd; if (!(tv->f&TVSF_OPEN)) return; - for (rd = tv->test->regs; rd->name; rd++) { + for (rd = t->regs; rd->name; rd++) { if (TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE) { if (rd->i < tv->nrout) TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE; } else if (!(rd->f&TVRF_OPT)) { tvec_error(tv, "required register `%s' not set in test `%s'", - rd->name, tv->test->name); + rd->name, t->name); goto end; } } - if (!(tv->f&TVSF_SKIP)) - { begin_test(tv); tv->test->run(tv); tvec_endtest(tv); } + if (!(tv->f&TVSF_SKIP)) { + begin_test(tv); + env = t->env; + if (env && env->before && env->before(tv, g->ctx)) + tvec_skip(tv, "test setup failed"); + else { + if (env && env->run) env->run(tv, t->fn, g->ctx); + else { t->fn(tv->in, tv->out, g->ctx); tvec_check(tv, 0); } + } + if (env && env->after) env->after(tv, g->ctx); + tvec_endtest(tv); + } end: tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv); } -static void begin_test_group(struct tvec_state *tv) +static void begin_test_group(struct tvec_state *tv, struct groupstate *g) { + const struct tvec_test *t = tv->test; + const struct tvec_env *env = t->env; unsigned i; tv->output->ops->bgroup(tv->output); tv->f &= ~TVSF_SKIP; init_registers(tv); for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0; - if (tv->test->preflight) tv->test->preflight(tv); + if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz); + if (env && env->setup && env->setup(tv, env, 0, g->ctx)) { + tvec_skipgroup(tv, "setup failed"); + xfree(g->ctx); g->ctx = 0; + } } void tvec_reportgroup(struct tvec_state *tv) @@ -432,22 +408,27 @@ void tvec_reportgroup(struct tvec_state *tv) } } -static void end_test_group(struct tvec_state *tv) +static void end_test_group(struct tvec_state *tv, struct groupstate *g) { - if (!tv->test) return; - if (tv->f&TVSF_OPEN) check(tv); + const struct tvec_test *t = tv->test; + const struct tvec_env *env; + + if (!t) return; + if (tv->f&TVSF_OPEN) check(tv, g); if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv); - release_registers(tv); tv->test = 0; + env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx); + release_registers(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0; } int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) { dstr d = DSTR_INIT; const struct tvec_test *test; + const struct tvec_env *env; const struct tvec_regdef *rd; struct tvec_reg *r; - int ch; - int rc = 0; + struct groupstate g = GROUPSTATE_INIT; + int ch, ret, rc = 0; tv->infile = infile; tv->lno = 1; tv->fp = fp; @@ -459,7 +440,7 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) goto end; case '[': - end_test_group(tv); + end_test_group(tv, &g); tvec_skipspc(tv); DRESET(&d); tvec_readword(tv, &d, "];", "group name"); tvec_skipspc(tv); @@ -468,12 +449,12 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) if (STRCMP(d.buf, ==, test->name)) goto found_test; tvec_error(tv, "unknown test group `%s'", d.buf); goto flush_line; found_test: - tvec_flushtoeol(tv, 0); tv->test = test; begin_test_group(tv); + tvec_flushtoeol(tv, 0); tv->test = test; begin_test_group(tv, &g); break; case '\n': tv->lno++; - if (tv->f&TVSF_OPEN) check(tv); + if (tv->f&TVSF_OPEN) check(tv, &g); break; default: @@ -483,7 +464,7 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) if (ch == EOF) goto end; else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY); else if (tvec_flushtoeol(tv, 0)) rc = -1; - else check(tv); + else check(tv, &g); } else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY); else { @@ -494,29 +475,20 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) if (ch != '=' && ch != ':') { tvec_syntax(tv, ch, "`=' or `:'"); goto flush_line; } tvec_skipspc(tv); + if (!tv->test) + { tvec_error(tv, "no current test"); goto flush_line; } if (d.buf[0] == '@') { - if (STRCMP(d.buf, ==, "@status")) { - if (!(tv->f&TVSF_OPEN)) - { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; } - ch = getc(tv->fp); - if (ch == EOF || ch == '\n' || ch == ';') - { tvec_syntax(tv, ch, "status character"); goto flush_line; } - else if (ch == '\\') { - ch = getc(tv->fp); - if (ch == EOF || ch == '\n') { - tvec_syntax(tv, ch, "escaped status character"); - goto flush_line; - } - } - tv->expst = ch; - tvec_flushtoeol(tv, 0); - } else { - tvec_error(tv, "unknown special register `%s'", d.buf); + env = tv->test->env; + if (!env || !env->set) ret = 0; + else ret = env->set(tv, d.buf, env, g.ctx); + if (ret <= 0) { + if (!ret) + tvec_error(tv, "unknown special register `%s'", d.buf); goto flush_line; } + if (!(tv->f&TVSF_OPEN)) + { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; } } else { - if (!tv->test) - { tvec_error(tv, "no current test"); goto flush_line; } for (rd = tv->test->regs; rd->name; rd++) if (STRCMP(rd->name, ==, d.buf)) goto found_reg; tvec_error(tv, "unknown register `%s' for test `%s'", @@ -545,80 +517,97 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) if (ferror(tv->fp)) { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; } end: - end_test_group(tv); + end_test_group(tv, &g); tv->infile = 0; tv->fp = 0; dstr_destroy(&d); return (rc); } -/*----- Benchmarking ------------------------------------------------------*/ +/*----- Session lifecycle -------------------------------------------------*/ -struct bench_state *tvec_benchstate; +void tvec_begin(struct tvec_state *tv_out, + const struct tvec_info *info, + struct tvec_output *o) +{ + unsigned i; -struct benchrun { - unsigned long *n; - tvec_testfn *fn; - const struct tvec_reg *in; struct tvec_reg *out; - void *ctx; -}; + tv_out->f = 0; -static void benchloop_outer(unsigned long n, void *p) - { struct benchrun *r = p; while (n--) r->fn(r->in, r->out, r->ctx); } + assert(info->nrout <= info->nreg); + tv_out->nrout = info->nrout; tv_out->nreg = info->nreg; + tv_out->regsz = info->regsz; + tv_out->in = xmalloc(tv_out->nreg*tv_out->regsz); + tv_out->out = xmalloc(tv_out->nrout*tv_out->regsz); + for (i = 0; i < tv_out->nreg; i++) { + TVEC_REG(tv_out, in, i)->f = 0; + if (i < tv_out->nrout) TVEC_REG(tv_out, out, i)->f = 0; + } -static void benchloop_inner(unsigned long n, void *p) - { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); } + for (i = 0; i < TVOUT_LIMIT; i++) + tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0; -int tvec_ensurebench(struct tvec_state *tv, struct bench_state **b_out) -{ - const struct tvec_bench *tvb = tv->test->arg.p; - struct bench_state **bb; - struct bench_timer *bt; + tv_out->tests = info->tests; tv_out->test = 0; + tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0; + o->tv = tv_out; tv_out->output = o; - if (tvb->b) bb = tvb->b; - else bb = &tvec_benchstate; + tv_out->output->ops->bsession(tv_out->output); +} - if (!*bb) { - bt = bench_createtimer(); - if (!bt) { tvec_skip(tv, "failed to create timer"); return (-1); } - *bb = xmalloc(sizeof(**bb)); bench_init(*bb, bt); - } else if (!(*bb)->tm) - { tvec_skip(tv, "failed to create timer"); return (-1); } +int tvec_end(struct tvec_state *tv) +{ + int rc = tv->output->ops->esession(tv->output); - *b_out = *bb; - return (0); + tv->output->ops->destroy(tv->output); + xfree(tv->in); xfree(tv->out); + return (rc); } -int tvec_bench(struct tvec_state *tv) +/*----- Serialization and deserialization ---------------------------------*/ + +int tvec_serialize(const struct tvec_reg *rv, buf *b, + const struct tvec_regdef *regs, + unsigned nr, size_t regsz) { - const struct tvec_bench *tvb = tv->test->arg.p; - struct bench_state *b; - struct bench_timing tm; - struct benchrun r; - bench_fn *loopfn; + unsigned char *bitmap; + size_t i, bitoff, nbits, bitsz; + const struct tvec_regdef *rd; + const struct tvec_reg *r; - if (tvec_ensurebench(tv, &b)) goto end_0; + bitoff = BLEN(b); + for (rd = regs, nbits = 0; rd->name; rd++, nbits++); + bitsz = (nbits + 7)/8; - r.in = tv->in; r.out = tv->out; r.fn = tv->test->fn; - if (tvb->ctxsz) r.ctx = xmalloc(tvb->ctxsz); - else r.ctx = 0; - if (tvb->setup && tvb->setup(tv->in, tv->out, &tvb->arg, r.ctx)) - { tvec_skip(tv, "benchmark setup failed"); goto end_1; } + bitmap = buf_get(b, bitsz); if (!bitmap) return (-1); + memset(bitmap, 0, bitsz); + for (rd = regs, i = 0; rd->name; rd++, i++) { + if (rd->i >= nr) continue; + r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; + bitmap = BBASE(b) + bitoff; bitmap[rd->i/8] |= 1 << rd->i%8; + if (rd->ty->tobuf(b, &r->v, rd)) return (-1); + } + return (0); +} - if (tvb->riter < 0) - { r.n = 0; loopfn = benchloop_outer; } - else - { r.n = &TVEC_REG(tv, in, tvb->riter)->v.u; loopfn = benchloop_inner; } +int tvec_deserialize(struct tvec_reg *rv, buf *b, + const struct tvec_regdef *regs, + unsigned nr, size_t regsz) +{ + const unsigned char *bitmap; + size_t i, nbits, bitsz; + const struct tvec_regdef *rd; + struct tvec_reg *r; - tv->output->ops->bbench(tv->output); - if (bench_measure(&tm, b, loopfn, &r)) - { tv->output->ops->ebench(tv->output, 0); goto end_2; } - tv->output->ops->ebench(tv->output, &tm); + for (rd = regs, nbits = 0; rd->name; rd++, nbits++); + bitsz = (nbits + 7)/8; -end_2: - if (tvb->teardown) tvb->teardown(r.ctx); -end_1: - if (r.ctx) xfree(r.ctx); -end_0: + bitmap = buf_get(b, bitsz); if (!bitmap) return (-1); + for (rd = regs, i = 0; rd->name; rd++, i++) { + if (rd->i >= nr) continue; + if (!(bitmap[rd->i/8]&(1 << rd->i%8))) continue; + r = TVEC_GREG(rv, rd->i, regsz); + if (rd->ty->frombuf(b, &r->v, rd)) return (-1); + r->f |= TVRF_LIVE; + } return (0); } @@ -626,15 +615,12 @@ end_0: static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } }; -static int fakerun(struct tvec_state *tv) - { assert(!"fake run function"); } static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p) { assert(!"fake test function"); } void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t) { - t->name = ""; t->regs = &no_regs; - t->preflight = 0; t->run = fakerun; t->fn = fakefn; t->arg.p = 0; + t->name = ""; t->regs = &no_regs; t->env = 0; t->fn = fakefn; tv->tests = t; } @@ -645,7 +631,7 @@ void tvec_begingroup(struct tvec_state *tv, const char *name, t->name = name; tv->test = t; tv->infile = file; tv->lno = tv->test_lno = lno; - begin_test_group(tv); + begin_test_group(tv, 0); } void tvec_endgroup(struct tvec_state *tv) @@ -681,8 +667,6 @@ static void adhoc_claim_setup(struct tvec_state *tv, ck->saved_file = tv->infile; if (file) tv->infile = file; ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno; t->regs = regs ? regs : &no_regs; - - tv->st = '.'; } static void adhoc_claim_teardown(struct tvec_state *tv, @@ -728,48 +712,10 @@ int tvec_claimeq(struct tvec_state *tv, adhoc_claim_setup(tv, &ck, regs, file, lno); ok = ty->eq(&tv->in[0].v, &tv->out[0].v, ®s[0]); - if (!ok) { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv); } + if (!ok) + { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); } adhoc_claim_teardown(tv, &ck); return (ok); } -/*----- Session lifecycle -------------------------------------------------*/ - -void tvec_begin(struct tvec_state *tv_out, - const struct tvec_info *info, - struct tvec_output *o) -{ - unsigned i; - - tv_out->f = 0; - - assert(info->nrout <= info->nreg); - tv_out->nrout = info->nrout; tv_out->nreg = info->nreg; - tv_out->regsz = info->regsz; - tv_out->in = xmalloc(tv_out->nreg*tv_out->regsz); - tv_out->out = xmalloc(tv_out->nrout*tv_out->regsz); - for (i = 0; i < tv_out->nreg; i++) { - TVEC_REG(tv_out, in, i)->f = 0; - if (i < tv_out->nrout) TVEC_REG(tv_out, out, i)->f = 0; - } - - for (i = 0; i < TVOUT_LIMIT; i++) - tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0; - - tv_out->tests = info->tests; tv_out->test = 0; - tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0; - o->tv = tv_out; tv_out->output = o; - - tv_out->output->ops->bsession(tv_out->output); -} - -int tvec_end(struct tvec_state *tv) -{ - int rc = tv->output->ops->esession(tv->output); - - tv->output->ops->destroy(tv->output); - xfree(tv->in); xfree(tv->out); - return (rc); -} - /*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec-main.c b/test/tvec-main.c index aa36180..25698a8 100644 --- a/test/tvec-main.c +++ b/test/tvec-main.c @@ -56,8 +56,6 @@ static const struct outform { { 0, 0 } }; -static struct bench_state benchstate; - const struct tvec_info tvec_adhocinfo = { 0, 1, 1, sizeof(struct tvec_reg) }; @@ -101,7 +99,6 @@ Options:\n\ \n\ -f, --format=FORMAT produce output in FORMAT.\n\ -o, --output=OUTPUT write output to OUTPUT file.\n\ - -t, --target=SECS aim to run benchmarks for SECS.\n\ ", fp); } @@ -111,10 +108,8 @@ void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out, FILE *ofp = 0; const struct outform *of = 0; struct tvec_output *o; - struct bench_timer *tm; - const char *p; char *q; + const char *p; int opt; - double t; unsigned f = 0; #define f_bogus 1u @@ -125,15 +120,12 @@ void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out, { "format", OPTF_ARGREQ, 0, 'f' }, { "output", OPTF_ARGREQ, 0, 'o' }, - { "target", OPTF_ARGREQ, 0, 't' }, { 0, 0, 0, 0 } }; - tvec_benchstate = &benchstate; - tm = bench_createtimer(); bench_init(&benchstate, tm); ego(argv[0]); for (;;) { - opt = mdwopt(argc, argv, "hvu" "f:o:t:", options, 0, 0, 0); + opt = mdwopt(argc, argv, "hvu" "f:o:", options, 0, 0, 0); if (opt < 0) break; switch (opt) { case 'h': help(stdout); exit(0); @@ -148,12 +140,6 @@ void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out, die(2, "failed to open `%s' for writing: %s", optarg, strerror(errno)); break; - case 't': - errno = 0; t = strtod(optarg, &q); - while (ISSPACE(*q)) q++; - if (errno || *q || t < 0) die(2, "invalid time `%s'", optarg); - benchstate.target_s = t; - break; default: f |= f_bogus; diff --git a/test/tvec-output.c b/test/tvec-output.c index 015013b..fbdb347 100644 --- a/test/tvec-output.c +++ b/test/tvec-output.c @@ -27,6 +27,8 @@ /*----- Header files ------------------------------------------------------*/ +#include "config.h" + #include #include #include @@ -43,34 +45,14 @@ /*----- Common machinery --------------------------------------------------*/ -enum { INPUT, OUTPUT, MATCH, EXPECT, FOUND }; -struct mismatchfns { - void (*report_status)(unsigned /*disp*/, int /*st*/, - struct tvec_state */*tv*/); - void (*report_register)(unsigned /*disp*/, - const struct tvec_reg */*r*/, - const struct tvec_regdef */*rd*/, - struct tvec_state */*tv*/); -}; - -static const char *stdisp(unsigned disp) -{ - switch (disp) { - case MATCH: return "final"; - case EXPECT: return "expected"; - case FOUND: return "actual"; - default: abort(); - } -} - static const char *regdisp(unsigned disp) { switch (disp) { - case INPUT: return "input"; - case OUTPUT: return "output"; - case MATCH: return "matched"; - case EXPECT: return "expected"; - case FOUND: return "computed"; + case TVRD_INPUT: return "input"; + case TVRD_OUTPUT: return "output"; + case TVRD_MATCH: return "matched"; + case TVRD_EXPECT: return "expected"; + case TVRD_FOUND: return "found"; default: abort(); } } @@ -88,6 +70,7 @@ static int getenv_boolean(const char *var, int dflt) STRCMP(p, ==, "1")) return (1); else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") || + STRCMP(p, ==, "f") || STRCMP(p, ==, "false") || STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") || STRCMP(p, ==, "0")) return (0); @@ -98,147 +81,20 @@ static int getenv_boolean(const char *var, int dflt) } } -static void basic_report_status(unsigned disp, int st, struct tvec_state *tv) - { tvec_write(tv, " %8s status = `%c'\n", stdisp(disp), st); } - -static void basic_report_register(unsigned disp, - const struct tvec_reg *r, - const struct tvec_regdef *rd, - struct tvec_state *tv) -{ - tvec_write(tv, " %8s %s = ", regdisp(disp), rd->name); - if (r->f&TVRF_LIVE) rd->ty->dump(&r->v, rd, tv, 0); - else tvec_write(tv, "#"); - tvec_write(tv, "\n"); -} - -static const struct mismatchfns basic_mismatchfns = - { basic_report_status, basic_report_register }; - -static void dump_inreg(const struct tvec_regdef *rd, - const struct mismatchfns *fns, struct tvec_state *tv) - { fns->report_register(INPUT, TVEC_REG(tv, in, rd->i), rd, tv); } - -static void dump_outreg(const struct tvec_regdef *rd, - const struct mismatchfns *fns, struct tvec_state *tv) -{ - const struct tvec_reg - *rin = TVEC_REG(tv, in, rd->i), *rout = TVEC_REG(tv, out, rd->i); - - if (tv->st == '.') { - if (!(rout->f&TVRF_LIVE)) { - if (!(rin->f&TVRF_LIVE)) - fns->report_register(INPUT, rin, rd, tv); - else { - fns->report_register(FOUND, rout, rd, tv); - fns->report_register(EXPECT, rin, rd, tv); - } - } else { - if (!(rin->f&TVRF_LIVE)) fns->report_register(OUTPUT, rout, rd, tv); - else if (rd->ty->eq(&rin->v, &rout->v, rd)) - fns->report_register(MATCH, rin, rd, tv); - else { - fns->report_register(FOUND, rout, rd, tv); - fns->report_register(EXPECT, rin, rd, tv); - } - } - } -} - -static void mismatch(const struct mismatchfns *fns, struct tvec_state *tv) -{ - const struct tvec_regdef *rd; - - if (tv->st != tv->expst) { - fns->report_status(FOUND, tv->st, tv); - fns->report_status(EXPECT, tv->expst, tv); - } else if (tv->st != '.') - fns->report_status(MATCH, tv->st, tv); - - for (rd = tv->test->regs; rd->name; rd++) { - if (rd->i < tv->nrout) dump_outreg(rd, fns, tv); - else dump_inreg(rd, fns, tv); - } -} - -static void bench_summary(struct tvec_state *tv) +static int register_maxnamelen(const struct tvec_state *tv) { const struct tvec_regdef *rd; - unsigned f = 0; -#define f_any 1u + int maxlen = 6, n; for (rd = tv->test->regs; rd->name; rd++) - if (rd->f&TVRF_ID) { - if (f&f_any) tvec_write(tv, ", "); - else f |= f_any; - tvec_write(tv, "%s = ", rd->name); - rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, tv, TVSF_COMPACT); - } - -#undef f_any -} - -static void normalize(double *x_inout, const char **unit_out, double scale) -{ - static const char - *const nothing = "", - *const big[] = { "k", "M", "G", "T", "P", "E", 0 }, - *const little[] = { "m", "µ", "n", "p", "f", "a", 0 }; - const char *const *u; - double x = *x_inout; - - if (x < 1) - for (u = little, x *= scale; x < 1 && u[1]; u++, x *= scale); - else if (x >= scale) - for (u = big, x /= scale; x >= scale && u[1]; u++, x /= scale); - else - u = ¬hing; - - *x_inout = x; *unit_out = *u; -} - -static void bench_report(struct tvec_state *tv, - const struct bench_timing *tm) -{ - const struct tvec_bench *b = tv->test->arg.p; - double n = (double)tm->n*b->niter; - double x, scale; - const char *u, *what, *whats; - - assert(tm->f&BTF_TIMEOK); - - if (b->rbuf == -1) { - tvec_write(tv, " -- %.0f iterations ", n); - what = "op"; whats = "ops"; scale = 1000; - } else { - n *= TVEC_REG(tv, in, b->rbuf)->v.bytes.sz; - x = n; normalize(&x, &u, 1024); tvec_write(tv, " -- %.3f %sB ", x, u); - what = whats = "B"; scale = 1024; - } - x = tm->t; normalize(&x, &u, 1000); - tvec_write(tv, "in %.3f %ss", x, u); - if (tm->f&BTF_CYOK) { - x = tm->cy; normalize(&x, &u, 1000); - tvec_write(tv, " (%.3f %scy)", x, u); - } - tvec_write(tv, ": "); - - x = n/tm->t; normalize(&x, &u, scale); - tvec_write(tv, "%.3f %s%s/s", x, u, whats); - x = tm->t/n; normalize(&x, &u, 1000); - tvec_write(tv, ", %.3f %ss/%s", x, u, what); - if (tm->f&BTF_CYOK) { - x = tm->cy/n; normalize(&x, &u, 1000); - tvec_write(tv, " (%.3f %scy/%s)", x, u, what); - } - tvec_write(tv, "\n"); + { n = strlen(rd->name); if (n > maxlen) maxlen = n; } + return (maxlen); } /*----- Skeleton ----------------------------------------------------------*/ /* static void ..._error(struct tvec_output *o, const char *msg, va_list *ap) static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap) -static void ..._write(struct tvec_output *o, const char *p, size_t sz) static void ..._bsession(struct tvec_output *o) static int ..._esession(struct tvec_output *o) static void ..._bgroup(struct tvec_output *o) @@ -246,19 +102,23 @@ static void ..._egroup(struct tvec_output *o, unsigned outcome) static void ..._skipgroup(struct tvec_output *o, const char *excuse, va_list *ap) static void ..._btest(struct tvec_output *o) -static void ..._skip(struct tvec_output *o, const char *detail, va_list *ap) +static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap) static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap) -static void ..._mismatch(struct tvec_output *o) +static void ..._dumpreg(struct tvec_output *o, unsigned disp, + union tvec_regval *rv, const struct tvec_regdef *rd) static void ..._etest(struct tvec_output *o, unsigned outcome) -static void ..._bbench(struct tvec_output *o) -static void ..._ebench(struct tvec_output *o, const struct tvec_timing *t) +static void ..._bbench(struct tvec_output *o, + const char *ident, unsigned unit) +static void ..._ebench(struct tvec_output *o, + const char *ident, unsigned unit, + const struct tvec_timing *t) static void ..._destroy(struct tvec_output *o) static const struct tvec_outops ..._ops = { - ..._error, ..._notice, ..._write, + ..._error, ..._notice, ..._bsession, ..._esession, ..._bgroup, ..._egroup, ..._skip, - ..._btest, ..._skip, ..._fail, ..._mismatch, ..._etest, + ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest, ..._bbench, ..._ebench, ..._destroy }; @@ -294,6 +154,7 @@ struct human_output { FILE *fp; dstr scoreboard; unsigned attr; + int maxlen; unsigned f; #define HOF_TTY 1u #define HOF_DUPERR 2u @@ -403,27 +264,22 @@ static void human_report(struct tvec_output *o, const char *msg, va_list *ap) { struct human_output *h = (struct human_output *)o; struct tvec_state *tv = h->_o.tv; + dstr d = DSTR_INIT; + + dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n'); clear_progress(h); fflush(h->fp); fprintf(stderr, "%s: ", QUIS); report_location(h, stderr, tv->infile, tv->lno); - vfprintf(stderr, msg, *ap); - fputc('\n', stderr); + fwrite(d.buf, 1, d.len, stderr); if (h->f&HOF_DUPERR) { - report_location(h, stderr, tv->infile, tv->lno); - vfprintf(h->fp, msg, *ap); - fputc('\n', h->fp); + report_location(h, h->fp, tv->infile, tv->lno); + fwrite(d.buf, 1, d.len, h->fp); } show_progress(h); } -static void human_write(struct tvec_output *o, const char *p, size_t sz) -{ - struct human_output *h = (struct human_output *)o; - fwrite(p, 1, sz, h->fp); -} - static void human_bsession(struct tvec_output *o) { ; } static void report_skipped(struct human_output *h, unsigned n) @@ -476,6 +332,8 @@ static int human_esession(struct tvec_output *o) static void human_bgroup(struct tvec_output *o) { struct human_output *h = (struct human_output *)o; + + h->maxlen = register_maxnamelen(h->_o.tv); dstr_reset(&h->scoreboard); show_progress(h); } @@ -554,51 +412,22 @@ static void human_fail(struct tvec_output *o, fputc('\n', h->fp); } -static void set_dispattr(struct human_output *h, unsigned disp) -{ - switch (disp) { - case EXPECT: setattr(h, HFG(GREEN)); break; - case FOUND: setattr(h, HFG(RED)); break; - default: setattr(h, 0); break; - } -} - -static void human_report_status(unsigned disp, int st, struct tvec_state *tv) -{ - struct human_output *h = (struct human_output *)tv->output; - - fprintf(h->fp, " %8s status = ", stdisp(disp)); - set_dispattr(h, disp); fprintf(h->fp, "`%c'", st); setattr(h, 0); - fputc('\n', h->fp); -} - -static void human_report_register(unsigned disp, - const struct tvec_reg *r, - const struct tvec_regdef *rd, - struct tvec_state *tv) -{ - struct human_output *h = (struct human_output *)tv->output; - - fprintf(h->fp, " %8s %s = ", regdisp(disp), rd->name); - if (!(r->f&TVRF_LIVE)) - tvec_write(tv, "#"); - else { - set_dispattr(h, disp); - rd->ty->dump(&r->v, rd, tv, 0); - setattr(h, 0); - } - tvec_write(tv, "\n"); -} - -static const struct mismatchfns human_mismatchfns = - { human_report_status, human_report_register }; - -static void human_mismatch(struct tvec_output *o) +static void human_dumpreg(struct tvec_output *o, + unsigned disp, const union tvec_regval *rv, + const struct tvec_regdef *rd) { struct human_output *h = (struct human_output *)o; + const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name); - if (h->f&HOF_COLOUR) mismatch(&human_mismatchfns, h->_o.tv); - else mismatch(&basic_mismatchfns, h->_o.tv); + fprintf(h->fp, "%*s%s %s = ", 10 + h->maxlen - n, "", ds, rd->name); + if (h->f&HOF_COLOUR) { + if (!rv) setattr(h, HFG(YELLOW)); + else if (disp == TVRD_FOUND) setattr(h, HFG(RED)); + else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN)); + } + if (!rv) fprintf(h->fp, "#"); + else rd->ty->dump(rv, rd, 0, &file_printops, h->fp); + setattr(h, 0); fputc('\n', h->fp); } static void human_etest(struct tvec_output *o, unsigned outcome) @@ -619,21 +448,22 @@ static void human_etest(struct tvec_output *o, unsigned outcome) } } -static void human_bbench(struct tvec_output *o) +static void human_bbench(struct tvec_output *o, + const char *ident, unsigned unit) { struct human_output *h = (struct human_output *)o; struct tvec_state *tv = h->_o.tv; clear_progress(h); - fprintf(h->fp, "%s: ", tv->test->name); - bench_summary(tv); fflush(h->fp); + fprintf(h->fp, "%s: %s: ", tv->test->name, ident); fflush(h->fp); } static void human_ebench(struct tvec_output *o, + const char *ident, unsigned unit, const struct bench_timing *tm) { struct human_output *h = (struct human_output *)o; - bench_report(h->_o.tv, tm); + tvec_benchreport(&file_printops, h->fp, unit, tm); fputc('\n', h->fp); } static void human_destroy(struct tvec_output *o) @@ -646,10 +476,10 @@ static void human_destroy(struct tvec_output *o) } static const struct tvec_outops human_ops = { - human_report, human_report, human_write, + human_report, human_report, human_bsession, human_esession, human_bgroup, human_egroup, human_skipgroup, - human_btest, human_skip, human_fail, human_mismatch, human_etest, + human_btest, human_skip, human_fail, human_dumpreg, human_etest, human_bbench, human_ebench, human_destroy }; @@ -663,7 +493,6 @@ struct tvec_output *tvec_humanoutput(FILE *fp) h->f = 0; h->attr = 0; h->fp = fp; - if (fp != stdout && fp != stderr) h->f |= HOF_DUPERR; switch (getenv_boolean("TVEC_TTY", -1)) { case 1: h->f |= HOF_TTY; break; @@ -683,6 +512,7 @@ struct tvec_output *tvec_humanoutput(FILE *fp) break; } + if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR; dstr_create(&h->scoreboard); return (&h->_o); } @@ -692,6 +522,8 @@ struct tvec_output *tvec_humanoutput(FILE *fp) struct tap_output { struct tvec_output _o; FILE *fp; + dstr d; + int maxlen; unsigned f; #define TOF_FRESHLINE 1u }; @@ -716,22 +548,59 @@ static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap) fputs("## ", t->fp); tap_report(t, msg, ap); } -static void tap_write(struct tvec_output *o, const char *p, size_t sz) +static int tap_writech(void *go, int ch) { - struct tap_output *t = (struct tap_output *)o; + struct tap_output *t = go; + + if (t->f&TOF_FRESHLINE) { + if (fputs("## ", t->fp) < 0) return (-1); + t->f &= ~TOF_FRESHLINE; + } + if (putc(ch, t->fp) < 0) return (-1); + if (ch == '\n') t->f |= TOF_FRESHLINE; + return (1); +} + +static int tap_writem(void *go, const char *p, size_t sz) +{ + struct tap_output *t = go; const char *q, *l = p + sz; + size_t n; - if (p == l) return; - if (t->f&TOF_FRESHLINE) fputs("## ", t->fp); + if (p == l) return (0); + if (t->f&TOF_FRESHLINE) + if (fputs("## ", t->fp) < 0) return (-1); for (;;) { q = memchr(p, '\n', l - p); if (!q) break; - fwrite(p, 1, q + 1 - p, t->fp); p = q + 1; - if (p == l) { t->f |= TOF_FRESHLINE; return; } - fputs("## ", t->fp); + n = q + 1 - p; if (fwrite(p, 1, n, t->fp) < n) return (-1); + p = q + 1; + if (p == l) { t->f |= TOF_FRESHLINE; return (sz); } + if (fputs("## ", t->fp) < 0) return (-1); } - fwrite(p, 1, l - p, t->fp); t->f &= ~TOF_FRESHLINE; + n = l - p; if (fwrite(p, 1, n, t->fp) < n) return (-1); + t->f &= ~TOF_FRESHLINE; return (0); +} + +static int tap_nwritef(void *go, size_t maxsz, const char *p, ...) +{ + struct tap_output *t = go; + va_list ap; + int n; + + va_start(ap, p); DRESET(&t->d); DENSURE(&t->d, maxsz + 1); +#ifdef HAVE_SNPRINTF + n = vsnprintf(t->d.buf, maxsz + 1, p, ap); +#else + n = vsprintf(t->d.buf, p, ap); +#endif + assert(0 <= n && n <= maxsz); + va_end(ap); + return (tap_writem(t, t->d.buf, n)); } +static const struct gprintf_ops tap_printops = + { tap_writech, tap_writem, tap_nwritef }; + static void tap_bsession(struct tvec_output *o) { ; } static unsigned tap_grpix(struct tap_output *t) @@ -759,7 +628,11 @@ static int tap_esession(struct tvec_output *o) return (tv->all[TVOUT_LOSE] ? 1 : 0); } -static void tap_bgroup(struct tvec_output *o) { ; } +static void tap_bgroup(struct tvec_output *o) +{ + struct tap_output *t = (struct tap_output *)o; + t->maxlen = register_maxnamelen(t->_o.tv); +} static void tap_egroup(struct tvec_output *o, unsigned outcome) { @@ -819,21 +692,39 @@ static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap) fputc('\n', t->fp); } -static void tap_mismatch(struct tvec_output *o) - { mismatch(&basic_mismatchfns, o->tv); } +static void tap_dumpreg(struct tvec_output *o, + unsigned disp, const union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + struct tap_output *t = (struct tap_output *)o; + const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name); + + fprintf(t->fp, "## %*s%s %s = ", 10 + t->maxlen - n, "", ds, rd->name); + if (!rv) + fprintf(t->fp, "#\n"); + else { + t->f &= ~TOF_FRESHLINE; + rd->ty->dump(rv, rd, 0, &tap_printops, t); + fputc('\n', t->fp); + } +} static void tap_etest(struct tvec_output *o, unsigned outcome) { ; } -static void tap_bbench(struct tvec_output *o) { ; } +static void tap_bbench(struct tvec_output *o, + const char *ident, unsigned unit) + { ; } static void tap_ebench(struct tvec_output *o, + const char *ident, unsigned unit, const struct bench_timing *tm) { struct tap_output *t = (struct tap_output *)o; struct tvec_state *tv = t->_o.tv; - tvec_write(tv, "%s: ", tv->test->name); bench_summary(tv); - bench_report(tv, tm); + fprintf(t->fp, "## %s: %s: ", tv->test->name, ident); + t->f &= ~TOF_FRESHLINE; tvec_benchreport(&tap_printops, t, unit, tm); + fputc('\n', t->fp); } static void tap_destroy(struct tvec_output *o) @@ -841,14 +732,15 @@ static void tap_destroy(struct tvec_output *o) struct tap_output *t = (struct tap_output *)o; if (t->fp != stdout && t->fp != stderr) fclose(t->fp); + dstr_destroy(&t->d); xfree(t); } static const struct tvec_outops tap_ops = { - tap_error, tap_notice, tap_write, + tap_error, tap_notice, tap_bsession, tap_esession, tap_bgroup, tap_egroup, tap_skipgroup, - tap_btest, tap_skip, tap_fail, tap_mismatch, tap_etest, + tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest, tap_bbench, tap_ebench, tap_destroy }; @@ -858,8 +750,8 @@ struct tvec_output *tvec_tapoutput(FILE *fp) struct tap_output *t; t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops; - t->f = TOF_FRESHLINE; - t->fp = fp; + dstr_create(&t->d); + t->f = 0; t->fp = fp; return (&t->_o); } diff --git a/test/tvec-remote.c b/test/tvec-remote.c new file mode 100644 index 0000000..e03297e --- /dev/null +++ b/test/tvec-remote.c @@ -0,0 +1,297 @@ +/* -*-c-*- + * + * Remote testing + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "alloc.h" +#include "buf.h" +#include "tvec.h" + +/*----- Data structures ---------------------------------------------------*/ + +struct tvec_remote { + int infd, outfd; + dbuf bin, bout; + unsigned f; +#define TVRF_BROKEN 1u +}; + +struct tvec_remotectx { + struct tvec_remote r; + pid_t kid; +}; + +struct remote_output { + struct tvec_output _o; + struct tvec_remote r; +}; + +/*----- Basic I/O ---------------------------------------------------------*/ + +static int PRINTF_LIKE(3, 4) + ioerr(struct tvec_state *tv, struct tvec_remote *r, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + r->f |= TVRF_BROKEN; + tvec_write(tv, msg, &ap); + va_end(ap); + return (-1); +} + +static int send_all(struct tvec_state *tv, struct tvec_remote *r, + const unsigned char *p, size_t sz) +{ + ssize_t n; + + while (sz) { + n = write(r->outfd, p, sz); + if (n > 0) + { p += n; sz -= n; } + else + return (ioerr(tv, r, "failed to send: %s", + n ? strerror(errno) : "empty write")); + } + return (0); +} + +#define RCVF_ALLOWEOF 1u +static int recv_all(struct tvec_state *tv, struct tvec_remote *r, + unsigned char *p, size_t sz, unsigned f) +{ + ssize_t n; + unsigned ff = 0; +#define f_any 1u + + while (sz) { + n = read(r->infd, p, sz); + if (n > 0) + { p += n; sz -= n; ff |= f_any; } + else if (!n && (f&RCVF_ALLOWEOF) && !(ff&f_any)) + return (1); + else + return (ioerr(tv, r, "failed to receive: %s", + n ? strerror(errno) : "unexpected end-of-file")); + } + return (0); + +#undef f_any +} + +int tvec_send(struct tvec_state *tv, struct tvec_reomte *r) +{ + kludge64 k; unsigned char lenbuf[8]; + const char *p; size_t sz; + + if (r->f&TVRF_BROKEN) return (-1); + if (BBAD(&r->bout.b)) + return (ioerr(tv, r, "failed to build output packet buffer"); + + p = BBASE(r->bout.b); sz = BLEN(&r->bout.b); + ASSIGN64(k, sz); STORE64_L_(lenbuf, k); + if (send_all(tv, r, lenbuf, sizeof(lenbuf))) return (-1); + if (send_all(tv, r, p, sz)) return (-1); + + return (0); +} + +int tvec_recv(struct tvec_state *tv, struct tvec_reomte *r, buf *b_out) +{ + kludge64 k, szmax; unsigned char lenbuf[8]; + unsigned char *p; + size_t sz; + int rc; + + if (r->f&TVRF_BROKEN) return (-1); + ASSIGN64(k, (size_t)-1); + rc = recv_all(tv, r, lenbuf, sizeof(lenbuf), RCVF_ALLOWEOF); + if (rc) return (rc); + LOAD64_L_(k, lenbuf); + if (CMP64(k, >, szmax)) + return (ioerr(tv, r, "packet size 0x%08lx%08lx out of range", + (unsigned long)HI64(k), (unsigned long)LO64(k))); + + sz = GET64(size_t, k); buf_reset(&r->bin); p = buf_get(&r->bin.b, sz); + if (!p) return (ioerr(tv, r, "failed to allocate receive buffer")); + if (recv_all(tv, r, p, sz, 0)) return (-1); + buf_init(b_out, p, sz); return (0); +} + +/*----- Data formatting primitives ----------------------------------------*/ + + +/*----- Packet types ------------------------------------------------------*/ + +#define TVPK_ERROR 0x0001 /* msg: string */ +#define TVPK_NOTICE 0x0002 /* msg: string */ +#define TVPK_STATUS 0x0003 /* st: char */ + +#define TVPK_BGROUP 0x0101 /* name: string */ +#define TVPK_TEST 0x0102 /* in: regs */ +#define TVPK_EGROUP 0x0103 /* --- */ + +#define TVPK_SKIPGRP 0x0201 /* excuse: string */ +#define TVPK_SKIP 0x0202 /* excuse: string */ +#define TVPK_FAIL 0x0203 /* detail: string */ +#define TVPK_MISMATCH 0x0204 /* in, out: regs */ +#define TVPK_BBENCH 0x0205 /* in: regs */ +#define TVPK_EBENCH 0x0206 /* flags: u16; n: u64; t, cy: float */ + +/*----- The output driver -------------------------------------------------*/ + +#define SENDPK(ro, pk) \ + MC_BEFORE(setpk, \ + { buf_reset(&(ro)->r.bout); \ + buf_putu16l(&(ro)->r.bout.b, (pk)); }) \ + MC_AFTER(send, \ + { tvec_send(&ro->_o.tv, &ro->r); }) + +static int sendstr(struct tvec_output *o, unsigned pk, + const char *p, va_list *ap) +{ + struct remote_output *ro = (struct remote_output *)o; + + if (ro->r.f&TVRF_BROKEN) return (-1); + dbuf_reset(&ro->r.bout); + buf_putu16l(&ro->r.bout.b, TVPK_ERROR); + buf_vputstrf16l(&ro->r.bout.b, msg, ap); + return (tvec_send(ro->_o.tv, &ro->r)); +} + +static void report(struct tvec_output *o, unsigned pk, + const char *msg, va_list *ap) +{ + if (sendstr(o, pk, msg, ap)) { + fprintf(stderr, "%s: ", QUIS); + vfprintf(stderr, msg, *ap); + fputc('\n', stderr); + } +} + +static void remote_error(struct tvec_output *o, const char *msg, va_list *ap) + { report(o, TVPK_ERROR, msg, ap); } + +static void remote_notice(struct tvec_output *o, + const char *msg, va_list *ap) + { report(o, TVPK_NOTICE, msg, ap); } + +static void remote_setstatus(struct tvec_ouptut *o, int st) +{ + struct remote_output *ro = (struct remote_output *)o; + SENDPK(ro, TVPK_STATUS) buf_putbyte(&ro->r.bout.b, st); +} + +static void remote_skipgroup(struct tvec_output *o, + const char *excuse, va_list *ap) + { sendstr(o, TVPK_SKIPGRP, excuse, ap); } + +static void remote_skip(struct tvec_output *o, + const char *excuse, va_list *ap) + { sendstr(o, TVPK_SKIP, excuse, ap); } + +static void remote_fail(struct tvec_output *o, + const char *detail, va_list *ap) + { sendstr(o, TVPK_FAIL, detail, ap); } + +static void remote_mismatch(struct tvec_output *o) +{ + struct remote_output *ro = (struct remote_output *)o; + struct tvec_state *rv = ro->_o.tv; + + SENDPK(ro, TVPK_MISMATCH) { + tvec_serialize(tv, &ro->r.bout.b, tv->in, tv->nreg, tv->regsz); + tvec_serialize(tv, &ro->r.bout.b, tv->out, tv->nrout, tv->regsz); + } +} + +static void remote_bbench(struct tvec_output *o) +{ + struct remote_output *ro = (struct remote_output *)o; + struct tvec_state *rv = ro->_o.tv; + + SENDPK(ro, TVPK_BBENCH) + tvec_serialize(tv, &ro->r.bout.b, tv->in, tv->nreg, tv->regsz); +} + +static void remote_ebench(struct tvec_output *o, + const struct bench_timing *t) +{ + struct remote_output *ro = (struct remote_output *)o; + kludge64 k; + + SENDPK(ro, TVPK_EBENCH) { + buf_putu16l(&ro->r.bout.b, t->f); + ASSIGN64(k, t->n); buf_putk64l(&ro->r.bout.b, k); + if (t->f&BTF_TIMEOK) buf_putf64l(&ro->r.bout.b, t->t); + if (t->f&BTF_CYOK) buf_putf64l(&ro->r.bout.b, t->cy); + } +} + +static void remote_write(struct tvec_output *o, const char *p, size_t sz) + { assert(!"remote_write"); } +static void remote_bsession(struct tvec_output *o) + { assert(!"remote_bsession"); } +static int remote_esession(struct tvec_output *o) + { assert(!"remote_esession"); return (-1); } +static void remote_bgroup(struct tvec_output *o) + { assert(!"remote_bgroup"); } +static void remote_btest(struct tvec_output *o) + { assert(!"remote_btest"); } +static void remote_egroup(struct tvec_output *o, unsigned outcome) + { assert(!"remote_egroup"); } +static void remote_etest(struct tvec_output *o, unsigned outcome) + { assert(!"remote_etest"); } + +static void remote_destroy(struct tvec_output *o) +{ +} + +static const struct tvec_outops remote_ops = { + remote_error, remote_notice, remote_setstatus, remote_write, + remote_bsession, remote_esession, + remote_bgroup, remote_egroup, remote_skip, + remote_btest, remote_skip, remote_fail, remote_mismatch, remote_etest, + remote_bbench, remote_ebench, + remote_destroy + +/*----- Main code ---------------------------------------------------------*/ + + + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec-types.c b/test/tvec-types.c index 249371b..8be8ae2 100644 --- a/test/tvec-types.c +++ b/test/tvec-types.c @@ -30,7 +30,9 @@ #include #include #include +#include #include +#include #include #include @@ -44,6 +46,18 @@ /*----- Preliminary utilities ---------------------------------------------*/ +#ifdef isnan +# define NANP(x) isnan(x) +#else +# define NANP(x) (!((x) == (x))) +#endif + +#ifdef isinf +# define INFP(x) isinf(x) +#else +# define INFP(x) ((x) > DBL_MAX || (x) < DBL_MIN) +#endif + static int signed_to_buf(buf *b, long i) { kludge64 k; @@ -126,11 +140,13 @@ static int parse_signed(long *i_out, const char *p, olderr = errno; errno = 0; pp = p; if (*pp == '-' || *pp == '+') pp++; - if (!ISDIGIT(*pp)) return (tvec_syntax(tv, *pp, "signed integer")); + if (!ISDIGIT(*pp)) + return (tvec_syntax(tv, *pp ? *pp : fgetc(tv->fp), "signed integer")); i = strtol(p, &q, 0); if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line")); - if (errno) return (tvec_error(tv, "invalid integer `%s'", p)); - check_signed_range(i, ir, tv); + if (errno) + return (tvec_error(tv, "invalid integer `%s': %s", p, strerror(errno))); + if (check_signed_range(i, ir, tv)) return (-1); errno = olderr; *i_out = i; return (0); } @@ -147,12 +163,210 @@ static int parse_unsigned(unsigned long *u_out, const char *p, if (!ISDIGIT(*p)) return (tvec_syntax(tv, *p, "unsigned integer")); u = strtoul(p, &q, 0); if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line")); - if (errno) return (tvec_error(tv, "invalid integer `%s'", p)); - check_unsigned_range(u, ur, tv); + if (errno) + return (tvec_error(tv, "invalid integer `%s': %s", p, strerror(errno))); + if (check_unsigned_range(u, ur, tv)) return (-1); errno = olderr; *u_out = u; return (0); } +static void format_floating(const struct gprintf_ops *gops, void *go, + double x) +{ + int prec; + + if (NANP(x)) + gprintf(gops, go, "#nan"); + else if (INFP(x)) + gprintf(gops, go, x > 0 ? "#+inf" : "#-inf"); + else { + /* Ugh. C doesn't provide any function for just printing a + * floating-point number /correctly/, i.e., so that you can read the + * result back and recover the number you first thought of. There are + * complicated algorithms published for doing this, but I really don't + * want to get into that here. So we have this. + * + * The sign doesn't cause significant difficulty so we're going to ignore + * it for now. So suppose we're given a number %$x = f b^e$%, in + * base-%$b$% format, so %$f b^n$% and %$e$% are integers, with + * %$0 \le f < 1$%. We're going to convert it into the nearest integer + * of the form %$X = F B^E$%, with similar conditions, only with the + * additional requirement that %$X$% is normalized, i.e., that %$X = 0$% + * or %$F \ge B^{-N}$%. + * + * We're rounding to the nearest such %$X$%. If there is to be ambiguity + * in the conversion, then some %$x = f b^e$% and the next smallest + * representable number %$x' = x + b^{e-n}$% must both map to the same + * %$X$%, which means both %$x$% and %$x'$% must be nearer to %$X$% than + * any other number representable in the target system. The nest larger + * number is %$X' = X + B^{E-N}$%; the next smaller number will normally + * be %$W = X - B^{E-N}$%, but if %$F = 1/B$ then the next smaller number + * is actually %$X - B^{E-N-1}$%. We ignore this latter possibility in + * the pursuit of a conservative estimate (though actually it doesn't + * matter). + * + * If both %$x$% and %$x'$% map to %$X$% then we must have + * %$L = X - B^{E-N}/2 \le x$% and %$x + b^{e-n} \le R = X + B^{E-N}/2$%; + * so firstly %$f b^e = x \ge L = W + B^{E-N}/2 > W = (F - B^{-N}) B^E$%, + * and secondly %$b^{e-n} \le B^{E-N}$%. Since these inequalities are in + * opposite senses, we can divide, giving + * + * %$f b^e/b^{e-n} > (F - B^{-N}) B^E/B^{E-N}$% , + * + * whence + * + * %$f b^n > (F - B^{-N}) B^N = F B^N - 1$% . + * + * Now %$f \le 1 - b^{-n}$%, and %$F \ge B^{-1}$%, so, for this to be + * possible, it must be the case that + * + * %$(1 - b^{-n}) b^n = b^n - 1 > B^{N-1} - 1$% . + * + * Then rearrange and take logarithms, obtaining + * + * %$(N - 1) \log B < n \log b$% , + * + * and so + * + * %$N < n \log b/\log B + 1$% . + * + * Recall that this is a necessary condition for a collision to occur; we + * are therefore safe whenever + * + * %$N \ge n \log b/\log B + 1$% ; + * + * so, taking ceilings, + * + * %$N \ge \lceil n \log b/\log B \rceil + 1$% . + * + * So that's why we have this. + * + * I'm going to assume that @n = DBL_MANT_DIG@ is sufficiently small that + * we can calculate this without ending up on the wrong side of an + * integer boundary. + * + * In C11, we have @DBL_DECIMAL_DIG@, which should be the same value only + * as a constant. Except that modern compilers are more than clever + * enough to work out that this is a constant anyway. + * + * This is sometimes an overestimate: we'll print out meaningless digits + * that don't represent anything we actually know about the number in + * question. To fix that, we'd need a complicated algorithm like Steele + * and White's Dragon4, Gay's @dtoa@, or Burger and Dybvig's algorithm + * (note that Loitsch's Grisu2 is conservative, and Grisu3 hands off to + * something else in difficult situations). + */ + + prec = ceil(DBL_MANT_DIG*log(FLT_RADIX)/log(10)) + 1; + gprintf(gops, go, "%.*g", prec, x); + } +} + +static int eqish_floating_p(double x, double y, + const struct tvec_floatinfo *fi) +{ + double xx, yy, t; + + if (NANP(x)) return (NANP(y)); else if (NANP(y)) return (0); + if (INFP(x)) return (x == y); else if (INFP(y)) return (0); + + switch (fi ? fi->f&TVFF_EQMASK : TVFF_EXACT) { + case TVFF_EXACT: + return (x == y); + case TVFF_ABSDELTA: + t = x - y; if (t < 0) t = -t; return (t < fi->delta); + case TVFF_RELDELTA: + xx = x >= 0 ? x : -x; yy = y >= 0 ? y : -y; + if (xx < yy) { t = x; x = y; y = t; } + return (1.0 - y/x < fi->delta); + default: + abort(); + } +} + +static int parse_floating(double *x_out, const char *p, + const struct tvec_floatinfo *fi, + struct tvec_state *tv) +{ + const char *pp; char *q; + dstr d = DSTR_INIT; + double x; + int olderr, rc; + + if (STRCMP(p, ==, "#nan")) { +#ifdef NAN + x = NAN; rc = 0; +#else + tvec_error(tv, "NaN not supported on this system"); + rc = -1; goto end; +#endif + } else if (STRCMP(p, ==, "#inf") || + STRCMP(p, ==, "#+inf") || + STRCMP(p, ==, "+#inf")) { +#ifdef NAN + x = INFINITY; rc = 0; +#else + tvec_error(tv, "infinity not supported on this system"); + rc = -1; goto end; +#endif + } else if (STRCMP(p, ==, "#-inf") || + STRCMP(p, ==, "-#inf")) { +#ifdef NAN + x = -INFINITY; rc = 0; +#else + tvec_error(tv, "infinity not supported on this system"); + rc = -1; goto end; +#endif + } else { + pp = p; + if (*pp == '+' || *pp == '-') pp++; + if (*pp == '.') pp++; + if (!ISDIGIT(*pp)) { + tvec_syntax(tv, *pp ? *pp : fgetc(tv->fp), "floating-point number"); + rc = -1; goto end; + } + olderr = errno; errno = 0; + x = strtod(p, &q); + if (*q) { + tvec_syntax(tv, *q, "end-of-line"); + rc = -1; goto end; + } + if (errno && (errno != ERANGE || (x > 0 ? -x : x) == HUGE_VAL)) { + tvec_error(tv, "invalid floating-point number `%s': %s", + p, strerror(errno)); + rc = -1; goto end; + } + errno = olderr; + } + + if (NANP(x) && fi && !(fi->f&TVFF_NANOK)) { + tvec_error(tv, "#nan not allowed here"); + rc = -1; goto end; + } + if (fi && ((!(fi->f&TVFF_NOMIN) && x < fi->min) || + (!(fi->f&TVFF_NOMAX) && x > fi->max))) { + dstr_puts(&d, "floating-point number "); + format_floating(&dstr_printops, &d, x); + dstr_puts(&d, " out of range (must be in "); + if (fi->f&TVFF_NOMIN) + dstr_puts(&d, "(#-inf"); + else + { dstr_putc(&d, '['); format_floating(&dstr_printops, &d, fi->min); } + dstr_puts(&d, " .. "); + if (fi->f&TVFF_NOMAX) + dstr_puts(&d, "#+inf)"); + else + { format_floating(&dstr_printops, &d, fi->max); dstr_putc(&d, ']'); } + dstr_putc(&d, ')'); dstr_putz(&d); + tvec_error(tv, "%s", d.buf); rc = -1; goto end; + } + + *x_out = x; rc = 0; +end: + dstr_destroy(&d); + return (rc); +} + static int convert_hex(char ch, int *v_out) { if ('0' <= ch && ch <= '9') { *v_out = ch - '0'; return (0); } @@ -161,76 +375,81 @@ static int convert_hex(char ch, int *v_out) else return (-1); } -static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv) +static int read_escape(int *ch_out, struct tvec_state *tv) { - char expect[4]; int ch, i, esc; unsigned f = 0; #define f_brace 1u - sprintf(expect, "`%c'", quote); + ch = getc(tv->fp); + switch (ch) { + case EOF: case '\n': tvec_syntax(tv, ch, "string escape"); + case '\'': *ch_out = '\''; break; + case '\\': *ch_out = '\\'; break; + case '"': *ch_out = '"'; break; + case 'a': *ch_out = '\a'; break; + case 'b': *ch_out = '\b'; break; + case 'e': *ch_out = '\x1b'; break; + case 'f': *ch_out = '\f'; break; + case 'n': *ch_out = '\n'; break; + case 'r': *ch_out = '\r'; break; + case 't': *ch_out = '\t'; break; + case 'v': *ch_out = '\v'; break; + + case 'x': + ch = getc(tv->fp); + if (ch == '{') { f |= f_brace; ch = getc(tv->fp); } + else f &= ~f_brace; + if (convert_hex(ch, &esc)) + return (tvec_syntax(tv, ch, "hex digit")); + for (;;) { + ch = getc(tv->fp); if (convert_hex(ch, &i)) break; + esc = 8*esc + i; + if (esc > UCHAR_MAX) + return (tvec_error(tv, + "character code %d out of range", esc)); + } + if (!(f&f_brace)) ungetc(ch, tv->fp); + else if (ch != '}') return (tvec_syntax(tv, ch, "`}'")); + *ch_out = esc; + break; + + default: + if ('0' <= ch && ch < '8') { + i = 1; esc = ch - '0'; + for (;;) { + ch = getc(tv->fp); + if ('0' > ch || ch >= '8') { ungetc(ch, tv->fp); break; } + esc = 8*esc + ch - '0'; + i++; if (i >= 3) break; + } + if (esc > UCHAR_MAX) + return (tvec_error(tv, + "character code %d out of range", esc)); + *ch_out = esc; + } else + return (tvec_syntax(tv, ch, "string escape")); + } + + return (0); + +#undef f_brace +} + +static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv) +{ + int ch; for (;;) { ch = getc(tv->fp); - reinsert: switch (ch) { case EOF: case '\n': - return (tvec_syntax(tv, ch, expect)); - + return (tvec_syntax(tv, ch, "`%c'", quote)); case '\\': if (quote == '\'') goto ordinary; - ch = getc(tv->fp); - switch (ch) { - case EOF: tvec_syntax(tv, ch, expect); - case '\n': tv->lno++; break; - case '\'': DPUTC(d, '\''); break; - case '\\': DPUTC(d, '\\'); break; - case '"': DPUTC(d, '"'); break; - case 'a': DPUTC(d, '\a'); break; - case 'b': DPUTC(d, '\b'); break; - case 'e': DPUTC(d, '\x1b'); break; - case 'f': DPUTC(d, '\f'); break; - case 'n': DPUTC(d, '\n'); break; - case 'r': DPUTC(d, '\r'); break; - case 't': DPUTC(d, '\t'); break; - case 'v': DPUTC(d, '\v'); break; - - case 'x': - ch = getc(tv->fp); - if (ch == '{') { f |= f_brace; ch = getc(tv->fp); } - else f &= ~f_brace; - if (convert_hex(ch, &esc)) - return (tvec_syntax(tv, ch, "hex digit")); - for (;;) { - ch = getc(tv->fp); if (convert_hex(ch, &i)) break; - esc = 8*esc + i; - if (esc > UCHAR_MAX) - return (tvec_error(tv, - "character code %d out of range", esc)); - } - DPUTC(d, esc); - if (!(f&f_brace)) goto reinsert; - else if (ch != '}') return (tvec_syntax(tv, ch, "`}'")); - break; - - default: - if ('0' <= ch && ch < '8') { - i = 1; esc = ch - '0'; - for (;;) { - ch = getc(tv->fp); - if (i > 3 || '0' > ch || ch >= '8') break; - esc = 8*esc + ch - '0'; i++; - } - if (esc > UCHAR_MAX) - return (tvec_error(tv, - "character code %d out of range", esc)); - DPUTC(d, esc); - goto reinsert; - } - return (tvec_syntax(tv, ch, "string escape")); - } - break; - + ch = getc(tv->fp); if (ch == '\n') { tv->lno++; break; } + ungetc(ch, tv->fp); if (read_escape(&ch, tv)) return (-1); + goto ordinary; default: if (ch == quote) goto end; ordinary: @@ -242,8 +461,43 @@ static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv) end: DPUTZ(d); return (0); +} -#undef f_brace +#define FCF_BRACE 1u +static void format_charesc(int ch, unsigned f, + const struct gprintf_ops *gops, void *go) +{ + switch (ch) { + case '\a': gprintf(gops, go, "\\a"); break; + case '\b': gprintf(gops, go, "\\b"); break; + case '\x1b': gprintf(gops, go, "\\e"); break; + case '\f': gprintf(gops, go, "\\f"); break; + case '\r': gprintf(gops, go, "\\r"); break; + case '\n': gprintf(gops, go, "\\n"); break; + case '\t': gprintf(gops, go, "\\t"); break; + case '\v': gprintf(gops, go, "\\v"); break; + case '\'': gprintf(gops, go, "\\'"); break; + case '"': gprintf(gops, go, "\\\""); break; + default: + if (f&FCF_BRACE) + gprintf(gops, go, "\\x{%0*x}", hex_width(UCHAR_MAX), ch); + else + gprintf(gops, go, "\\x%0*x", hex_width(UCHAR_MAX), ch); + break; + } +} + +static void format_char(int ch, const struct gprintf_ops *gops, void *go) +{ + if (ch == EOF) + gprintf(gops, go, "#eof"); + else if (isprint(ch) && ch != '\'') + gprintf(gops, go, "'%c'", ch); + else { + gprintf(gops, go, "'"); + format_charesc(ch, 0, gops, go); + gprintf(gops, go, "'"); + } } enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 }; @@ -378,20 +632,18 @@ static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd) static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd) static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1, const struct tvec_regdef *rd) -static size_t measure_...(const union tvec_regval *rv, - const struct tvec_regdef *rd) static int tobuf_...(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) static int frombuf_...(buf *b, union tvec_regval *rv, const struct tvec_regdef *rd) -static void parse_...(union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv) +static int parse_...(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) static void dump_...(const union tvec_regval *rv, const struct tvec_regdef *rd, struct tvec_state *tv, unsigned style) const struct tvec_regty tvty_... = { - init_..., release_..., eq_..., measure_..., + init_..., release_..., eq_..., tobuf_..., frombuf_..., parse_..., dump_... }; @@ -416,10 +668,6 @@ static int eq_uint(const union tvec_regval *rv0, const struct tvec_regdef *rd) { return (rv0->u == rv1->u); } -static size_t measure_int(const union tvec_regval *rv, - const struct tvec_regdef *rd) - { return (8); } - static int tobuf_int(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) { return (signed_to_buf(b, rv->i)); } @@ -442,9 +690,12 @@ static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd, dstr d = DSTR_INIT; int rc; - if (tvec_readword(tv, &d, ";", "signed integer")) { rc = -1; goto end; } - if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) { rc = -1; goto end; } - if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } + if (tvec_readword(tv, &d, ";", "signed integer")) + { rc = -1; goto end; } + if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) + { rc = -1; goto end; } + if (tvec_flushtoeol(tv, 0)) + { rc = -1; goto end; } rc = 0; end: dstr_destroy(&d); @@ -457,9 +708,12 @@ static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd, dstr d = DSTR_INIT; int rc; - if (tvec_readword(tv, &d, ";", "unsigned integer")) { rc = -1; goto end; } - if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) { rc = -1; goto end; } - if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } + if (tvec_readword(tv, &d, ";", "unsigned integer")) + { rc = -1; goto end; } + if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) + { rc = -1; goto end; } + if (tvec_flushtoeol(tv, 0)) + { rc = -1; goto end; } rc = 0; end: dstr_destroy(&d); @@ -468,29 +722,37 @@ end: static void dump_int(const union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv, unsigned style) + unsigned style, + const struct gprintf_ops *gops, void *go) { unsigned long u; - tvec_write(tv, "%ld", rv->i); + gprintf(gops, go, "%ld", rv->i); if (!(style&TVSF_COMPACT)) { if (rv->i >= 0) u = rv->i; else u = -(unsigned long)rv->i; - tvec_write(tv, " ; = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u); + gprintf(gops, go, " ; = %s0x%0*lx", + rv->i < 0 ? "-" : "", hex_width(u), u); + if (rv->i == EOF || (0 <= rv->i && rv->i < UCHAR_MAX)) + { gprintf(gops, go, " = "); format_char(rv->i, gops, go); } } } static void dump_uint(const union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv, unsigned style) + unsigned style, + const struct gprintf_ops *gops, void *go) { - tvec_write(tv, "%lu", rv->u); - if (!(style&TVSF_COMPACT)) - tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u); + gprintf(gops, go, "%lu", rv->u); + if (!(style&TVSF_COMPACT)) { + gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u); + if (rv->u < UCHAR_MAX) + { gprintf(gops, go, " = "); format_char(rv->u, gops, go); } + } } const struct tvec_regty tvty_int = { - init_int, release_int, eq_int, measure_int, + init_int, release_int, eq_int, tobuf_int, frombuf_int, parse_int, dump_int }; @@ -505,7 +767,7 @@ const struct tvec_irange tvrange_i32 = { -2147483648, 2147483647 }; const struct tvec_regty tvty_uint = { - init_uint, release_int, eq_uint, measure_int, + init_uint, release_int, eq_uint, tobuf_uint, frombuf_uint, parse_uint, dump_uint }; @@ -535,6 +797,77 @@ int tvec_claimeq_uint(struct tvec_state *tv, return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr)); } +/*----- Main code ---------------------------------------------------------*/ + +static void init_float(union tvec_regval *rv, const struct tvec_regdef *rd) + { rv->f = 0.0; } +static void release_float(union tvec_regval *rv, + const struct tvec_regdef *rd) + { ; } + +static int eq_float(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) + { return (eqish_floating_p(rv0->f, rv1->f, rd->arg.p)); } + +static int tobuf_float(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (buf_putf64l(b, rv->f)); } +static int frombuf_float(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (buf_getf64l(b, &rv->f)); } + +static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + dstr d = DSTR_INIT; + int rc; + + if (tvec_readword(tv, &d, ";", "floating-point number")) + { rc = -1; goto end; } + if (parse_floating(&rv->f, d.buf, rd->arg.p, tv)) + { rc = -1; goto end; } + if (tvec_flushtoeol(tv, 0)) + { rc = -1; goto end; } + rc = 0; +end: + dstr_destroy(&d); + return (rc); +} + +static void dump_float(const union tvec_regval *rv, + const struct tvec_regdef *rd, + unsigned style, + const struct gprintf_ops *gops, void *go) + { format_floating(gops, go, rv->f); } + +const struct tvec_regty tvty_float = { + init_float, release_float, eq_float, + tobuf_float, frombuf_float, + parse_float, dump_float +}; + +int tvec_claimeqish_float(struct tvec_state *tv, + double f0, double f1, unsigned f, double delta, + const char *file, unsigned lno, + const char *expr) +{ + struct tvec_floatinfo fi; + union tvec_misc arg; + + fi.f = f; fi.min = fi.max = 0.0; fi.delta = delta; arg.p = &fi; + tv->in[0].v.f = f0; tv->in[1].v.f = f1; + return (tvec_claimeq(tv, &tvty_float, &arg, file, lno, expr)); +} +int tvec_claimeq_float(struct tvec_state *tv, + double f0, double f1, + const char *file, unsigned lno, + const char *expr) +{ + return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0, + file, lno, expr)); +} + /*----- Enumerations ------------------------------------------------------*/ static void init_enum(union tvec_regval *rv, const struct tvec_regdef *rd) @@ -555,12 +888,29 @@ static int eq_enum(const union tvec_regval *rv0, const struct tvec_regdef *rd) { const struct tvec_enuminfo *ei = rd->arg.p; + const struct tvec_fenuminfo *fei; switch (ei->mv) { #define CASE(tag, ty, slot) \ - case TVMISC_##tag: return (rv0->slot == rv1->slot); + case TVMISC_##tag: PREP_##tag return (HANDLE_##tag); +#define PREP_INT +#define HANDLE_INT rv0->i == rv1->i +#define PREP_UINT +#define HANDLE_UINT rv0->u == rv1->u +#define PREP_FLT fei = (const struct tvec_fenuminfo *)ei; +#define HANDLE_FLT eqish_floating_p(rv0->f, rv1->f, fei->fi) +#define PREP_PTR +#define HANDLE_PTR rv0->p == rv1->p TVEC_MISCSLOTS(CASE) #undef CASE +#undef PREP_INT +#undef HANDLE_INT +#undef PREP_UINT +#undef HANDLE_UINT +#undef PREP_FLT +#undef HANDLE_FLT +#undef PREP_PTR +#undef HANDLE_PTR default: abort(); } } @@ -569,17 +919,29 @@ static int tobuf_enum(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) { const struct tvec_enuminfo *ei = rd->arg.p; + const struct tvec_penuminfo *pei; + const struct tvec_passoc *pa; + long i; switch (ei->mv) { #define CASE(tag, ty, slot) \ - case TVMISC_##tag: return (HANDLE_##tag); -#define HANDLE_INT signed_to_buf(b, rv->i) -#define HANDLE_UINT unsigned_to_buf(b, rv->u) -#define HANDLE_PTR -1 + case TVMISC_##tag: HANDLE_##tag +#define HANDLE_INT return (signed_to_buf(b, rv->i)); +#define HANDLE_UINT return (unsigned_to_buf(b, rv->u)); +#define HANDLE_FLT return (buf_putf64l(b, rv->f)); +#define HANDLE_PTR \ + pei = (const struct tvec_penuminfo *)ei; \ + for (pa = pei->av, i = 0; pa->tag; pa++, i++) \ + if (pa->p == rv->p) goto found; \ + if (!rv->p) i = -1; \ + else return (-1); \ + found: \ + return (signed_to_buf(b, i)); TVEC_MISCSLOTS(CASE) #undef CASE #undef HANDLE_INT #undef HANDLE_UINT +#undef HANDLE_FLT #undef HANDLE_PTR default: abort(); } @@ -590,17 +952,29 @@ static int frombuf_enum(buf *b, union tvec_regval *rv, const struct tvec_regdef *rd) { const struct tvec_enuminfo *ei = rd->arg.p; + const struct tvec_penuminfo *pei; + const struct tvec_passoc *pa; + long i, n; switch (ei->mv) { #define CASE(tag, ty, slot) \ - case TVMISC_##tag: return (HANDLE_##tag); -#define HANDLE_INT signed_from_buf(b, &rv->i) -#define HANDLE_UINT unsigned_from_buf(b, &rv->u) -#define HANDLE_PTR -1 + case TVMISC_##tag: HANDLE_##tag +#define HANDLE_INT return (signed_from_buf(b, &rv->i)); +#define HANDLE_UINT return (unsigned_from_buf(b, &rv->u)); +#define HANDLE_FLT return (buf_getf64l(b, &rv->f)); +#define HANDLE_PTR \ + pei = (const struct tvec_penuminfo *)ei; \ + for (pa = pei->av, n = 0; pa->tag; pa++, n++); \ + if (signed_from_buf(b, &i)) return (-1); \ + if (0 <= i && i < n) rv->p = (/*unconst*/ void *)pei->av[i].p; \ + else if (i == -1) rv->p = 0; \ + else return (-1); \ + return (0); TVEC_MISCSLOTS(CASE) #undef CASE #undef HANDLE_INT #undef HANDLE_UINT +#undef HANDLE_FLT #undef HANDLE_PTR default: abort(); } @@ -611,6 +985,7 @@ static int parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd, { const struct tvec_enuminfo *ei = rd->arg.p; #define DECLS(tag, ty, slot) \ + const struct tvec_##slot##enuminfo *slot##ei; \ const struct tvec_##slot##assoc *slot##a; TVEC_MISCSLOTS(DECLS) #undef DECLS @@ -620,39 +995,48 @@ static int parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd, if (tvec_readword(tv, &d, ";", "enumeration tag or literal integer")) { rc = -1; goto end; } switch (ei->mv) { + #define CASE(tag_, ty, slot) \ case TVMISC_##tag_: \ - for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++) \ + slot##ei = (const struct tvec_##slot##enuminfo *)ei; \ + for (slot##a = slot##ei->av; slot##a->tag; slot##a++) \ if (STRCMP(d.buf, ==, slot##a->tag)) \ - { rv->slot = FETCH_##tag_; goto done; } -#define FETCH_INT (ia->i) -#define FETCH_UINT (ua->u) -#define FETCH_PTR ((/*unconst*/ void *)(pa->p)) - TVEC_MISCSLOTS(CASE) -#undef CASE -#undef FETCH_INT -#undef FETCH_UINT -#undef FETCH_PTR - } + { rv->slot = FETCH_##tag_; goto done; } \ + HANDLE_##tag_ goto done; - switch (ei->mv) { -#define CASE(tag, ty, slot) \ - case TVMISC_##tag: HANDLE_##tag goto done; +#define FETCH_INT (ia->i) #define HANDLE_INT \ - if (parse_signed(&rv->i, d.buf, ei->u.i.ir, tv)) \ + if (parse_signed(&rv->i, d.buf, iei->ir, tv)) \ { rc = -1; goto end; } + +#define FETCH_UINT (ua->u) #define HANDLE_UINT \ - if (parse_unsigned(&rv->u, d.buf, ei->u.u.ur, tv)) \ + if (parse_unsigned(&rv->u, d.buf, uei->ur, tv)) \ { rc = -1; goto end; } + +#define FETCH_FLT (fa->f) +#define HANDLE_FLT \ + if (parse_floating(&rv->f, d.buf, fei->fi, tv)) \ + { rc = -1; goto end; } + +#define FETCH_PTR ((/*unconst*/ void *)(pa->p)) #define HANDLE_PTR \ if (STRCMP(d.buf, ==, "#nil")) rv->p = 0; \ else goto tagonly; + TVEC_MISCSLOTS(CASE) + #undef CASE +#undef FETCH_INT #undef HANDLE_INT +#undef FETCH_UINT #undef HANDLE_UINT +#undef FETCH_FLT +#undef HANDLE_FLT +#undef FETCH_PTR #undef HANDLE_PTR - default: tagonly: + + tagonly: tvec_error(tv, "unknown `%s' value `%s'", ei->name, d.buf); rc = -1; goto end; } @@ -667,10 +1051,12 @@ end: static void dump_enum(const union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv, unsigned style) + unsigned style, + const struct gprintf_ops *gops, void *go) { const struct tvec_enuminfo *ei = rd->arg.p; #define DECLS(tag, ty, slot) \ + const struct tvec_##slot##enuminfo *slot##ei; \ const struct tvec_##slot##assoc *slot##a; TVEC_MISCSLOTS(DECLS) #undef DECLS @@ -682,7 +1068,8 @@ static void dump_enum(const union tvec_regval *rv, switch (ei->mv) { #define CASE(tag_, ty, slot) \ case TVMISC_##tag_: \ - for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++) \ + slot##ei = (const struct tvec_##slot##enuminfo *)ei; \ + for (slot##a = slot##ei->av; slot##a->tag; slot##a++) \ if (rv->slot == slot##a->slot) \ { tag = slot##a->tag; goto found; } \ break; @@ -690,59 +1077,86 @@ static void dump_enum(const union tvec_regval *rv, #undef CASE default: abort(); } - goto print_int; + goto print_raw; found: f |= f_known; - tvec_write(tv, "%s", tag); + gprintf(gops, go, "%s", tag); if (style&TVSF_COMPACT) return; - tvec_write(tv, " ; = "); + gprintf(gops, go, " ; = "); -print_int: +print_raw: switch (ei->mv) { #define CASE(tag, ty, slot) \ case TVMISC_##tag: HANDLE_##tag break; -#define HANDLE_INT tvec_write(tv, "%ld", rv->i); -#define HANDLE_UINT tvec_write(tv, "%lu", rv->u); -#define HANDLE_PTR if (!rv->p) tvec_write(tv, "#nil"); \ - else tvec_write(tv, "#<%s %p>", ei->name, rv->p); +#define HANDLE_INT gprintf(gops, go, "%ld", rv->i); +#define HANDLE_UINT gprintf(gops, go, "%lu", rv->u); +#define HANDLE_FLT format_floating(gops, go, rv->f); +#define HANDLE_PTR if (!rv->p) gprintf(gops, go, "#nil"); \ + else gprintf(gops, go, "#<%s %p>", ei->name, rv->p); TVEC_MISCSLOTS(CASE) #undef CASE #undef HANDLE_INT #undef HANDLE_UINT +#undef HANDLE_FLT #undef HANDLE_PTR } + if (style&TVSF_COMPACT) return; switch (ei->mv) { case TVMISC_INT: - if (!(f&f_known)) tvec_write(tv, " ;"); + if (!(f&f_known)) gprintf(gops, go, " ;"); if (rv->i >= 0) u = rv->i; else u = -(unsigned long)rv->i; - tvec_write(tv, " = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u); + gprintf(gops, go, " = %s0x%0*lx", + rv->i < 0 ? "-" : "", hex_width(u), u); + if (rv->i == EOF || (0 <= rv->i && rv->i < UCHAR_MAX)) + { gprintf(gops, go, " = "); format_char(rv->i, gops, go); } break; case TVMISC_UINT: - if (!(f&f_known)) tvec_write(tv, " ;"); - tvec_write(tv, " = 0x%0*lx", hex_width(rv->u), rv->u); + if (!(f&f_known)) gprintf(gops, go, " ;"); + gprintf(gops, go, " = 0x%0*lx", hex_width(rv->u), rv->u); + if (rv->u < UCHAR_MAX) + { gprintf(gops, go, " = "); format_char(rv->u, gops, go); } break; } } const struct tvec_regty tvty_enum = { - init_enum, release_int, eq_enum, measure_int, + init_enum, release_int, eq_enum, tobuf_enum, frombuf_enum, parse_enum, dump_enum }; +static const struct tvec_iassoc bool_assoc[] = { + { "nil", 0 }, + { "false", 0 }, + { "f", 0 }, + { "no", 0 }, + { "n", 0 }, + { "off", 0 }, + + { "t", 1 }, + { "true", 1 }, + { "yes", 1 }, + { "y", 1 }, + { "on", 1 }, + + { 0, 0 } +}; + +const struct tvec_ienuminfo tvenum_bool = + { { "bool", TVMISC_INT }, bool_assoc, &tvrange_int }; + #define DEFCLAIM(tag, ty, slot) \ - int tvec_claimeq_##slot##enum(struct tvec_state *tv, \ - const struct tvec_enuminfo *ei, \ - ty e0, ty e1, \ - const char *file, unsigned lno, \ - const char *expr) \ + int tvec_claimeq_##slot##enum \ + (struct tvec_state *tv, \ + const struct tvec_##slot##enuminfo *ei, ty e0, ty e1, \ + const char *file, unsigned lno, const char *expr) \ { \ union tvec_misc arg; \ \ - assert(ei->mv == TVMISC_##tag); \ + assert(ei->_ei.mv == TVMISC_##tag); \ arg.p = ei; \ tv->in[0].v.slot = GET_##tag(e0); \ tv->out[0].v.slot = GET_##tag(e1); \ @@ -750,11 +1164,13 @@ const struct tvec_regty tvty_enum = { } #define GET_INT(e) (e) #define GET_UINT(e) (e) +#define GET_FLT(e) (e) #define GET_PTR(e) ((/*unconst*/ void *)(e)) TVEC_MISCSLOTS(DEFCLAIM) #undef DEFCLAIM #undef GET_INT #undef GET_UINT +#undef GET_FLT #undef GET_PTR /*----- Flag types --------------------------------------------------------*/ @@ -781,7 +1197,8 @@ static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd, { m |= f->m; v |= f->v; goto next; } } - if (parse_unsigned(&t, d.buf, fi->range, tv)) { rc = -1; goto end; } + if (parse_unsigned(&t, d.buf, fi->range, tv)) + { rc = -1; goto end; } v |= t; next: if (tvec_nexttoken(tv)) break; @@ -799,7 +1216,8 @@ end: static void dump_flags(const union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv, unsigned style) + unsigned style, + const struct gprintf_ops *gops, void *go) { const struct tvec_flaginfo *fi = rd->arg.p; const struct tvec_flag *f; @@ -808,18 +1226,18 @@ static void dump_flags(const union tvec_regval *rv, for (f = fi->fv, sep = ""; f->tag; f++) if ((m&f->m) && (v&f->m) == f->v) { - tvec_write(tv, "%s%s", sep, f->tag); m &= ~f->m; + gprintf(gops, go, "%s%s", sep, f->tag); m &= ~f->m; sep = style&TVSF_COMPACT ? "|" : " | "; } - if (v&m) tvec_write(tv, "%s0x%0*lx", sep, hex_width(v), v&m); + if (v&m) gprintf(gops, go, "%s0x%0*lx", sep, hex_width(v), v&m); if (!(style&TVSF_COMPACT)) - tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u); + gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u); } const struct tvec_regty tvty_flags = { - init_uint, release_int, eq_uint, measure_int, + init_uint, release_int, eq_uint, tobuf_uint, frombuf_uint, parse_flags, dump_flags }; @@ -835,6 +1253,107 @@ int tvec_claimeq_flags(struct tvec_state *tv, return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr)); } +/*----- Characters --------------------------------------------------------*/ + +static int tobuf_char(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + uint32 u; + if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i; + else if (rv->i == EOF) u = MASK32; + else return (-1); + return (buf_putu32l(b, u)); +} + +static int frombuf_char(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + uint32 u; + + if (buf_getu32l(b, &u)) return (-1); + if (0 <= u && u <= UCHAR_MAX) rv->i = u; + else if (u == MASK32) rv->i = EOF; + else return (-1); + return (0); +} + +static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + dstr d = DSTR_INIT; + int ch, rc; + unsigned f = 0; +#define f_quote 1u + + ch = getc(tv->fp); + if (ch == '#') { + ungetc(ch, tv->fp); + if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; } + if (STRCMP(d.buf, ==, "#eof")) + rv->i = EOF; + else { + rc = tvec_error(tv, "unknown character name `%s'", d.buf); + goto end; + } + rc = 0; goto end; + } + + if (ch == '\'') { f |= f_quote; ch = getc(tv->fp); } + switch (ch) { + case '\'': + if (!(f&f_quote)) goto plain; + case EOF: case '\n': + rc = tvec_syntax(tv, ch, "character"); goto end; + case '\\': + if (read_escape(&ch, tv)) return (-1); + default: plain: + rv->i = ch; break; + } + if (f&f_quote) { + ch = getc(tv->fp); + if (ch != '\'') { rc = tvec_syntax(tv, ch, "`''"); goto end; } + } + if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } + rc = 0; +end: + dstr_destroy(&d); + return (rc); + +#undef f_quote +} + +static void dump_char(const union tvec_regval *rv, + const struct tvec_regdef *rd, + unsigned style, + const struct gprintf_ops *gops, void *go) +{ + unsigned u; + + if ((style&TVSF_COMPACT) && isprint(rv->i) && rv->i != '\'') + gprintf(gops, go, "%c", (int)rv->i); + else + format_char(rv->i, gops, go); + + if (!(style&TVSF_COMPACT)) { + u = rv->i < 0 ? -rv->i : rv->i; + gprintf(gops, go, " ; = %d = %s0x%0*x", + (int)rv->i, rv->i < 0 ? "-" : "", hex_width(u), u); + } +} + +const struct tvec_regty tvty_char = { + init_int, release_int, eq_int, + tobuf_char, frombuf_char, + parse_char, dump_char +}; + +int tvec_claimeq_char(struct tvec_state *tv, int c0, int c1, + const char *file, unsigned lno, const char *expr) +{ + tv->in[0].v.i = c0; tv->out[0].v.i = c1; + return (tvec_claimeq(tv, &tvty_char, 0, file, lno, expr)); +} + /*----- Text and byte strings ---------------------------------------------*/ void tvec_allocstring(union tvec_regval *rv, size_t sz) @@ -881,14 +1400,6 @@ static int eq_bytes(const union tvec_regval *rv0, MEMCMP(rv0->bytes.p, ==, rv1->bytes.p, rv1->bytes.sz))); } -static size_t measure_string(const union tvec_regval *rv, - const struct tvec_regdef *rd) - { return (rv->str.sz + 4); } - -static size_t measure_bytes(const union tvec_regval *rv, - const struct tvec_regdef *rd) - { return (rv->bytes.sz + 4); } - static int tobuf_string(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) { return (buf_putmem32l(b, rv->str.p, rv->str.sz)); } @@ -953,59 +1464,35 @@ static int parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd, static void dump_string(const union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv, unsigned style) + unsigned style, + const struct gprintf_ops *gops, void *go) { const unsigned char *p, *q, *l; - int ch; unsigned f = 0; #define f_nonword 1u #define f_newline 2u - if (!rv->str.sz) { tvec_write(tv, "\"\""); return; } + if (!rv->str.sz) { gprintf(gops, go, "\"\""); return; } p = (const unsigned char *)rv->str.p; l = p + rv->str.sz; if (*p == '!' || *p == ';' || *p == '"' || *p == '\'') goto quote; for (q = p; q < l; q++) if (*q == '\n' && q != l - 1) f |= f_newline; else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword; - if (f&f_newline) { tvec_write(tv, "\n\t"); goto quote; } + if (f&f_newline) { gprintf(gops, go, "\n\t"); goto quote; } else if (f&f_nonword) goto quote; - tv->output->ops->write(tv->output, (const char *)p, rv->str.sz); return; + gops->putm(go, (const char *)p, rv->str.sz); return; quote: - tvec_write(tv, "\""); + gprintf(gops, go, "\""); for (q = p; q < l; q++) - switch (*q) { - case '"': case '\\': ch = *q; goto escape; - case '\a': ch = 'a'; goto escape; - case '\b': ch = 'b'; goto escape; - case '\x1b': ch = 'e'; goto escape; - case '\f': ch = 'f'; goto escape; - case '\r': ch = 'r'; goto escape; - case '\t': ch = 't'; goto escape; - case '\v': ch = 'v'; goto escape; - escape: - if (p < q) - tv->output->ops->write(tv->output, (const char *)p, q - p); - tvec_write(tv, "\\%c", ch); p = q + 1; - break; - - case '\n': - if (p < q) - tv->output->ops->write(tv->output, (const char *)p, q - p); - tvec_write(tv, "\\n"); p = q + 1; - if (!(style&TVSF_COMPACT) && q < l) tvec_write(tv, "\"\t\""); - break; - - default: - if (isprint(*q)) break; - if (p < q) - tv->output->ops->write(tv->output, (const char *)p, q - p); - tvec_write(tv, "\\x{%0*x}", hex_width(UCHAR_MAX), *q); p = q + 1; - break; + if (!isprint(*q) || *q == '"') { + if (p < q) gops->putm(go, (const char *)p, q - p); + if (*q == '\n' && !(style&TVSF_COMPACT))gprintf(gops, go, "\\n\"\t\""); + else format_charesc(*q, FCF_BRACE, gops, go); } - if (p < q) tv->output->ops->write(tv->output, (const char *)p, q - p); - tvec_write(tv, "\""); + if (p < q) gops->putm(go, (const char *)p, q - p); + gprintf(gops, go, "\""); #undef f_nonword #undef f_newline @@ -1013,7 +1500,8 @@ quote: static void dump_bytes(const union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv, unsigned style) + unsigned style, + const struct gprintf_ops *gops, void *go) { const unsigned char *p = rv->bytes.p, *l = p + rv->bytes.sz; size_t off, sz = rv->bytes.sz; @@ -1021,16 +1509,16 @@ static void dump_bytes(const union tvec_regval *rv, int wd; if (!sz) { - tvec_write(tv, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty"); + gprintf(gops, go, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty"); return; } if (style&TVSF_COMPACT) { - while (p < l) tvec_write(tv, "%02x", *p++); + while (p < l) gprintf(gops, go, "%02x", *p++); return; } - if (sz > 16) tvec_write(tv, "\n\t"); + if (sz > 16) gprintf(gops, go, "\n\t"); off = 0; wd = hex_width(sz); while (p < l) { @@ -1038,27 +1526,27 @@ static void dump_bytes(const union tvec_regval *rv, else n = 16; for (i = 0; i < 16; i++) { - if (i < n) tvec_write(tv, "%02x", p[i]); - else tvec_write(tv, " "); - if (i%4 == 3) tvec_write(tv, " "); + if (i < n) gprintf(gops, go, "%02x", p[i]); + else gprintf(gops, go, " "); + if (i%4 == 3) gprintf(gops, go, " "); } - tvec_write(tv, " ; "); - if (sz > 16) tvec_write(tv, "[%0*lx] ", wd, (unsigned long)off); + gprintf(gops, go, " ; "); + if (sz > 16) gprintf(gops, go, "[%0*lx] ", wd, (unsigned long)off); for (i = 0; i < n; i++) - tvec_write(tv, "%c", isprint(p[i]) ? p[i] : '.'); + gprintf(gops, go, "%c", isprint(p[i]) ? p[i] : '.'); p += n; off += n; - if (p < l) tvec_write(tv, "\n\t"); + if (p < l) gprintf(gops, go, "\n\t"); } } const struct tvec_regty tvty_string = { - init_string, release_string, eq_string, measure_string, + init_string, release_string, eq_string, tobuf_string, frombuf_string, parse_string, dump_string }; const struct tvec_regty tvty_bytes = { - init_bytes, release_bytes, eq_bytes, measure_bytes, + init_bytes, release_bytes, eq_bytes, tobuf_bytes, frombuf_bytes, parse_bytes, dump_bytes }; @@ -1175,21 +1663,22 @@ rangerr: static void dump_buffer(const union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv, unsigned style) + unsigned style, + const struct gprintf_ops *gops, void *go) { const char *unit; unsigned long u = rv->bytes.sz; if (!u || u%1024) - tvec_write(tv, "%lu B", u); + gprintf(gops, go, "%lu B", u); else { for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++); - tvec_write(tv, "%lu %cB", u, *unit); + gprintf(gops, go, "%lu %cB", u, *unit); } } const struct tvec_regty tvty_buffer = { - init_bytes, release_bytes, eq_buffer, measure_int, + init_bytes, release_bytes, eq_buffer, tobuf_buffer, frombuf_buffer, parse_buffer, dump_buffer }; diff --git a/test/tvec.3 b/test/tvec.3 new file mode 100644 index 0000000..06a1211 --- /dev/null +++ b/test/tvec.3 @@ -0,0 +1,24 @@ +.\" -*-nroff-*- +.TH tvec 3 "10 May 2023" "Straylight/Edgeware" "mLib utilities library" +.SH NAME +tvec \- test vector framework + + + + +.SH SYNOPSIS +.nf +.B "#include + + + +extern int tvec_serialize(const struct tvec_reg */*rv*/, + const struct tvec_regdef */*regs*/, + unsigned /*nr*/, size_t /*regsz*/, + void **/*p_out*/, size_t */*sz_out*/); + +extern int tvec_deserialize(struct tvec_reg */*rv*/, + const struct tvec_regdef */*regs*/, + unsigned /*nr*/, size_t /*regsz*/, + const void */*p*/, size_t /*sz*/); + diff --git a/test/tvec.h b/test/tvec.h index 2309edf..2f1cc56 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -51,6 +51,10 @@ # include "dstr.h" #endif +#ifndef MLIB_GPRINTF_H +# include "gprintf.h" +#endif + #ifndef MLIB_MACROS_H # include "macros.h" #endif @@ -64,7 +68,8 @@ #define TVEC_MISCSLOTS(_) \ _(PTR, const void *, p) /* arbitrary pointer */ \ _(INT, long, i) /* signed integer */ \ - _(UINT, unsigned long, u) /* signed integer */ + _(UINT, unsigned long, u) /* signed integer */ \ + _(FLT, double, f) /* floating point */ union tvec_misc { #define TVEC_DEFSLOT(tag, ty, slot) ty slot; @@ -114,6 +119,7 @@ union tvec_regval { long i; /* signed integer */ unsigned long u; /* unsigned integer */ void *p; /* pointer */ + double f; /* floating point */ struct { unsigned char *p; size_t sz; } bytes; /* binary string of bytes */ struct { char *p; size_t sz; } str; /* text string */ #ifdef TVEC_REGSLOTS @@ -164,21 +170,102 @@ struct tvec_regdef { union tvec_misc arg; /* extra detail for the type */ }; -extern int tvec_serialize(const struct tvec_reg */*rv*/, +/* @TVEC_GREG(vec, i, regsz)@ + * + * If @vec@ is a data pointer which happens to contain the address of a + * vector of @struct tvec_reg@ objects, @i@ is an integer, and @regsz@ is the + * size of a @struct tvec_reg@, then this evaluates to the address of the + * @i@th element of the vector. + * + * This is the general tool you need for accessing register vectors when you + * don't have absolute knowledge of the size of a @union tvec_regval@. + * Usually you want to access one of the register vectors in a @struct + * tvec_state@, and @TVEC_REG@ will be more convenient. + */ +#define TVEC_GREG(vec, i, regsz) \ + ((struct tvec_reg *)((unsigned char *)(vec) + (i)*(regsz))) + +/*------ Serialization utilities ------------------------------------------*/ + +/* --- @tvec_serialize@ --- * + * + * Arguments: @const struct tvec_reg *rv@ = vector of registers + * @buf *b@ = buffer to write on + * @const struct tvec_regdef *regs@ = vector of register + * descriptions, terminated by an entry with a null + * @name@ slot + * @unsigned nr@ = number of entries in the @rv@ vector + * @size_t regsz@ = true size of each element of @rv@ + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Serialize a collection of register values. + * + * The `candidate register definitions' are those entries @r@ in + * the @regs@ vector whose index @r.i@ is strictly less than + * @nr@. The `selected register definitions' are those + * candidate register definitions @r@ for which the indicated + * register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The + * serialized output begins with a header bitmap: if there are + * %$n$% candidate register definitions then the header bitmap + * consists of %$\lceil n/8 \rceil$% bytes. Bits are ordered + * starting from the least significant bit of the first byte, + * end ending at the most significant bit of the final byte. + * The bit corresponding to a candidate register definition is + * set if and only if that register defintion is selected. The + * header bitmap is then followed by the serializations of the + * selected registers -- i.e., for each selected register + * definition @r@, the serialized value of register @rv[r.i]@ -- + * simply concatenated together, with no padding or alignment. + * + * The serialized output is written to the buffer @b@. Failure + * can be caused by running out of buffer space, or a failing + * type handler. + */ + +extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/, const struct tvec_regdef */*regs*/, - unsigned /*nr*/, size_t /*regsz*/, - void **/*p_out*/, size_t */*sz_out*/); + unsigned /*nr*/, size_t /*regsz*/); -extern int tvec_deserialize(struct tvec_reg */*rv*/, +/* --- @tvec_deserialize@ --- * + * + * Arguments: @struct tvec_reg *rv@ = vector of registers + * @buf *b@ = buffer to write on + * @const struct tvec_regdef *regs@ = vector of register + * descriptions, terminated by an entry with a null + * @name@ slot + * @unsigned nr@ = number of entries in the @rv@ vector + * @size_t regsz@ = true size of each element of @rv@ + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Deserialize a collection of register values. + * + * The size of the register vector @nr@ and the register + * definitions @regs@ must match those used when producing the + * serialization. For each serialized register value, + * deserialize and store the value into the appropriate register + * slot, and set the @TVRF_LIVE@ flag on the register. See + * @tvec_serialize@ for a description of the format. + * + * On successful completion, store the address of the first byte + * after the serialized data in @*end_out@ if @end_out@ is not + * null. Failure results only from a failing register type + * handler. + */ + +extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, const struct tvec_regdef */*regs*/, - unsigned /*nr*/, size_t /*regsz*/, - const void */*p*/, size_t /*sz*/); + unsigned /*nr*/, size_t /*regsz*/); /*----- Test state --------------------------------------------------------*/ +/* Possible test outcomes. */ enum { TVOUT_LOSE, TVOUT_SKIP, TVOUT_WIN, TVOUT_LIMIT }; struct tvec_state { + /* The primary state structure for the test vector machinery. */ + unsigned f; /* flags */ #define TVSF_SKIP 1u /* skip this test group */ #define TVSF_OPEN 2u /* test is open */ @@ -186,59 +273,273 @@ struct tvec_state { #define TVSF_ERROR 8u /* an error occurred */ #define TVSF_OUTMASK 0xf0 /* test outcome */ #define TVSF_OUTSHIFT 4 + + /* Registers. Available to execution environments. */ unsigned nrout, nreg; /* number of output/total registers */ size_t regsz; /* size of register entry */ struct tvec_reg *in, *out; /* register vectors */ - char expst, st; /* progress status codes */ + + /* Test groups state. Available to output formatters. */ const struct tvec_test *tests, *test; /* all tests and current test */ + + /* Test scoreboard. Available to output formatters. */ unsigned curr[TVOUT_LIMIT], all[TVOUT_LIMIT], grps[TVOUT_LIMIT]; + + /* Output machinery. */ struct tvec_output *output; /* output formatter */ + + /* Input machinery. Available to type parsers. */ const char *infile; unsigned lno, test_lno; /* input file name, line */ FILE *fp; /* input file stream */ }; -#define TVEC_GREG(vec, i, regsz) \ - ((struct tvec_reg *)((unsigned char *)(vec) + (i)*(regsz))) +/* @TVEC_REG(tv, vec, i)@ + * + * If @tv@ is a pointer to a @struct tvec_state@, @vec@ is either @in@ or + * @out@, and @i@ is an integer, then this evaluates to the address of the + * @i@th register in the selected vector. + */ #define TVEC_REG(tv, vec, i) TVEC_GREG((tv)->vec, (i), (tv)->regsz) /*----- Test descriptions -------------------------------------------------*/ -typedef int tvec_hookfn(struct tvec_state */*tv*/); typedef void tvec_testfn(const struct tvec_reg */*in*/, struct tvec_reg */*out*/, void */*ctx*/); + /* A test function. It should read inputs from @in@ and write outputs to + * @out@. The @TVRF_LIVE@ is set on inputs which are actually present, and + * on outputs which are wanted to test. A test function can set additional + * `gratuitous outputs' by setting @TVRF_LIVE@ on them; clearing + * @TVRF_LIVE@ on a wanted output causes a mismatch. + * + * A test function may be called zero or more times by the environment. In + * particular, it may be called multiple times, though usually by prior + * arrangement with the environment. + * + * The @ctx@ is supplied by the environment's @run@ function (see below). + * The default environment calls the test function once, with a null + * @ctx@. There is no expectation that the environment's context has + * anything to do with the test function's context. + */ + +struct tvec_env { + /* A test environment sets things up for and arranges to run the test. + * + * The caller is responsible for allocating storage for the environment's + * context, based on the @ctxsz@ slot, and freeing it later; this space is + * passed in as the @ctx@ parameter to the remaining functions; if @ctxsz@ + * is zero then @ctx@ is null. + */ + + size_t ctxsz; /* environment context size */ + + int (*setup)(struct tvec_state */*tv*/, const struct tvec_env */*env*/, + void */*pctx*/, void */*ctx*/); + /* Initialize the context; called at the start of a test group. Return + * zero on success, or @-1@ on failure. If setup fails, the context is + * freed, and the test group is skipped. + */ + + int (*set)(struct tvec_state */*tv*/, const char */*var*/, + const struct tvec_env */*env*/, void */*ctx*/); + /* Called when the parser finds a %|@var|%' setting to parse and store + * the value. If @setup@ failed, this is still called (so as to skip the + * value), but @ctx@ is null. + */ + + int (*before)(struct tvec_state */*tv*/, void */*ctx*/); + /* Called prior to running a test. This is the right place to act on any + * `%|@var|%' settings. Return zero on success or @-1@ on failure (which + * causes the test to be skipped). This function is never called if the + * test group is skipped. + */ + + void (*run)(struct tvec_state */*tv*/, tvec_testfn */*fn*/, void */*ctx*/); + /* Run the test. It should either call @tvec_skip@, or run @fn@ one or + * more times. In the latter case, it is responsible for checking the + * outputs, and calling @tvec_fail@ as necessary; @tvec_checkregs@ will + * check the register values against the supplied test vector, while + * @tvec_check@ does pretty much everything necessary. This function is + * never called if the test group is skipped. + */ + + void (*after)(struct tvec_state */*tv*/, void */*ctx*/); + /* Called after running or skipping a test. Typical actions involve + * resetting whatever things were established by @set@. This function is + * never called if the test group is skipped. + */ + + void (*teardown)(struct tvec_state */*tv*/, void */*ctx*/); + /* Tear down the environment: called at the end of a test group. If the + * setup failed, then this function is still called, with a null @ctx@. + */ +}; struct tvec_test { + /* A test description. */ + const char *name; /* name of the test */ const struct tvec_regdef *regs; /* descriptions of the registers */ - tvec_hookfn *preflight; /* check before starting */ - tvec_hookfn *run; /* test runner */ + const struct tvec_env *env; /* environment to run test in */ tvec_testfn *fn; /* test function */ - union tvec_misc arg; /* additional parameter to `run' */ }; + +enum { + /* Register output dispositions. */ + + TVRD_INPUT, /* input-only register */ + TVRD_OUTPUT, /* output-only (input is dead) */ + TVRD_MATCH, /* matching (equal) registers */ + TVRD_FOUND, /* mismatching output register */ + TVRD_EXPECT /* mismatching input register */ +}; + +/* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *excuse@, @va_list ap@ = reason why group skipped + * + * Returns: --- + * + * Use: Skip the current group. This should only be called from a + * test environment @setup@ function; a similar effect occurs if + * the @setup@ function fails. + */ + extern void PRINTF_LIKE(2, 3) - tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...); -extern void tvec_check_v(struct tvec_state */*tv*/, - const char */*detail*/, va_list */*ap*/); + tvec_skipgroup(struct tvec_state */*tv*/, const char */*excuse*/, ...); +extern void tvec_skipgroup_v(struct tvec_state */*tv*/, + const char */*excuse*/, va_list */*ap*/); -extern int tvec_runtest(struct tvec_state */*tv*/); +/* --- @tvec_skip@, @tvec_skip_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *excuse@, @va_list ap@ = reason why test skipped + * + * Returns: --- + * + * Use: Skip the current test. This should only be called from a + * test environment @run@ function; a similar effect occurs if + * the @before@ function fails. + */ -/*----- Input utilities ---------------------------------------------------*/ +extern void PRINTF_LIKE(2, 3) + tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...); +extern void tvec_skip_v(struct tvec_state */*tv*/, + const char */*excuse*/, va_list */*ap*/); -extern void tvec_skipspc(struct tvec_state */*tv*/); +/* --- @tvec_resetoutputs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Reset (releases and reinitializes) the output registers in + * the test state. This is mostly of use to test environment + * @run@ functions, between invocations of the test function. + */ -#define TVFF_ALLOWANY 1u -extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/); +extern void tvec_resetoutputs(struct tvec_state */*tv*/); -extern int PRINTF_LIKE(4, 5) - tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, - const char */*delims*/, const char */*expect*/, ...); -extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/, - const char */*delims*/, const char */*expect*/, - va_list */*ap*/); +/* --- @tvec_checkregs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, @-1@ on mismatch. + * + * Use: Compare the active output registers (according to the current + * test group definition) with the corresponding input register + * values. A mismatch occurs if the two values differ + * (according to the register type's @eq@ method), or if the + * input is live but the output is dead. + * + * This function only checks for a mismatch and returns the + * result; it takes no other action. In particular, it doesn't + * report a failure, or dump register values. + */ -extern int tvec_nexttoken(struct tvec_state */*tv*/); +extern int tvec_checkregs(struct tvec_state */*tv*/); + +/* --- @tvec_fail@, @tvec_fail_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *detail@, @va_list ap@ = description of test + * + * Returns: --- + * + * Use: Report the current test as a failure. This function can be + * called multiple times for a single test, e.g., if the test + * environment's @run@ function invokes the test function + * repeatedly; but a single test that fails repeatedly still + * only counts as a single failure in the statistics. The + * @detail@ string and its format parameters can be used to + * distinguish which of several invocations failed; it can + * safely be left null if the test function is run only once. + */ + +extern void PRINTF_LIKE(2, 3) + tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...); +extern void tvec_fail_v(struct tvec_state */*tv*/, + const char */*detail*/, va_list */*ap*/); + +/* --- @tvec_dumpreg@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned disp@ = the register disposition (@TVRD_...@) + * @const union tvec_regval *tv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Dump a register value to the output. This is the lowest- + * level function for dumping registers, and calls the output + * formatter directly. + * + * Usually @tvec_mismatch@ is much more convenient. Low-level + * access is required for reporting `virtual' registers + * corresponding to test environment settings. + */ + +extern void tvec_dumpreg(struct tvec_state */*tv*/, + unsigned /*disp*/, const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + +/* --- @tvec_mismatch@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned f@ = flags (@TVMF_...@) + * + * Returns: --- + * + * Use: Dumps registers suitably to report a mismatch. The flag bits + * @TVMF_IN@ and @TVF_OUT@ select input-only and output + * registers. If both are reset then nothing happens. + * Suppressing the output registers may be useful, e.g., if the + * test function crashed rather than returning outputs. + */ + +#define TVMF_IN 1u +#define TVMF_OUT 2u +extern void tvec_mismatch(struct tvec_state */*tv*/, unsigned /*f*/); + +/* --- @tvec_check@, @tvec_check_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *detail@, @va_list ap@ = description of test + * + * Returns: --- + * + * Use: Check the register values, reporting a failure and dumping + * the registers in the event of a mismatch. This just wraps up + * @tvec_checkregs@, @tvec_fail@ and @tvec_mismatch@ in the + * obvious way. + */ + +extern void PRINTF_LIKE(2, 3) + tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...); +extern void tvec_check_v(struct tvec_state */*tv*/, + const char */*detail*/, va_list */*ap*/); /*----- Session lifecycle -------------------------------------------------*/ @@ -259,21 +560,46 @@ extern int tvec_read(struct tvec_state */*tv*/, /*----- Benchmarking ------------------------------------------------------*/ struct tvec_bench { + struct tvec_env _env; /* benchmarking is an environment */ + struct bench_state **bst; /* benchmark state anchor or null */ unsigned long niter; /* iterations done per unit */ int riter, rbuf; /* iterations and buffer registers */ - size_t ctxsz; /* size of context */ - int (*setup)(const struct tvec_reg */*in*/, struct tvec_reg */*out*/, - const union tvec_misc */*arg*/, void */*ctx*/); /* setup fn */ - void (*teardown)(void */*ctx*/); /* teardown function, or null */ - struct bench_state **b; /* benchmark state anchor or null */ - union tvec_misc arg; /* argument to setup */ + const struct tvec_env *env; /* environment (per test, not grp) */ +}; +#define TVEC_BENCHENV \ + { sizeof(struct tvec_benchctx), \ + tvec_benchsetup, \ + tvec_benchset, \ + tvec_benchbefore, \ + tvec_benchrun, \ + tvec_benchafter, \ + tvec_benchteardown } +#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate + +struct tvec_benchctx { + const struct tvec_bench *b; + struct bench_state *bst; + double dflt_target; + void *subctx; }; +struct bench_timing; extern struct bench_state *tvec_benchstate; -extern int tvec_ensurebench(struct tvec_state */*tv*/, - struct bench_state **/*b_out*/); -extern int tvec_bench(struct tvec_state */*tv*/); +extern int tvec_benchsetup(struct tvec_state */*tv*/, + const struct tvec_env */*env*/, + void */*pctx*/, void */*ctx*/); +extern int tvec_benchset(struct tvec_state */*tv*/, const char */*var*/, + const struct tvec_env */*env*/, void */*ctx*/); +extern int tvec_benchbefore(struct tvec_state */*tv*/, void */*ctx*/); +extern void tvec_benchrun(struct tvec_state */*tv*/, + tvec_testfn */*fn*/, void */*ctx*/); +extern void tvec_benchafter(struct tvec_state */*tv*/, void */*ctx*/); +extern void tvec_benchteardown(struct tvec_state */*tv*/, void */*ctx*/); + +extern void tvec_benchreport + (const struct gprintf_ops */*gops*/, void */*go*/, + unsigned /*unit*/, const struct bench_timing */*tm*/); /*----- Ad-hoc testing ----------------------------------------------------*/ @@ -351,14 +677,13 @@ struct tvec_output { struct tvec_state *tv; }; -struct bench_timing; +enum { TVBU_OP, TVBU_BYTE }; struct tvec_outops { void (*error)(struct tvec_output */*o*/, const char */*msg*/, va_list */*ap*/); void (*notice)(struct tvec_output */*o*/, const char */*msg*/, va_list */*ap*/); - void (*write)(struct tvec_output */*o*/, const char */*p*/, size_t /*sz*/); void (*bsession)(struct tvec_output */*o*/); int (*esession)(struct tvec_output */*o*/); @@ -373,11 +698,15 @@ struct tvec_outops { const char */*excuse*/, va_list */*ap*/); void (*fail)(struct tvec_output */*o*/, const char */*detail*/, va_list */*ap*/); - void (*mismatch)(struct tvec_output */*o*/); + void (*dumpreg)(struct tvec_output */*o*/, + unsigned /*disp*/, const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); - void (*bbench)(struct tvec_output */*o*/); + void (*bbench)(struct tvec_output */*o*/, + const char */*ident*/, unsigned /*unit*/); void (*ebench)(struct tvec_output */*o*/, + const char */*ident*/, unsigned /*unit*/, const struct bench_timing */*tm*/); void (*destroy)(struct tvec_output */*o*/); @@ -399,31 +728,25 @@ extern int PRINTF_LIKE(3, 4) extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/, const char */*expect*/, va_list */*ap*/); -extern void PRINTF_LIKE(2, 3) - tvec_skipgroup(struct tvec_state */*tv*/, const char */*note*/, ...); -extern void tvec_skipgroup_v(struct tvec_state */*tv*/, - const char */*note*/, va_list */*ap*/); +extern struct tvec_output *tvec_humanoutput(FILE */*fp*/); +extern struct tvec_output *tvec_tapoutput(FILE */*fp*/); +extern struct tvec_output *tvec_dfltout(FILE */*fp*/); -extern void PRINTF_LIKE(2, 3) - tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...); -extern void tvec_skip_v(struct tvec_state */*tv*/, - const char */*excuse*/, va_list */*ap*/); +/*----- Input utilities ---------------------------------------------------*/ -extern void PRINTF_LIKE(2, 3) - tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...); -extern void tvec_fail_v(struct tvec_state */*tv*/, - const char */*detail*/, va_list */*ap*/); +extern void tvec_skipspc(struct tvec_state */*tv*/); -extern void tvec_mismatch(struct tvec_state */*tv*/); +#define TVFF_ALLOWANY 1u +extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/); -extern void PRINTF_LIKE(2, 3) - tvec_write(struct tvec_state */*tv*/, const char */*p*/, ...); -extern void tvec_write_v(struct tvec_state */*tv*/, - const char */*p*/, va_list */*ap*/); +extern int PRINTF_LIKE(4, 5) + tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, + const char */*delims*/, const char */*expect*/, ...); +extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/, + const char */*delims*/, const char */*expect*/, + va_list */*ap*/); -extern struct tvec_output *tvec_humanoutput(FILE */*fp*/); -extern struct tvec_output *tvec_tapoutput(FILE */*fp*/); -extern struct tvec_output *tvec_dfltout(FILE */*fp*/); +extern int tvec_nexttoken(struct tvec_state */*tv*/); /*----- Register types ----------------------------------------------------*/ @@ -434,8 +757,6 @@ struct tvec_regty { int (*eq)(const union tvec_regval */*rv0*/, const union tvec_regval */*rv1*/, const struct tvec_regdef */*rd*/); - size_t (*measure)(const union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/, @@ -444,7 +765,8 @@ struct tvec_regty { struct tvec_state */*tv*/); void (*dump)(const union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, - struct tvec_state */*tv*/, unsigned /*style*/); + unsigned /*style*/, + const struct gprintf_ops */*gops*/, void */*go*/); #define TVSF_COMPACT 1u }; @@ -458,6 +780,8 @@ extern const struct tvec_irange extern const struct tvec_urange tvrange_uchar, tvrange_ushort, tvrange_uint, tvrange_ulong, tvrange_size, tvrange_byte, tvrange_u16, tvrange_u32; +extern const struct tvec_frange + tvrange_float, tvrange_double; extern int tvec_claimeq_int(struct tvec_state */*tv*/, long /*i0*/, long /*i1*/, @@ -472,6 +796,35 @@ extern int tvec_claimeq_uint(struct tvec_state */*tv*/, #define TVEC_CLAIMEQ_UINT(tv, u0, u1) \ (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1)) +extern const struct tvec_regty tvty_float; +struct tvec_floatinfo { + unsigned f; +#define TVFF_NOMIN 1u +#define TVFF_NOMAX 2u +#define TVFF_NANOK 4u +#define TVFF_EXACT 0u +#define TVFF_ABSDELTA 0x10 +#define TVFF_RELDELTA 0x20 +#define TVFF_EQMASK 0xf0 + double min, max; + double delta; +}; + +extern int tvec_claimeqish_float(struct tvec_state */*tv*/, + double /*f0*/, double /*f1*/, + unsigned /*f*/, double /*delta*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +extern int tvec_claimeq_float(struct tvec_state */*tv*/, + double /*f0*/, double /*f1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQISH_FLOAT(tv, f0, f1, f, delta) \ + (tvec_claimeqish_float(tv, f0, f1, f, delta, , __FILE__, __LINE__, \ + #f0 " /= " #f1 " (+/- " #delta ")")) +#define TVEC_CLAIMEQ_FLOAT(tv, f0, f1) \ + (tvec_claimeq_float(tv, f0, f1, __FILE__, __LINE__, #f0 " /= " #f1)) + extern const struct tvec_regty tvty_enum; #define DEFASSOC(tag_, ty, slot) \ @@ -479,28 +832,34 @@ extern const struct tvec_regty tvty_enum; TVEC_MISCSLOTS(DEFASSOC) #undef DEFASSOC -struct tvec_enuminfo { - const char *name; unsigned mv; - union { -#define DEFENUMINFO(tag, ty, slot) struct { \ - const struct tvec_##slot##assoc *av; \ - RANGESLOT_##tag \ - } slot; -#define RANGESLOT_INT const struct tvec_irange *ir; -#define RANGESLOT_UINT const struct tvec_urange *ur; -#define RANGESLOT_PTR - TVEC_MISCSLOTS(DEFENUMINFO) -#undef DEFENUMINFO -#undef RANGESLOT_INT -#undef RANGESLOT_UINT -#undef RANGESLOT_PTR - } u; +struct tvec_enuminfo { const char *name; unsigned mv; /* ... */ }; +struct tvec_ienuminfo { + struct tvec_enuminfo _ei; + const struct tvec_iassoc *av; + const struct tvec_irange *ir; +}; +struct tvec_uenuminfo { + struct tvec_enuminfo _ei; + const struct tvec_uassoc *av; + const struct tvec_urange *ur; +}; +struct tvec_fenuminfo { + struct tvec_enuminfo _ei; + const struct tvec_fassoc *av; + const struct tvec_floatinfo *fi; +}; +struct tvec_penuminfo { + struct tvec_enuminfo _ei; + const struct tvec_passoc *av; }; +const struct tvec_ienuminfo tvenum_bool; + #define DECLCLAIM(tag, ty, slot) \ extern int tvec_claimeq_##slot##enum \ (struct tvec_state */*tv*/, \ - const struct tvec_enuminfo */*ei*/, ty /*e0*/, ty /*e1*/, \ + const struct tvec_##slot##enuminfo */*ei*/, \ + ty /*e0*/, ty /*e1*/, \ const char */*file*/, unsigned /*lno*/, const char */*expr*/); TVEC_MISCSLOTS(DECLCLAIM) #undef DECLCLAIM @@ -510,6 +869,9 @@ TVEC_MISCSLOTS(DECLCLAIM) #define TVEC_CLAIMEQ_UENUM(tv, ei, e0, e1) \ (tvec_claimeq_uenum(tv, ei, e0, e1, \ __FILE__, __LINE__, #e0 " /= " #e1)) +#define TVEC_CLAIMEQ_FENUM(tv, ei, e0, e1) \ + (tvec_claimeq_fenum(tv, ei, e0, e1, \ + __FILE__, __LINE__, #e0 " /= " #e1)) #define TVEC_CLAIMEQ_PENUM(tv, ei, e0, e1) \ (tvec_claimeq_penum(tv, ei, e0, e1, \ __FILE__, __LINE__, #e0 " /= " #e1)) @@ -531,6 +893,14 @@ extern int tvec_claimeq_flags(struct tvec_state */*tv*/, (tvec_claimeq_flags(tv, fi, f0, f1, \ __FILE__, __LINE__, #f0 " /= " #f1)) +extern const struct tvec_regty tvty_char; +extern int tvec_claimeq_char(struct tvec_state */*tv*/, + int /*ch0*/, int /*ch1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQ_CHAR(tv, c0, c1) \ + (tvec_claimeq_char(tv, c0, c1, __FILE__, __LINE__, #c0 " /= " #c1)) + extern const struct tvec_regty tvty_string, tvty_bytes; extern int tvec_claimeq_string(struct tvec_state */*tv*/, @@ -547,7 +917,6 @@ extern int tvec_claimeq_bytes(struct tvec_state */*tv*/, const void */*p1*/, size_t /*sz1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); - #define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1) \ (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) diff --git a/utils/Makefile.am b/utils/Makefile.am index 9f4562e..7807721 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -72,6 +72,11 @@ check_PROGRAMS += t/exc.t t_exc_t_SOURCES = t/exc-test.c t_exc_t_LDFLAGS = -static +## Generalized formatting. +pkginclude_HEADERS += gprintf.h +libutils_la_SOURCES += gprintf.c +##LIBMANS += gprintf.3 + ## Linear regression. pkginclude_HEADERS += linreg.h libutils_la_SOURCES += linreg.c diff --git a/utils/gprintf.c b/utils/gprintf.c new file mode 100644 index 0000000..f527186 --- /dev/null +++ b/utils/gprintf.c @@ -0,0 +1,599 @@ +/* -*-c-*- + * + * Generalized string formatting + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_FLOAT_H +# include +#endif + +#ifdef HAVE_STDINT_H +# include +#endif + +#include "darray.h" +#include "dstr.h" +#include "gprintf.h" +#include "macros.h" + +/*----- Tunable constants -------------------------------------------------*/ + +/* For each format specifier, at least @STEP@ bytes are ensured before + * writing the formatted result. + */ + +#define STEP 64 /* Buffer size for @vgprintf@ */ + +/*----- 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 +}; + +struct fmtarg { + int fmt; + union { +#define MEMB(code, ty) ty code; + FMTTYPES(MEMB) +#undef MEMB + } u; +}; + +DA_DECL(fmtarg_v, struct 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 + +struct fmtspec { + const char *p; + size_t n; + unsigned f; + int fmt, ch; + int wd, prec; + int arg; +}; + +DA_DECL(fmtspec_v, struct fmtspec); + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @vgprintf@ --- * + * + * Arguments: @const struct gprintf_ops *ops@ = output operations + * @void *out@ = context for output operations + * @const char *p@ = pointer to @printf@-style format string + * @va_list *ap@ = argument handle + * + * Returns: The number of characters written to the string. + * + * Use: As for @gprintf@, but takes a reified argument tail. + */ + +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 vgprintf(const struct gprintf_ops *ops, void *out, + const char *p, va_list *ap) +{ + size_t sz, mx, n; + dstr dd = DSTR_INIT; + fmtspec_v sv = DA_INIT; + fmtarg_v av = DA_INIT; + struct fmtarg *fa, *fal; + struct fmtspec *fs, *fsl; + unsigned f; + int i, anext, tot = 0; + 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 represents 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) { + f = 0; + DA_ENSURE(&sv, 1); + fs = &DA(&sv)[DA_LEN(&sv)]; + DA_UNSAFE_EXTEND(&sv, 1); + + /* --- Find the end of this literal portion --- */ + + 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; } + + /* --- 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++; + } + + /* --- Pick up the field width --- */ + + done_flags: + i = 0; + while (ISDIGIT(*p)) i = 10*i + *p++ - '0'; + + /* --- Snag: this might have been an argument position indicator --- */ + + if (i && *p == '$' && (!f || f == f_zero)) { + f |= f_posarg; + fs->arg = i - 1; + p++; + goto flags; + } + + /* --- 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. + */ + + if (i) { + f |= f_wd; + fs->wd = i; + } else if (*p == '*') { + p++; + if (!ISDIGIT(*p)) + i = anext++; + else { + i = *p++ - '0'; + while (ISDIGIT(*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(*p)) { + i = *p++ - '0'; + while (ISDIGIT(*p)) i = 10*i + *p++ - '0'; + fs->prec = i; + } else if (*p != '*') + fs->prec = 0; + else { + p++; + if (!ISDIGIT(*p)) + i = anext++; + else { + i = *p++ - '0'; + while (ISDIGIT(*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; + } + } + + /* --- 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; ) + } + + /* --- 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; + + /* --- Output the literal portion --- */ + + if (fs->n) { + if (ops->putm(out, fs->p, fs->n)) return (-1); + tot += fs->n; + } + + /* --- And now the variable portion --- */ + + if (fs->fmt == fmt_unset) { + switch (fs->ch) { + case 0: break; + case '%': ops->putch(out, '%'); break; + default: abort(); + } + continue; + } + + DRESET(&dd); + 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 --- */ + + 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, STEP); + dd.len += sprintf(dd.buf + dd.len, "%d", wd); + } + + if (f&f_prec) { + DENSURE(&dd, STEP + 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 = STEP; + 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 +# define MSG "" + if (ops->putm(out, MSG, sizeof(MSG) - 1)) return (-1); + continue; +# undef MSG +#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 = tot; break; + PERCENT_N_FMTTYPES(CASE) +#undef CASE + default: abort(); + } + continue; + } + + /* --- Finally do the output stage --- */ + + switch (fs->fmt) { +#define CASE(code, ty) \ + case fmt_##code: \ + i = ops->nputf(out, sz, dd.buf, fa[fs->arg].u.code); \ + break; + OUTPUT_FMTTYPES(CASE) +#undef CASE + default: abort(); + } + if (i < 0) return (-1); + tot += i; + } + + /* --- We're done --- */ + + DDESTROY(&dd); + DA_DESTROY(&av); + DA_DESTROY(&sv); + return (tot); +} + +/* --- @gprintf@ --- * + * + * Arguments: @const struct gprintf_ops *ops@ = output operations + * @void *out@ = context for output operations + * @const char *p@ = pointer to @printf@-style format string + * @...@ = argument handle + * + * Returns: The number of characters written to the string. + * + * Use: Formats a @printf@-like message and writes the result using + * the given output operations. This is the backend machinery + * for @dstr_putf@, for example. + */ + +int gprintf(const struct gprintf_ops *ops, void *out, const char *p, ...) +{ + va_list ap; + int n; + + va_start(ap, p); n = vgprintf(ops, out, p, &ap); va_end(ap); + return (n); +} + +/*----- Standard printers -------------------------------------------------*/ + +static int file_putch(void *out, int ch) +{ + FILE *fp = out; + + if (putc(ch, fp) == EOF) return (-1); + return (0); +} + +static int file_putm(void *out, const char *p, size_t sz) +{ + FILE *fp = out; + + if (fwrite(p, 1, sz, fp) < sz) return (-1); + return (0); +} + +static int file_nputf(void *out, size_t maxsz, const char *p, ...) +{ + FILE *fp = out; + va_list ap; + int n; + + va_start(ap, p); + n = vfprintf(fp, p, ap); + va_end(ap); if (n < 0) return (-1); + return (0); +} + +const struct gprintf_ops file_printops = + { file_putch, file_putm, file_nputf }; + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/utils/gprintf.h b/utils/gprintf.h new file mode 100644 index 0000000..9e32338 --- /dev/null +++ b/utils/gprintf.h @@ -0,0 +1,95 @@ +/* -*-c-*- + * + * Generalized string formatting + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef MLIB_GPRINTF_H +#define MLIB_GPRINTF_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#include + +#ifndef MLIB_MACROS_H +# include "macros.h" +#endif + +/*----- Data structures ---------------------------------------------------*/ + +struct gprintf_ops { + int (*putch)(void */*out*/, int /*ch*/); + int (*putm)(void */*out*/, const char */*p*/, size_t /*sz*/); + PRINTF_LIKE(3, 4) int (*nputf) + (void */*out*/, size_t /*maxsz*/, const char */*p*/, ...); +}; + +extern const struct gprintf_ops file_printops; + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @vgprintf@ --- * + * + * Arguments: @const struct gprintf_ops *ops@ = output operations + * @void *out@ = context for output operations + * @const char *p@ = pointer to @printf@-style format string + * @va_list *ap@ = argument handle + * + * Returns: The number of characters written to the string. + * + * Use: As for @gprintf@, but takes a reified argument tail. + */ + +extern int vgprintf(const struct gprintf_ops */*ops*/, void */*out*/, + const char */*p*/, va_list */*ap*/); + +/* --- @gprintf@ --- * + * + * Arguments: @const struct gprintf_ops *ops@ = output operations + * @void *out@ = context for output operations + * @const char *p@ = pointer to @printf@-style format string + * @...@ = argument handle + * + * Returns: The number of characters written to the string. + * + * Use: Formats a @printf@-like message and writes the result using + * the given output operations. This is the backend machinery + * for @dstr_putf@, for example. + */ + +extern int PRINTF_LIKE(3, 4) + gprintf(const struct gprintf_ops */*ops*/, void */*out*/, + const char */*p*/, ...); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/utils/t/bits-test.c b/utils/t/bits-test.c index 743ad0e..c87fec5 100644 --- a/utils/t/bits-test.c +++ b/utils/t/bits-test.c @@ -85,13 +85,13 @@ static const struct tvec_regdef arith_regs[] = { }; static const struct tvec_test tests[] = { - { "lsl64", shift_regs, 0, tvec_runtest, test_LSL }, - { "lsr64", shift_regs, 0, tvec_runtest, test_LSR }, - { "rol64", shift_regs, 0, tvec_runtest, test_ROL }, - { "ror64", shift_regs, 0, tvec_runtest, test_ROR }, - { "add64", arith_regs, 0, tvec_runtest, test_ADD }, - { "sub64", arith_regs, 0, tvec_runtest, test_SUB }, - { 0, 0, 0, 0, 0 } + { "lsl64", shift_regs, 0, test_LSL }, + { "lsr64", shift_regs, 0, test_LSR }, + { "rol64", shift_regs, 0, test_ROL }, + { "ror64", shift_regs, 0, test_ROR }, + { "add64", arith_regs, 0, test_ADD }, + { "sub64", arith_regs, 0, test_SUB }, + { 0, 0, 0, 0 } }; static const struct tvec_info testinfo = diff --git a/utils/t/versioncmp-test.c b/utils/t/versioncmp-test.c index 1368e00..0fb344c 100644 --- a/utils/t/versioncmp-test.c +++ b/utils/t/versioncmp-test.c @@ -37,28 +37,28 @@ static void test_versioncmp(const struct tvec_reg *in, struct tvec_reg *out, void *ctx) { out[RRC].v.i = versioncmp(in[RV0].v.str.p, in[RV1].v.str.p); } -static int swap_test(struct tvec_state *tv) +static void swap_test(struct tvec_state *tv, tvec_testfn *fn, void *ctx) { struct tvec_reg rt; - tv->st = '.'; - tv->test->fn(tv->in, tv->out, 0); tvec_check(tv, "vanilla"); + fn(tv->in, tv->out, 0); tvec_check(tv, "vanilla"); + tvec_resetoutputs(tv); rt = tv->in[RV0]; tv->in[RV0] = tv->in[RV1]; tv->in[RV1] = rt; tv->in[RRC].v.i = -tv->in[RRC].v.i; - tv->test->fn(tv->in, tv->out, 0); tvec_check(tv, "swapped"); - return (0); + fn(tv->in, tv->out, 0); tvec_check(tv, "swapped"); } +static const struct tvec_env swap_testenv = { 0, 0, 0, 0, swap_test, 0, 0 }; static const struct tvec_irange cmp_range = { -1, +1 }; static const struct tvec_regdef versioncmp_regs[] = { { "v0", RV0, &tvty_string, 0 }, { "v1", RV1, &tvty_string, 0 }, { "rc", RRC, &tvty_int, 0, { &cmp_range } }, - { 0, 0, 0 } + { 0, 0, 0, 0 } }; static const struct tvec_test tests[] = { - { "versioncmp", versioncmp_regs, 0, swap_test, test_versioncmp }, + { "versioncmp", versioncmp_regs, &swap_testenv, test_versioncmp }, { 0, 0, 0, 0 } }; -- 2.11.0