chiark / gitweb /
@@@ so much mess
authorMark Wooding <mdw@distorted.org.uk>
Thu, 25 May 2023 07:45:34 +0000 (08:45 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 25 May 2023 07:46:08 +0000 (08:46 +0100)
27 files changed:
hash/t/hash-test.c
struct/Makefile.am
struct/buf-float.c [new file with mode: 0644]
struct/buf-putf.c [new file with mode: 0644]
struct/buf.c
struct/buf.h
struct/dstr-putf.c
struct/dstr.h
t/template-canonify [new file with mode: 0755]
test/Makefile.am
test/bench.c
test/bench.h
test/t/tvec-test.c
test/tests.at
test/tvec-bench.c [new file with mode: 0644]
test/tvec-core.c
test/tvec-main.c
test/tvec-output.c
test/tvec-remote.c [new file with mode: 0644]
test/tvec-types.c
test/tvec.3 [new file with mode: 0644]
test/tvec.h
utils/Makefile.am
utils/gprintf.c [new file with mode: 0644]
utils/gprintf.h [new file with mode: 0644]
utils/t/bits-test.c
utils/t/versioncmp-test.c

index 6298b82e802ee6b06282e8640cb0d7178003ba5d..1033c59f1a4e54f85c02d49c4938c152d06316f9 100644 (file)
@@ -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 =
index 6365023bc46b70bd064c103fa86bebd0160a9a54..799623190cb1c883db6bc4efdea976511700aa6a 100644 (file)
@@ -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 (file)
index 0000000..502b96b
--- /dev/null
@@ -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 <float.h>
+#include <math.h>
+
+#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 @<math.h>@; 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 (file)
index 0000000..5b465f2
--- /dev/null
@@ -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 <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#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 -------------------------------------------------*/
index 11b1425b0d1623d4f4edab7b6a574da5f3836f7f..00163bfd37daaca5775765df7582f1b921b0b021 100644 (file)
@@ -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;
index 97dbd105b8aff5c4b5a36547f2b783696f5cfcd6..389d038c65b303ab53b69f852d3956852aa7dfcf 100644 (file)
 
 /*----- Header files ------------------------------------------------------*/
 
+#include <stdarg.h>
 #include <stddef.h>
 
 #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 @<math.h>@; 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
index 270f9a3f84bdd96e4cdf83c772c6da86daaf6ee7..aa492e00efad4a46cbf36114dfa7eeae8320e0cc 100644 (file)
 
 /*----- Header files ------------------------------------------------------*/
 
-#include "config.h"
-
-#include <assert.h>
-#include <ctype.h>
-#include <limits.h>
 #include <stdarg.h>
+#include <stddef.h>
 #include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#ifdef HAVE_FLOAT_H
-#  include <float.h>
-#endif
 
-#ifdef HAVE_STDINT_H
-#  include <stdint.h>
-#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, "<no float support>");
-       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@ --- *
  *
index 1c43cb720e1b74ab1b61b95627af809da6691a22..fedc48c70219759844f50ec34fa538d756181a0f 100644 (file)
@@ -47,6 +47,7 @@
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 #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 (executable)
index 0000000..a82acde
--- /dev/null
@@ -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()
index cf1a19838edb71ad34c2f7d133dbb7ee7062b37f..2f7a041773e210cb66f6ebd2e9042f398dd42fe4 100644 (file)
@@ -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
index df8c4efd8ad20d9bb99b5be583ade918797de5b5..d1a00ea4da9e6f119d95de7f019a079cab9d0c66 100644 (file)
@@ -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 -------------------------------------------------*/
index f9472cd9fae3e5bb2f9ffe4d5e3a71bf8dd3afd0..2dbce0f004e755e7cf9be5d372e3c4acd245459d 100644 (file)
@@ -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 -------------------------------------------------*/
 
index 8b27f7fb3629770b92ad7af32ac0ffa91fc042f1..f2ed3dab4303b3d32398d9f2f96c1d9cf35c7778 100644 (file)
 
 /*----- 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
index 7088199cb21ebf122ab26fc6b360e2d8fd267509..30c898a3429c2f049d5e0a875d4f12b02465c5be 100644 (file)
 ### 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 <eol>])
+       [syntax error: expected signed integer but found @%:@<eol>])
+
 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 <eol>])
+       [syntax error: expected unsigned integer but found @%:@<eol>])
+
 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 ; = @%:@<player ...>])
-test_parse([penum], [alice], [alice ; = @%:@<player ...>])
+
+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 ; = @%:@<actor ={ACTOR:@<:@^>@:>@*}>])
+test_parse([penum], [alice], [alice ; = @%:@<actor ={ACTOR:@<:@^>@:>@*}>])
 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 (file)
index 0000000..ab21b9c
--- /dev/null
@@ -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 = &nothing;
+
+  *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 -------------------------------------------------*/
index a25d6532d9b43dec077e80e225b3c8414daf1a44..9ec5d107722c590b8f63bdbe2e57fc8466cac6a1 100644 (file)
@@ -33,7 +33,6 @@
 #include <string.h>
 
 #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, "<eof>"); break;
-    case '\n': strcpy(found, "<eol>"); ungetc(ch, tv->fp); break;
+    case EOF: strcpy(found, "#<eof>"); break;
+    case '\n': strcpy(found, "#<eol>"); 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 = "<unset>"; t->regs = &no_regs;
-  t->preflight = 0; t->run = fakerun; t->fn = fakefn; t->arg.p = 0;
+  t->name = "<unset>"; 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, &regs[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 -------------------------------------------------*/
index aa361807e9ee7e51ad309f9dbfe5e77d6db66def..25698a864c4657d28bd1800ea42312d72286e9a1 100644 (file)
@@ -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;
index 015013beb7632abb2e7dce8d68a041d1dfa0554d..fbdb347582e360dba8dfe8279e4b778718ea826b 100644 (file)
@@ -27,6 +27,8 @@
 
 /*----- Header files ------------------------------------------------------*/
 
+#include "config.h"
+
 #include <assert.h>
 #include <stdarg.h>
 #include <stdio.h>
 
 /*----- 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, "#<unset>");
-  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 = &nothing;
-
-  *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, "#<unset>");
-  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, "#<unset>");
+  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, "#<unset>\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 (file)
index 0000000..e03297e
--- /dev/null
@@ -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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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 -------------------------------------------------*/
index 249371b6e00d95dd5ed285502651fbe0d278a0f1..8be8ae2004bcc13482e154194c99013322ebd360 100644 (file)
@@ -30,7 +30,9 @@
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
+#include <float.h>
 #include <limits.h>
+#include <math.h>
 #include <stdio.h>
 #include <string.h>
 
 
 /*----- 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 (file)
index 0000000..06a1211
--- /dev/null
@@ -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 <mLib/tvec.h>
+
+
+
+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*/);
+
index 2309edf744da022fad2a28c993690e5b3ced03c8..2f1cc568b27b674f4aa3efa10a093a9596780df7 100644 (file)
 #  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 "]"))
index 9f4562ec705880678e8e1d1eba080ba23bd3c6f2..780772120495de66aae9756f836ec7a99b9781ff 100644 (file)
@@ -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 (file)
index 0000000..f527186
--- /dev/null
@@ -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 <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_FLOAT_H
+#  include <float.h>
+#endif
+
+#ifdef HAVE_STDINT_H
+#  include <stdint.h>
+#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 "<no float support>"
+       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 (file)
index 0000000..9e32338
--- /dev/null
@@ -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 <stdarg.h>
+
+#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
index 743ad0ec1e479d85089cf3c32a832dbc3437fdcb..c87fec57c4785ac4a5833e8a0752de7a2e427ee1 100644 (file)
@@ -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 =
index 1368e00aa7170d3c1f614e647523cb79983a219d..0fb344c1559f7c69738560cb66aa194b7f6b6afe 100644 (file)
@@ -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 }
 };