chiark / gitweb /
@@@ timeout wip
authorMark Wooding <mdw@distorted.org.uk>
Fri, 1 Mar 2024 10:05:16 +0000 (10:05 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 1 Mar 2024 10:05:16 +0000 (10:05 +0000)
22 files changed:
struct/buf-dstr.c
struct/buf-float.c
struct/buf.c
struct/buf.h
struct/dstr.h
struct/t/dstr-putf-test.c
sys/mdup.c
test/Makefile.am
test/bench.c
test/t/tvec-test.c
test/tests.at
test/tvec-bench.c
test/tvec-core.c
test/tvec-output.c
test/tvec-remote.c
test/tvec-timeout.c [new file with mode: 0644]
test/tvec-types.c
test/tvec.h
trace/trace.h
ui/report.h
utils/bits.h
utils/gprintf.h

index 2d91b42ed92aa1ebbf550f6c4d1e9b261752e612..18f98f39d47d8d82c92249c71fe457498f82f6de 100644 (file)
@@ -33,9 +33,9 @@
 
 /*----- Main code ---------------------------------------------------------*/
 
-/* --- @buf_getdstr{8,{16,24,32,64}{,l,b},z} --- *
+/* --- @{,d}buf_getdstr{8,{16,24,32,64}{,l,b},z} --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @dstr *d@ = where to put the result
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
       return (-1);                                                     \
     DPUTM(d, p, sz);                                                   \
     return (0);                                                                \
-  }
+  }                                                                    \
+  int (dbuf_getdstr##w)(dbuf *db, dstr *d)                             \
+    { return (dbuf_getdstr##w(db, d)); }
 BUF_DOSUFFIXES(BUF_GETDSTR_)
 
-/* --- @buf_putdstr{8,{16,24,32,64}{,l,b},z} --- *
+/* --- @{,d}buf_putdstr{8,{16,24,32,64}{,l,b},z} --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @dstr *d@ = string to write
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -69,7 +71,9 @@ BUF_DOSUFFIXES(BUF_GETDSTR_)
 
 #define BUF_PUTDSTR_(n, W, w)                                          \
   int buf_putdstr##w(buf *b, dstr *d)                                  \
-    { return (buf_putmem##w(b, d->buf, d->len)); }
+    { return (buf_putmem##w(b, d->buf, d->len)); }                     \
+  int (dbuf_putdstr##w)(dbuf *db, dstr *d)                             \
+    { return (dbuf_putdstr##w(db, d)); }
 BUF_DOSUFFIXES(BUF_PUTDSTR_)
 
 /*----- That's all, folks -------------------------------------------------*/
index 88d6c440a1622a21389893dcb0bb7454af6eceec..1f61c02ee53367a8e7e61366b67cc936e39680b7 100644 (file)
  * otherwise it's a signalling NaN.
  */
 
+/* --- @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);
+}
+
 /* --- @f64_to_k64@ --- *
  *
  * Arguments:  @double x@ = a floating-point number
@@ -153,113 +231,9 @@ static kludge64 f64_to_k64(double x)
   SET64(k, hi, lo); return (k);
 }
 
-/* --- @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} --- *
+/* --- @buf_getf64{,l,b} --- *
  *
  * Arguments:  @buf *b@ = a buffer to read from
  *             @double *x_out@ = where to put the result
@@ -285,21 +259,63 @@ int buf_getf64(buf *b, double *x_out)
   if (k64_to_f64(x_out, k)) { b->f |= BF_BROKEN; return (-1); }
   return (0);
 }
-int buf_getf64b(buf *b, double *x_out)
+
+int buf_getf64l(buf *b, double *x_out)
 {
   kludge64 k;
 
-  if (buf_getk64b(b, &k)) return (-1);
+  if (buf_getk64l(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)
+
+int buf_getf64b(buf *b, double *x_out)
 {
   kludge64 k;
 
-  if (buf_getk64l(b, &k)) return (-1);
+  if (buf_getk64b(b, &k)) return (-1);
   if (k64_to_f64(x_out, k)) { b->f |= BF_BROKEN; return (-1); }
   return (0);
 }
 
+int (dbuf_getf64)(dbuf *db, double *x_out)
+  { return (dbuf_getf64(db, x_out)); }
+int (dbuf_getf64l)(dbuf *db, double *x_out)
+  { return (dbuf_getf64l(db, x_out)); }
+int (dbuf_getf64b)(dbuf *db, double *x_out)
+  { return (dbuf_getf64b(db, x_out)); }
+
+/* --- @buf_putf64{,l,b} --- *
+ *
+ * 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_putf64l(buf *b, double x)
+  { return (buf_putk64l(b, f64_to_k64(x))); }
+int buf_putf64b(buf *b, double x)
+  { return (buf_putk64b(b, f64_to_k64(x))); }
+
+int (dbuf_putf64)(dbuf *db, double x)
+  { return (dbuf_putf64(db, x)); }
+int (dbuf_putf64l)(dbuf *db, double x)
+  { return (dbuf_putf64l(db, x)); }
+int (dbuf_putf64b)(dbuf *db, double x)
+  { return (dbuf_putf64b(db, x)); }
+
 /*----- That's all, folks -------------------------------------------------*/
index 5d1fc27dfab0d629964a8ede2a37b658b9715bde..9efc4fcecbd59b37d1a13e7168f2e51ce13489e1 100644 (file)
@@ -37,7 +37,7 @@
 
 /* --- @buf_init@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @void *p@ = pointer to a buffer
  *             @size_t sz@ = size of the buffer
  *
@@ -78,11 +78,7 @@ void dbuf_create(dbuf *db)
  * 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;
-}
+void dbuf_reset(dbuf *db) { DBRESET(db); }
 
 /* --- @dbuf_destroy@ --- *
  *
@@ -99,9 +95,9 @@ void dbuf_destroy(dbuf *db)
   dbuf_create(db);
 }
 
-/* --- @buf_break@ --- *
+/* --- @{,d}buf_break@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *
  * Returns:    Some negative value.
  *
@@ -109,10 +105,11 @@ void dbuf_destroy(dbuf *db)
  */
 
 int buf_break(buf *b) { b->f |= BF_BROKEN; return (-1); }
+int (dbuf_break)(dbuf *db) { return (dbuf_break(db)); }
 
-/* --- @buf_flip@ --- *
+/* --- @{,d}buf_flip@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *
  * Returns:    ---
  *
@@ -120,15 +117,12 @@ int buf_break(buf *b) { b->f |= BF_BROKEN; return (-1); }
  *             you can now read from the bit you've written.
  */
 
-void buf_flip(buf *b)
-{
-  b->limit = b->p; b->p = b->base;
-  b->f &= ~BF_WRITE;
-}
+void buf_flip(buf *b) { BFLIP(b); }
+void (dbuf_flip)(dbuf *db) { dbuf_flip(db); }
 
-/* --- @buf_ensure@ --- *
+/* --- @{,d}buf_ensure@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t sz@ = size of data wanted
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -137,10 +131,11 @@ void buf_flip(buf *b)
  */
 
 int buf_ensure(buf *b, size_t sz) { return (BENSURE(b, sz)); }
+int (dbuf_ensure)(dbuf *db, size_t sz) { return (dbuf_ensure(db, sz)); }
 
-/* --- @buf_tryextend@ --- *
+/* --- @{,d}buf_tryextend@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t sz@ = size of data wanted
  *
  * Returns:    Zero if it worked, nonzero if the buffer won't grow.
@@ -170,10 +165,12 @@ int buf_tryextend(buf *b, size_t sz)
   db->_b.limit = db->_b.base + newsz;
   return (0);
 }
+int (dbuf_tryextend)(dbuf *db, size_t sz)
+  { return (dbuf_tryextend(db, sz)); }
 
-/* --- @buf_get@ --- *
+/* --- @{,d}buf_get@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t sz@ = size of the buffer
  *
  * Returns:    Pointer to the place in the buffer.
@@ -191,10 +188,12 @@ void *buf_get(buf *b, size_t sz)
   BSTEP(b, sz);
   return (p);
 }
+void *(dbuf_get)(dbuf *db, size_t sz)
+  { return (dbuf_get(db, sz)); }
 
-/* --- @buf_put@ --- *
+/* --- @{,d}buf_put@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @const void *p@ = pointer to a buffer
  *             @size_t sz@ = size of the buffer
  *
@@ -211,10 +210,12 @@ int buf_put(buf *b, const void *p, size_t sz)
   BSTEP(b, sz);
   return (0);
 }
+int (dbuf_put)(dbuf *db, const void *p, size_t sz)
+  { return (dbuf_put(db, p, sz)); }
 
-/* --- @buf_getbyte@ --- *
+/* --- @{,d}buf_getbyte@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *
  * Returns:    A byte, or less than zero if there wasn't a byte there.
  *
@@ -227,10 +228,12 @@ int buf_getbyte(buf *b)
     return (-1);
   return (*b->p++);
 }
+int (dbuf_getbyte)(dbuf *db)
+  { return (dbuf_getbyte(db)); }
 
-/* --- @buf_putbyte@ --- *
+/* --- @{,d}buf_putbyte@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @int ch@ = byte to write
  *
  * Returns:    Zero if OK, nonzero if there wasn't enough space.
@@ -245,10 +248,12 @@ int buf_putbyte(buf *b, int ch)
   *b->p++ = ch;
   return (0);
 }
+int (dbuf_putbyte)(dbuf *db, int ch)
+  { return (dbuf_putbyte(db, ch)); }
 
-/* --- @buf_getu{8,{16,24,32,64}{,l,b}}@ --- *
+/* --- @{,d}buf_getu{8,{16,24,32,64}{,l,b}}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @uintSZ *w@ = where to put the word
  *
  * Returns:    Zero if OK, or nonzero if there wasn't a word there.
@@ -263,12 +268,14 @@ int buf_putbyte(buf *b, int ch)
     *ww = LOAD##W(b->p);                                               \
     BSTEP(b, SZ_##W);                                                  \
     return (0);                                                                \
-  }
+  }                                                                    \
+  int (dbuf_getu##w)(dbuf *db, uint##n *ww)                            \
+    { return (dbuf_getu##w(db, ww)); }
 DOUINTCONV(BUF_GETU_)
 
-/* --- @buf_getk64{,l,b}@ --- *
+/* --- @{,d}buf_getk64{,l,b}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @kludge64 *w@ = where to put the word
  *
  * Returns:    Zero if OK, or nonzero if there wasn't a word there.
@@ -294,9 +301,13 @@ int buf_getk64b(buf *b, kludge64 *w)
   LOAD64_B_(*w, b->p); BSTEP(b, 8); return (0);
 }
 
-/* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- *
+int (dbuf_getk64)(dbuf *db, kludge64 *w) { return (dbuf_getk64(db, w)); }
+int (dbuf_getk64l)(dbuf *db, kludge64 *w) { return (dbuf_getk64l(db, w)); }
+int (dbuf_getk64b)(dbuf *db, kludge64 *w) { return (dbuf_getk64b(db, w)); }
+
+/* --- @{,d}buf_putu{8,{16,24,32,64}{,l,b}}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @uintSZ w@ = word to write
  *
  * Returns:    Zero if OK, or nonzero if there wasn't enough space
@@ -311,12 +322,14 @@ int buf_getk64b(buf *b, kludge64 *w)
     STORE##W(b->p, ww);                                                        \
     BSTEP(b, SZ_##W);                                                  \
     return (0);                                                                \
-  }
+  }                                                                    \
+  int (dbuf_putu##w)(dbuf *db, uint##n ww)                             \
+    { return (dbuf_putu##w(db, ww)); }
 DOUINTCONV(BUF_PUTU_)
 
-/* --- @buf_putk64{,l,b}@ --- *
+/* --- @{,d}buf_putk64{,l,b}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @kludge64 w@ = word to write
  *
  * Returns:    Zero if OK, or nonzero if there wasn't enough space
@@ -342,9 +355,13 @@ int buf_putk64b(buf *b, kludge64 w)
   STORE64_B_(b->p, w); BSTEP(b, 8); return (0);
 }
 
+int (dbuf_putk64)(dbuf *db, kludge64 w) { return (dbuf_putk64(db, w)); }
+int (dbuf_putk64l)(dbuf *db, kludge64 w) { return (dbuf_putk64l(db, w)); }
+int (dbuf_putk64b)(dbuf *db, kludge64 w) { return (dbuf_putk64b(db, w)); }
+
 /* --- @findz@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t *nn@ = where to put the length
  *
  * Returns:    Zero if OK, nonzero if there wasn't a null byte to be found.
@@ -365,9 +382,9 @@ static int findz(buf *b, size_t *nn)
   return (0);
 }
 
-/* --- @buf_getmem{8,{16,24,32,64}{,l,b},z} --- *
+/* --- @{,d}buf_getmem{8,{16,24,32,64}{,l,b},z} --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t *nn@ = where to put the length
  *
  * Returns:    Pointer to the buffer data, or null.
@@ -385,7 +402,9 @@ static int findz(buf *b, size_t *nn)
     if (BENSURE(b, sz)) return (0);                                    \
     *nn = sz;                                                          \
     return (buf_get(b, sz));                                           \
-  }
+  }                                                                    \
+  void *(dbuf_getmem##w)(dbuf *db, size_t *nn)                         \
+    { return (dbuf_getmem##w(db, nn)); }
 DOUINTCONV(BUF_GETMEM_)
 
 void *buf_getmemz(buf *b, size_t *nn)
@@ -393,6 +412,8 @@ void *buf_getmemz(buf *b, size_t *nn)
   if (findz(b, nn)) return (0);
   return (buf_get(b, *nn));
 }
+void *(dbuf_getmemz)(dbuf *db, size_t *nn)
+  { return (dbuf_getmemz(db, nn)); }
 
 #ifndef HAVE_UINT64
 
@@ -414,27 +435,34 @@ void *buf_getmem64(buf *b, size_t *nn)
   return (getmem_k64(b, nn, k));
 }
 
-void *buf_getmem64b(buf *b, size_t *nn)
+void *buf_getmem64l(buf *b, size_t *nn)
 {
   kludge64 k;
 
-  if (buf_getk64b(b, &k)) return (-1);
+  if (buf_getk64l(b, &k)) return (-1);
   return (getmem_k64(b, nn, k));
 }
 
-void *buf_getmem64l(buf *b, size_t *nn)
+void *buf_getmem64b(buf *b, size_t *nn)
 {
   kludge64 k;
 
-  if (buf_getk64l(b, &k)) return (-1);
+  if (buf_getk64b(b, &k)) return (-1);
   return (getmem_k64(b, nn, k));
 }
 
+void *(dbuf_getmem64)(dbuf *db, size_t *nn)
+  { return (dbuf_getmem64(db, nn)); }
+void *(dbuf_getmem64l)(dbuf *db, size_t *nn)
+  { return (dbuf_getmem64l(db, nn)); }
+void *(dbuf_getmem64b)(dbuf *db, size_t *nn)
+  { return (dbuf_getmem64b(db, nn)); }
+
 #endif
 
-/* --- @buf_putmem{8,{16,24,32,64}{,l,b},z} --- *
+/* --- @{,d}buf_putmem{8,{16,24,32,64}{,l,b},z} --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @const void *p@ = pointer to data to write
  *             @size_t n@ = length to write
  *
@@ -454,7 +482,9 @@ void *buf_getmem64l(buf *b, size_t *nn)
     if (buf_putu##w(b, sz) || buf_put(b, p, sz))                       \
       return (-1);                                                     \
     return (0);                                                                \
-  }
+  }                                                                    \
+  int (dbuf_putmem##w)(dbuf *db, const void *p, size_t sz)             \
+    { return (dbuf_putmem##w(db, p, sz)); }
 DOUINTCONV(BUF_PUTMEM_)
 
 #ifndef HAVE_UINT64
@@ -467,22 +497,29 @@ void *buf_putmem64(buf *b, const void *p, size_t n)
   return (0);
 }
 
-void *buf_putmem64b(buf *b, const void *p, size_t n)
+void *buf_putmem64l(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);
+  ASSIGN64(k, n); if (buf_putk64l(b, k) || buf_put(b, p, n)) return (-1);
   return (0);
 }
 
-void *buf_putmem64l(buf *b, const void *p, size_t n)
+void *buf_putmem64b(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);
+  ASSIGN64(k, n); if (buf_putk64b(b, k) || buf_put(b, p, n)) return (-1);
   return (0);
 }
 
+int (dbuf_putmem64)(dbuf *db, const void *p, size_t n)
+  { return (dbuf_putmem64(db, p, n)); }
+int (dbuf_putmem64l)(dbuf *db, const void *p, size_t n)
+  { return (dbuf_putmem64l(db, p, n)); }
+int (dbuf_putmem64b)(dbuf *db, const void *p, size_t n)
+  { return (dbuf_putmem64b(db, p, n)); }
+
 #endif
 
 int buf_putmemz(buf *b, const void *p, size_t n)
@@ -496,10 +533,12 @@ int buf_putmemz(buf *b, const void *p, size_t n)
   q[n] = 0;
   return (0);
 }
+int (dbuf_putmemz)(dbuf *db, const void *p, size_t n)
+  { return (dbuf_putmemz(db, p, n)); }
 
-/* --- @buf_getbuf{8,{16,24,32,64}{,l,b},z} --- *
+/* --- @{,d}buf_getbuf{8,{16,24,32,64}{,l,b},z} --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @buf *bb@ = where to put the result
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -518,12 +557,14 @@ int buf_putmemz(buf *b, const void *p, size_t n)
       return (-1);                                                     \
     buf_init(bb, p, sz);                                               \
     return (0);                                                                \
-  }
+  }                                                                    \
+  int (dbuf_getbuf##w)(dbuf *db, buf *bb)                              \
+    { return (dbuf_getbuf##w(db, bb)); }
 BUF_DOSUFFIXES(BUF_GETBUF_)
 
-/* --- @buf_putbuf{8,{16,24,32,64}{,l,b},z} --- *
+/* --- @{,d}buf_putbuf{8,{16,24,32,64}{,l,b},z} --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @buf *bb@ = buffer to write
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -533,12 +574,14 @@ BUF_DOSUFFIXES(BUF_GETBUF_)
 
 #define BUF_PUTBUF_(n, W, w)                                           \
   int buf_putbuf##w(buf *b, buf *bb)                                   \
-    { return (buf_putmem##w(b, BBASE(bb), BLEN(bb))); }
+    { return (buf_putmem##w(b, BBASE(bb), BLEN(bb))); }                        \
+  int (dbuf_putbuf##w)(dbuf *db, buf *bb)                              \
+    { return (dbuf_putbuf##w(db, bb)); }
 BUF_DOSUFFIXES(BUF_PUTBUF_)
 
-/* --- @buf_putstr{8,{16,24,32,64}{,l,b},z} --- *
+/* --- @{,d}buf_putstr{8,{16,24,32,64}{,l,b},z} --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @const char *p@ = string to write
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -548,7 +591,9 @@ BUF_DOSUFFIXES(BUF_PUTBUF_)
 
 #define BUF_PUTSTR_(n, W, w)                                           \
   int buf_putstr##w(buf *b, const char *p)                             \
-    { return (buf_putmem##w(b, p, strlen(p))); }
+    { return (buf_putmem##w(b, p, strlen(p))); }                       \
+  int (dbuf_putstr##w)(dbuf *db, const char *p)                                \
+    { return (dbuf_putstr##w(db, p)); }
 BUF_DOSUFFIXES(BUF_PUTSTR_)
 
 /*----- That's all, folks -------------------------------------------------*/
index 6193cd3fb4dbead04807bed9d92db138828ccae4..7dd555541257a3d48fdf8aec32293787206ae432 100644 (file)
@@ -93,14 +93,8 @@ extern const struct gprintf_ops buf_printops;
 #define BBAD(b) ((b)->f & BF_BROKEN)
 #define BOK(b) (!BBAD(b))
 
-#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) ? buf_tryextend(b, sz) : 0))
-#else
-#  define BENSURE(b, sz)                                               \
+#define BENSURE(b, sz)                                         \
      (BBAD(b) ? -1 : (sz) > BLEFT(b) ? buf_tryextend(b, sz) : 0)
-#endif
 
 #define DBBASE(db) BBASE(DBUF_BUF(db))
 #define DBLIM(db) BLIM(DBUF_BUF(db))
@@ -160,6 +154,11 @@ extern void dbuf_create(dbuf */*db*/);
 
 extern void dbuf_reset(dbuf */*db*/);
 
+#define DBRESET(db) do {                                               \
+  (db)->_b.p = (db)->_b.base; (db)->_b.limit = (db)->_b.base + (db)->sz; \
+  (db)->_b.f = ((db)->_b.f&~BF_BROKEN) | BF_WRITE;                     \
+} while (0)
+
 /* --- @dbuf_destroy@ --- *
  *
  * Arguments:  @dbuf *db@ = pointer to a buffer block
@@ -171,9 +170,9 @@ extern void dbuf_reset(dbuf */*db*/);
 
 extern void dbuf_destroy(dbuf */*db*/);
 
-/* --- @buf_break@ --- *
+/* --- @{,d}buf_break@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *
  * Returns:    Some negative value.
  *
@@ -181,10 +180,12 @@ extern void dbuf_destroy(dbuf */*db*/);
  */
 
 extern int buf_break(buf */*b*/);
+extern int dbuf_break(dbuf */*db*/);
+#define dbuf_break(db) (buf_break(DBUF_BUF(db)))
 
-/* --- @buf_flip@ --- *
+/* --- @{,d}buf_flip@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *
  * Returns:    ---
  *
@@ -193,10 +194,18 @@ extern int buf_break(buf */*b*/);
  */
 
 extern void buf_flip(buf */*b*/);
+extern void dbuf_flip(dbuf */*db*/);
+#define dbuf_flip(db) (buf_flip(DBUF_BUF(db)))
+
+#define BFLIP(b) do {                                                  \
+  (b)->limit = (b)->p; (b)->p = (b)->base;                             \
+  (b)->f &= ~BF_WRITE;                                                 \
+} while (0)
+#define DBFLIP(db) BFLIP(DBUF_BUF(db))
 
-/* --- @buf_ensure@ --- *
+/* --- @{,d}buf_ensure@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t sz@ = size of data wanted
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -205,10 +214,12 @@ extern void buf_flip(buf */*b*/);
  */
 
 extern int buf_ensure(buf */*b*/, size_t /*sz*/);
+extern int dbuf_ensure(dbuf */*db*/, size_t /*sz*/);
+#define dbuf_ensure(db, sz) (buf_ensure(DBUF_BUF(db), (sz)))
 
-/* --- @buf_tryextend@ --- *
+/* --- @{,d}buf_tryextend@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t sz@ = size of data wanted
  *
  * Returns:    Zero if it worked, nonzero if the buffer won't grow.
@@ -218,10 +229,12 @@ extern int buf_ensure(buf */*b*/, size_t /*sz*/);
  */
 
 extern int buf_tryextend(buf */*b*/, size_t /*sz*/);
+extern int dbuf_tryextend(dbuf */*db*/, size_t /*sz*/);
+#define dbuf_tryextend(db, sz) (buf_tryextend(DBUF_BUF(db), (sz)))
 
-/* --- @buf_get@ --- *
+/* --- @{,d}buf_get@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t sz@ = size of the buffer
  *
  * Returns:    Pointer to the place in the buffer.
@@ -231,10 +244,12 @@ extern int buf_tryextend(buf */*b*/, size_t /*sz*/);
  */
 
 extern void *buf_get(buf */*b*/, size_t /*sz*/);
+extern void *dbuf_get(dbuf */*db*/, size_t /*sz*/);
+#define dbuf_get(db, sz) (buf_get(DBUF_BUF(db), (sz)))
 
-/* --- @buf_put@ --- *
+/* --- @{,d}buf_put@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @const void *p@ = pointer to a buffer
  *             @size_t sz@ = size of the buffer
  *
@@ -244,10 +259,12 @@ extern void *buf_get(buf */*b*/, size_t /*sz*/);
  */
 
 extern int buf_put(buf */*b*/, const void */*p*/, size_t /*sz*/);
+extern int dbuf_put(dbuf */*db*/, const void */*p*/, size_t /*sz*/);
+#define dbuf_put(db, p, sz) (buf_put(DBUF_BUF(db), (p), (sz)))
 
-/* --- @buf_getbyte@ --- *
+/* --- @{,d}buf_getbyte@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *
  * Returns:    A byte, or less than zero if there wasn't a byte there.
  *
@@ -255,10 +272,12 @@ extern int buf_put(buf */*b*/, const void */*p*/, size_t /*sz*/);
  */
 
 extern int buf_getbyte(buf */*b*/);
+extern int dbuf_getbyte(dbuf */*db*/);
+#define dbuf_getbyte(db) (buf_getbyte(DBUF_BUF(db)))
 
-/* --- @buf_putbyte@ --- *
+/* --- @{,d}buf_putbyte@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @int ch@ = byte to write
  *
  * Returns:    Zero if OK, nonzero if there wasn't enough space.
@@ -267,10 +286,12 @@ extern int buf_getbyte(buf */*b*/);
  */
 
 extern int buf_putbyte(buf */*b*/, int /*ch*/);
+extern int dbuf_putbyte(dbuf */*db*/, int /*ch*/);
+#define dbuf_putbyte(db, ch) (buf_putbyte(DBUF_BUF(db), (ch)))
 
-/* --- @buf_getu{8,{16,24,32,64}{,l,b}}@ --- *
+/* --- @{,d}buf_getu{8,{16,24,32,64}{,l,b}}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @uintSZ *w@ = where to put the word
  *
  * Returns:    Zero if OK, or nonzero if there wasn't a word there.
@@ -279,12 +300,28 @@ extern int buf_putbyte(buf */*b*/, int /*ch*/);
  */
 
 #define BUF_DECL_GETU_(n, W, w)                                                \
-  extern int buf_getu##w(buf */*b*/, uint##n */*w*/);
+  extern int buf_getu##w(buf */*b*/, uint##n */*w*/);                  \
+  extern int dbuf_getu##w(dbuf */*db*/, uint##n */*w*/);
 DOUINTCONV(BUF_DECL_GETU_)
+#define dbuf_getu8(db, w) (buf_getu8(DBUF_BUF(db), (w)))
+#define dbuf_getu16(db, w) (buf_getu16(DBUF_BUF(db), (w)))
+#define dbuf_getu16l(db, w) (buf_getu16l(DBUF_BUF(db), (w)))
+#define dbuf_getu16b(db, w) (buf_getu16b(DBUF_BUF(db), (w)))
+#define dbuf_getu24(db, w) (buf_getu24(DBUF_BUF(db), (w)))
+#define dbuf_getu24l(db, w) (buf_getu24l(DBUF_BUF(db), (w)))
+#define dbuf_getu24b(db, w) (buf_getu24b(DBUF_BUF(db), (w)))
+#define dbuf_getu32(db, w) (buf_getu32(DBUF_BUF(db), (w)))
+#define dbuf_getu32l(db, w) (buf_getu32l(DBUF_BUF(db), (w)))
+#define dbuf_getu32b(db, w) (buf_getu32b(DBUF_BUF(db), (w)))
+#ifdef HAVE_UINT64
+#  define dbuf_getu64(db, w) (buf_getu64(DBUF_BUF(db), (w)))
+#  define dbuf_getu64l(db, w) (buf_getu64l(DBUF_BUF(db), (w)))
+#  define dbuf_getu64b(db, w) (buf_getu64b(DBUF_BUF(db), (w)))
+#endif
 
-/* --- @buf_getk64{,l,b}@ --- *
+/* --- @{,d}buf_getk64{,l,b}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @kludge64 *w@ = where to put the word
  *
  * Returns:    Zero if OK, or nonzero if there wasn't a word there.
@@ -295,10 +332,16 @@ DOUINTCONV(BUF_DECL_GETU_)
 extern int buf_getk64(buf */*b*/, kludge64 */*w*/);
 extern int buf_getk64l(buf */*b*/, kludge64 */*w*/);
 extern int buf_getk64b(buf */*b*/, kludge64 */*w*/);
+extern int dbuf_getk64(dbuf */*db*/, kludge64 */*w*/);
+extern int dbuf_getk64l(dbuf */*db*/, kludge64 */*w*/);
+extern int dbuf_getk64b(dbuf */*db*/, kludge64 */*w*/);
+#define dbuf_getk64(db, w) (buf_getk64(DBUF_BUF(db), (w)))
+#define dbuf_getk64l(db, w) (buf_getk64l(DBUF_BUF(db), (w)))
+#define dbuf_getk64b(db, w) (buf_getk64b(DBUF_BUF(db), (w)))
 
-/* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- *
+/* --- @{,d}buf_putu{8,{16,24,32,64}{,l,b}}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @uintSZ w@ = word to write
  *
  * Returns:    Zero if OK, or nonzero if there wasn't enough space
@@ -307,12 +350,28 @@ extern int buf_getk64b(buf */*b*/, kludge64 */*w*/);
  */
 
 #define BUF_DECL_PUTU_(n, W, w)                                                \
-  extern int buf_putu##w(buf */*b*/, uint##n /*w*/);
+  extern int buf_putu##w(buf */*b*/, uint##n /*w*/);                   \
+  extern int dbuf_putu##w(dbuf */*db*/, uint##n /*w*/);
 DOUINTCONV(BUF_DECL_PUTU_)
+#define dbuf_putu8(db, w) (buf_putu8(DBUF_BUF(db), (w)))
+#define dbuf_putu16(db, w) (buf_putu16(DBUF_BUF(db), (w)))
+#define dbuf_putu16l(db, w) (buf_putu16l(DBUF_BUF(db), (w)))
+#define dbuf_putu16b(db, w) (buf_putu16b(DBUF_BUF(db), (w)))
+#define dbuf_putu24(db, w) (buf_putu24(DBUF_BUF(db), (w)))
+#define dbuf_putu24l(db, w) (buf_putu24l(DBUF_BUF(db), (w)))
+#define dbuf_putu24b(db, w) (buf_putu24b(DBUF_BUF(db), (w)))
+#define dbuf_putu32(db, w) (buf_putu32(DBUF_BUF(db), (w)))
+#define dbuf_putu32l(db, w) (buf_putu32l(DBUF_BUF(db), (w)))
+#define dbuf_putu32b(db, w) (buf_putu32b(DBUF_BUF(db), (w)))
+#ifdef HAVE_UINT64
+#  define dbuf_putu64(db, w) (buf_putu64(DBUF_BUF(db), (w)))
+#  define dbuf_putu64l(db, w) (buf_putu64l(DBUF_BUF(db), (w)))
+#  define dbuf_putu64b(db, w) (buf_putu64b(DBUF_BUF(db), (w)))
+#endif
 
-/* --- @buf_putk64{,l,b}@ --- *
+/* --- @{,d}buf_putk64{,l,b}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @kludge64 w@ = word to write
  *
  * Returns:    Zero if OK, or nonzero if there wasn't enough space
@@ -323,10 +382,16 @@ DOUINTCONV(BUF_DECL_PUTU_)
 extern int buf_putk64(buf */*b*/, kludge64 /*w*/);
 extern int buf_putk64l(buf */*b*/, kludge64 /*w*/);
 extern int buf_putk64b(buf */*b*/, kludge64 /*w*/);
+extern int dbuf_putk64(dbuf */*db*/, kludge64 /*w*/);
+extern int dbuf_putk64l(dbuf */*db*/, kludge64 /*w*/);
+extern int dbuf_putk64b(dbuf */*db*/, kludge64 /*w*/);
+#define dbuf_putk64(db, w) (buf_putk64(DBUF_BUF(db), (w)))
+#define dbuf_putk64l(db, w) (buf_putk64l(DBUF_BUF(db), (w)))
+#define dbuf_putk64b(db, w) (buf_putk64b(DBUF_BUF(db), (w)))
 
-/* --- @buf_getmem{8,{16,24,32,64}{,l,b},z} --- *
+/* --- @{,d}buf_getmem{8,{16,24,32,64}{,l,b},z} --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer block
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @size_t *nn@ = where to put the length
  *
  * Returns:    Pointer to the buffer data, or null.
@@ -337,12 +402,27 @@ extern int buf_putk64b(buf */*b*/, kludge64 /*w*/);
  */
 
 #define BUF_DECL_GETMEM_(n, W, w)                                      \
-  extern void *buf_getmem##w(buf */*b*/, size_t */*nn*/);
+  extern void *buf_getmem##w(buf */*b*/, size_t */*nn*/);              \
+  extern void *dbuf_getmem##w(dbuf */*db*/, size_t */*nn*/);
 BUF_DOSUFFIXES(BUF_DECL_GETMEM_)
-
-/* --- @buf_putmem{8,{16,24,32,64}{,l,b},z} --- *
- *
- * Arguments:  @buf *b@ = pointer to a buffer block
+#define dbuf_getmem8(db, nn) (buf_getmem8(DBUF_BUF(db), (nn)))
+#define dbuf_getmem16(db, nn) (buf_getmem16(DBUF_BUF(db), (nn)))
+#define dbuf_getmem16l(db, nn) (buf_getmem16l(DBUF_BUF(db), (nn)))
+#define dbuf_getmem16b(db, nn) (buf_getmem16b(DBUF_BUF(db), (nn)))
+#define dbuf_getmem24(db, nn) (buf_getmem24(DBUF_BUF(db), (nn)))
+#define dbuf_getmem24l(db, nn) (buf_getmem24l(DBUF_BUF(db), (nn)))
+#define dbuf_getmem24b(db, nn) (buf_getmem24b(DBUF_BUF(db), (nn)))
+#define dbuf_getmem32(db, nn) (buf_getmem32(DBUF_BUF(db), (nn)))
+#define dbuf_getmem32l(db, nn) (buf_getmem32l(DBUF_BUF(db), (nn)))
+#define dbuf_getmem32b(db, nn) (buf_getmem32b(DBUF_BUF(db), (nn)))
+#define dbuf_getmem64(db, nn) (buf_getmem64(DBUF_BUF(db), (nn)))
+#define dbuf_getmem64l(db, nn) (buf_getmem64l(DBUF_BUF(db), (nn)))
+#define dbuf_getmem64b(db, nn) (buf_getmem64b(DBUF_BUF(db), (nn)))
+#define dbuf_getmemz(db, nn) (buf_getmemz(DBUF_BUF(db), (nn)))
+
+/* --- @{,d}buf_putmem{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @const void *p@ = pointer to data to write
  *             @size_t n@ = length to write
  *
@@ -354,12 +434,27 @@ BUF_DOSUFFIXES(BUF_DECL_GETMEM_)
  */
 
 #define BUF_DECL_PUTMEM_(n, W, w)                                      \
-  extern int buf_putmem##w(buf */*b*/, const void */*p*/, size_t /*nn*/);
+  extern int buf_putmem##w(buf */*b*/, const void */*p*/, size_t /*nn*/); \
+  extern int dbuf_putmem##w(dbuf */*db*/, const void */*p*/, size_t /*nn*/);
 BUF_DOSUFFIXES(BUF_DECL_PUTMEM_)
-
-/* --- @buf_getbuf{8,{16,24,32,64}{,l,b},z} --- *
- *
- * Arguments:  @buf *b@ = pointer to a buffer block
+#define dbuf_putmem8(db, p, nn) (buf_putmem8(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem16(db, p, nn) (buf_putmem16(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem16l(db, p, nn) (buf_putmem16l(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem16b(db, p, nn) (buf_putmem16b(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem24(db, p, nn) (buf_putmem24(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem24l(db, p, nn) (buf_putmem24l(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem24b(db, p, nn) (buf_putmem24b(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem32(db, p, nn) (buf_putmem32(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem32l(db, p, nn) (buf_putmem32l(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem32b(db, p, nn) (buf_putmem32b(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem64(db, p, nn) (buf_putmem64(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem64l(db, p, nn) (buf_putmem64l(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmem64b(db, p, nn) (buf_putmem64b(DBUF_BUF(db), (p), (nn)))
+#define dbuf_putmemz(db, p, nn) (buf_putmemz(DBUF_BUF(db), (p), (nn)))
+
+/* --- @{,d}buf_getbuf{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @buf *bb@ = where to put the result
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -369,12 +464,27 @@ BUF_DOSUFFIXES(BUF_DECL_PUTMEM_)
  */
 
 #define BUF_DECL_GETBUF_(n, W, w)                                      \
-  extern int buf_getbuf##w(buf */*b*/, buf */*bb*/);
+  extern int buf_getbuf##w(buf */*b*/, buf */*bb*/);                   \
+  extern int dbuf_getbuf##w(dbuf */*db*/, buf */*bb*/);
 BUF_DOSUFFIXES(BUF_DECL_GETBUF_)
-
-/* --- @buf_putbuf{8,{16,24,32,64}{,l,b},z} --- *
- *
- * Arguments:  @buf *b@ = pointer to a buffer block
+#define dbuf_getbuf8(db, bb) (buf_getbuf8(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf16(db, bb) (buf_getbuf16(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf16l(db, bb) (buf_getbuf16l(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf16b(db, bb) (buf_getbuf16b(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf24(db, bb) (buf_getbuf24(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf24l(db, bb) (buf_getbuf24l(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf24b(db, bb) (buf_getbuf24b(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf32(db, bb) (buf_getbuf32(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf32l(db, bb) (buf_getbuf32l(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf32b(db, bb) (buf_getbuf32b(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf64(db, bb) (buf_getbuf64(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf64l(db, bb) (buf_getbuf64l(DBUF_BUF(db), (bb)))
+#define dbuf_getbuf64b(db, bb) (buf_getbuf64b(DBUF_BUF(db), (bb)))
+#define dbuf_getbufz(db, bb) (buf_getbufz(DBUF_BUF(db), (bb)))
+
+/* --- @{,d}buf_putbuf{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @buf *bb@ = buffer to write
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -383,12 +493,27 @@ BUF_DOSUFFIXES(BUF_DECL_GETBUF_)
  */
 
 #define BUF_DECL_PUTBUF_(n, W, w)                                      \
-  extern int buf_putbuf##w(buf */*b*/, buf */*bb*/);
+  extern int buf_putbuf##w(buf */*b*/, buf */*bb*/);                   \
+  extern int dbuf_putbuf##w(dbuf */*db*/, buf */*bb*/);
 BUF_DOSUFFIXES(BUF_DECL_PUTBUF_)
-
-/* --- @buf_getdstr{8,{16,24,32,64}{,l,b},z} --- *
- *
- * Arguments:  @buf *b@ = pointer to a buffer block
+#define dbuf_putbuf8(db, bb) (buf_putbuf8(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf16(db, bb) (buf_putbuf16(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf16l(db, bb) (buf_putbuf16l(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf16b(db, bb) (buf_putbuf16b(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf24(db, bb) (buf_putbuf24(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf24l(db, bb) (buf_putbuf24l(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf24b(db, bb) (buf_putbuf24b(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf32(db, bb) (buf_putbuf32(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf32l(db, bb) (buf_putbuf32l(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf32b(db, bb) (buf_putbuf32b(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf64(db, bb) (buf_putbuf64(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf64l(db, bb) (buf_putbuf64l(DBUF_BUF(db), (bb)))
+#define dbuf_putbuf64b(db, bb) (buf_putbuf64b(DBUF_BUF(db), (bb)))
+#define dbuf_putbufz(db, bb) (buf_putbufz(DBUF_BUF(db), (bb)))
+
+/* --- @{,d}buf_getdstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @dstr *d@ = where to put the result
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -398,12 +523,27 @@ BUF_DOSUFFIXES(BUF_DECL_PUTBUF_)
  */
 
 #define BUF_DECL_GETDSTR_(n, W, w)                                     \
-  extern int buf_getdstr##w(buf */*b*/, dstr */*d*/);
+  extern int buf_getdstr##w(buf */*b*/, dstr */*d*/);                  \
+  extern int dbuf_getdstr##w(dbuf */*db*/, dstr */*d*/);
 BUF_DOSUFFIXES(BUF_DECL_GETDSTR_)
-
-/* --- @buf_putdstr{8,{16,24,32,64}{,l,b},z} --- *
- *
- * Arguments:  @buf *b@ = pointer to a buffer block
+#define dbuf_getdstr8(db, d) (buf_getdstr8(DBUF_BUF(db), (d)))
+#define dbuf_getdstr16(db, d) (buf_getdstr16(DBUF_BUF(db), (d)))
+#define dbuf_getdstr16l(db, d) (buf_getdstr16l(DBUF_BUF(db), (d)))
+#define dbuf_getdstr16b(db, d) (buf_getdstr16b(DBUF_BUF(db), (d)))
+#define dbuf_getdstr24(db, d) (buf_getdstr24(DBUF_BUF(db), (d)))
+#define dbuf_getdstr24l(db, d) (buf_getdstr24l(DBUF_BUF(db), (d)))
+#define dbuf_getdstr24b(db, d) (buf_getdstr24b(DBUF_BUF(db), (d)))
+#define dbuf_getdstr32(db, d) (buf_getdstr32(DBUF_BUF(db), (d)))
+#define dbuf_getdstr32l(db, d) (buf_getdstr32l(DBUF_BUF(db), (d)))
+#define dbuf_getdstr32b(db, d) (buf_getdstr32b(DBUF_BUF(db), (d)))
+#define dbuf_getdstr64(db, d) (buf_getdstr64(DBUF_BUF(db), (d)))
+#define dbuf_getdstr64l(db, d) (buf_getdstr64l(DBUF_BUF(db), (d)))
+#define dbuf_getdstr64b(db, d) (buf_getdstr64b(DBUF_BUF(db), (d)))
+#define dbuf_getdstrz(db, d) (buf_getdstrz(DBUF_BUF(db), (d)))
+
+/* --- @{,d}buf_putdstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @dstr *d@ = string to write
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -412,12 +552,27 @@ BUF_DOSUFFIXES(BUF_DECL_GETDSTR_)
  */
 
 #define BUF_DECL_PUTDSTR_(n, W, w)                                     \
-  extern int buf_putdstr##w(buf */*b*/, dstr */*d*/);
+  extern int buf_putdstr##w(buf */*b*/, dstr */*d*/);                  \
+  extern int dbuf_putdstr##w(dbuf */*db*/, dstr */*d*/);
 BUF_DOSUFFIXES(BUF_DECL_PUTDSTR_)
-
-/* --- @buf_putstr{8,{16,24,32,64}{,l,b},z} --- *
- *
- * Arguments:  @buf *b@ = pointer to a buffer block
+#define dbuf_putdstr8(db, d) (buf_putdstr8(DBUF_BUF(db), (d)))
+#define dbuf_putdstr16(db, d) (buf_putdstr16(DBUF_BUF(db), (d)))
+#define dbuf_putdstr16l(db, d) (buf_putdstr16l(DBUF_BUF(db), (d)))
+#define dbuf_putdstr16b(db, d) (buf_putdstr16b(DBUF_BUF(db), (d)))
+#define dbuf_putdstr24(db, d) (buf_putdstr24(DBUF_BUF(db), (d)))
+#define dbuf_putdstr24l(db, d) (buf_putdstr24l(DBUF_BUF(db), (d)))
+#define dbuf_putdstr24b(db, d) (buf_putdstr24b(DBUF_BUF(db), (d)))
+#define dbuf_putdstr32(db, d) (buf_putdstr32(DBUF_BUF(db), (d)))
+#define dbuf_putdstr32l(db, d) (buf_putdstr32l(DBUF_BUF(db), (d)))
+#define dbuf_putdstr32b(db, d) (buf_putdstr32b(DBUF_BUF(db), (d)))
+#define dbuf_putdstr64(db, d) (buf_putdstr64(DBUF_BUF(db), (d)))
+#define dbuf_putdstr64l(db, d) (buf_putdstr64l(DBUF_BUF(db), (d)))
+#define dbuf_putdstr64b(db, d) (buf_putdstr64b(DBUF_BUF(db), (d)))
+#define dbuf_putdstrz(db, d) (buf_putdstrz(DBUF_BUF(db), (d)))
+
+/* --- @{,d}buf_putstr{8,{16,24,32,64}{,l,b},z} --- *
+ *
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @const char *p@ = string to write
  *
  * Returns:    Zero if it worked, nonzero if there wasn't enough space.
@@ -426,12 +581,55 @@ BUF_DOSUFFIXES(BUF_DECL_PUTDSTR_)
  */
 
 #define BUF_DECL_PUTSTR_(n, W, w)                                      \
-  extern int buf_putstr##w(buf */*b*/, const char */*p*/);
+  extern int buf_putstr##w(buf */*b*/, const char */*p*/);             \
+  extern int dbuf_putstr##w(dbuf */*db*/, const char */*p*/);
 BUF_DOSUFFIXES(BUF_DECL_PUTSTR_)
+#define dbuf_putstr8(db, p) (buf_putstr8(DBUF_BUF(db), (p)))
+#define dbuf_putstr16(db, p) (buf_putstr16(DBUF_BUF(db), (p)))
+#define dbuf_putstr16l(db, p) (buf_putstr16l(DBUF_BUF(db), (p)))
+#define dbuf_putstr16b(db, p) (buf_putstr16b(DBUF_BUF(db), (p)))
+#define dbuf_putstr24(db, p) (buf_putstr24(DBUF_BUF(db), (p)))
+#define dbuf_putstr24l(db, p) (buf_putstr24l(DBUF_BUF(db), (p)))
+#define dbuf_putstr24b(db, p) (buf_putstr24b(DBUF_BUF(db), (p)))
+#define dbuf_putstr32(db, p) (buf_putstr32(DBUF_BUF(db), (p)))
+#define dbuf_putstr32l(db, p) (buf_putstr32l(DBUF_BUF(db), (p)))
+#define dbuf_putstr32b(db, p) (buf_putstr32b(DBUF_BUF(db), (p)))
+#define dbuf_putstr64(db, p) (buf_putstr64(DBUF_BUF(db), (p)))
+#define dbuf_putstr64l(db, p) (buf_putstr64l(DBUF_BUF(db), (p)))
+#define dbuf_putstr64b(db, p) (buf_putstr64b(DBUF_BUF(db), (p)))
+#define dbuf_putstrz(db, p) (buf_putstrz(DBUF_BUF(db), (p)))
+
+/* --- @{,d}buf_getf64{,l,b} --- *
+ *
+ * Arguments:  @buf *b@ = pointer to a bfufer block
+ *             @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_getf64l(buf */*b*/, double */*x_out*/);
+extern int buf_getf64b(buf */*b*/, double */*x_out*/);
+extern int dbuf_getf64(dbuf */*db*/, double */*x_out*/);
+extern int dbuf_getf64l(dbuf */*db*/, double */*x_out*/);
+extern int dbuf_getf64b(dbuf */*db*/, double */*x_out*/);
+#define dbuf_getf64(db, x_out) (buf_getf64(DBUF_BUF(db), (x_out)))
+#define dbuf_getf64l(db, x_out) (buf_getf64l(DBUF_BUF(db), (x_out)))
+#define dbuf_getf64b(db, x_out) (buf_getf64b(DBUF_BUF(db), (x_out)))
 
-/* --- @buf_putf64{,b,l} --- *
+/* --- @{,d}buf_putf64{,l,b} --- *
  *
- * Arguments:  @buf *b@ = a buffer to write to
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @double x@ = a number to write
  *
  * Returns:    Zero on success, @-1@ on failure (and the buffer is broken).
@@ -449,45 +647,63 @@ BUF_DOSUFFIXES(BUF_DECL_PUTSTR_)
  */
 
 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_putf64b(buf */*b*/, double /*x*/);
+extern int dbuf_putf64(dbuf */*db*/, double /*x*/);
+extern int dbuf_putf64l(dbuf */*db*/, double /*x*/);
+extern int dbuf_putf64b(dbuf */*db*/, double /*x*/);
+#define dbuf_putf64(db, x) (buf_putf64(DBUF_BUF(db), (x)))
+#define dbuf_putf64l(db, x) (buf_putf64l(DBUF_BUF(db), (x)))
+#define dbuf_putf64b(db, x) (buf_putf64b(DBUF_BUF(db), (x)))
+
+/* --- @{,D}BUF_ENCLOSETAG@ --- *
+ *
+ * Arguments:  @tag@ = a control-structure macro tag
+ *             @buf *b@ or @dbuf *db@ = pointer to a buffer block
+ *             @size_t mk@ = temporary, used to stash starting offset
+ *             @check@ = expression which is true if the length @_delta@
+ *                     is representable
+ *             @poke@ = function or macro called as @poke(octet *, size_t)@
+ *                     to store the final size at the given address
+ *             @size_t lensz@ = space to leave for the length
+ *
+ * Use:                This is a statement head.  It ensures that there is enough
+ *             space in the buffer, saves the current output offset in @mk,
+ *             and reserves @lensz@ bytes for a length prefix.  It then
+ *             executes the @body@, which should contribute zero or more
+ *             further bytes to the buffer.  Finally, it invokes @poke@ to
+ *             store the length of the material written by @body@ in the
+ *             space reserved.
  */
 
-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_ENCLOSETAG(tag, b, mk, check, poke, lensz)                 \
+  MC_BEFORE(tag##__save, {                                             \
+    (mk) = BLEN(b);                                                    \
+    if (!BENSURE(b, lensz)) BSTEP(b, (lensz));                         \
+  })                                                                   \
+  MC_AFTER(tag##__poke, {                                              \
+    size_t _delta = BLEN(b) - (mk) - (lensz);                          \
+    assert(check);                                                     \
+    if (BOK(b)) poke(BBASE(b) + (mk), _delta);                         \
+  })
+
+#define DBUF_ENCLOSETAG(tag, b, mk, check, poke, lensz)                        \
+  BUF_ENCLOSETAG(tag, DBUF_BUF(b), (mk), (check), poke, (lensz))
+
+/* --- @{,D}BUF_ENCLOSE{I,K,Z}TAG@ --- *
+ *
+ * Arguments:  @tag@ = a control-structure macro tag
+ *             @buf *b@ or @dbuf *db@ = pointer to a buffer block
+ *             @size_t mk@ = temporary, used to stash starting offset
+ *             @W@ = word-size and -order suffix
+ *
+ * Use:                Specialized versions of @BUF_ENCLOSETAG@ above.
+ *
+ *             @BUF_ENCLOSEZTAG@ just writes a terminating zero byte.
+ *             @BUF_ENCLOSEITAG@ writes a word with the given size and
+ *             byte ordering.  @BUF_ENCLOSEKTAG@ does the same using the
+ *             @kludge64@ machinery.
+ */
 
 #define BUF_STORESZK64(p, sz)                                          \
   do { kludge64 _k; ASSIGN64(_k, (sz)); STORE64_((p), _k); } while (0)
@@ -495,63 +711,105 @@ extern int buf_getf64l(buf */*b*/, double *x_/*out*/);
   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)
+
+#define BUF_ENCLOSEITAG(tag, b, mk, W)                                 \
+  BUF_ENCLOSETAG(tag, (b), (mk), (_delta <= MASK##W), STORE##W, SZ_##W)
+#define BUF_ENCLOSEKTAG(tag, b, mk, W)                         \
+  BUF_ENCLOSE(tag, (b), (mk), 1, BUF_STORESZK##W, 8)
+#define BUF_ENCLOSEZTAG(tag, b)                                                \
+  MC_AFTER(tag##__zero, { buf_putbyte((b), 0); })
+
+#define DBUF_ENCLOSEITAG(tag, b, mk, W)                                        \
+  BUF_ENCLOSEITAG(tag, DBUF_BUF(b), (mk), W)
+#define DBUF_ENCLOSEKTAG(tag, b, mk, W)                                        \
+  BUF_ENCLOSEKTAG(tag, DBUF_BUF(b), (mk), W)
+#define DBUF_ENCLOSEZTAG(tag, b)                                       \
+  BUF_ENCLOSEZTAG(tag, DBUF_BUF(b))
+
+/* --- @{,D}BUF_ENCLOSE{8,{16,24,32,64}{,_L,_B},Z}@ --- *
+ *
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
+ *             @size_t mk@ = temporary, used to stash starting offset
+ *             @W@ = word-size and -order suffix
+ *
+ * Use:                User versions of @BUF_ENCLOSETAG@; see that macro for
+ *             details.
+ *
+ *             These are statement heads.  They reserve space for a length
+ *             prefix and execute the statement.  When the statement
+ *             completes, they patch the length of material written by the
+ *             statement into the reserved space.
+ */
+
+#define BUF_ENCLOSE8(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 8)
+#define BUF_ENCLOSE16(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16)
+#define BUF_ENCLOSE16_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16_B)
+#define BUF_ENCLOSE16_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16_L)
+#define BUF_ENCLOSE24(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24)
+#define BUF_ENCLOSE24_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24_B)
+#define BUF_ENCLOSE24_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24_L)
+#define BUF_ENCLOSE32(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32)
+#define BUF_ENCLOSE32_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32_B)
+#define BUF_ENCLOSE32_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32_L)
+#ifdef HAVE_UINT64
+#  define BUF_ENCLOSE64(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64)
+#  define BUF_ENCLOSE64_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64_B)
+#  define BUF_ENCLOSE64_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64_L)
+#else
+#  define BUF_ENCLOSE64(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64)
+#  define BUF_ENCLOSE64_B(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64_B)
+#  define BUF_ENCLOSE64_L(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64_L)
+#endif
+#define BUF_ENCLOSEZ(b) BUF_ENCLOSEZTAG(encl, (b))
+
+#define DBUF_ENCLOSE8(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 8)
+#define DBUF_ENCLOSE16(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16)
+#define DBUF_ENCLOSE16_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16_B)
+#define DBUF_ENCLOSE16_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16_L)
+#define DBUF_ENCLOSE24(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24)
+#define DBUF_ENCLOSE24_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24_B)
+#define DBUF_ENCLOSE24_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24_L)
+#define DBUF_ENCLOSE32(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32)
+#define DBUF_ENCLOSE32_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32_B)
+#define DBUF_ENCLOSE32_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (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)
+#  define DBUF_ENCLOSE64(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64)
+#  define DBUF_ENCLOSE64_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64_B)
+#  define DBUF_ENCLOSE64_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (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)
+#  define DBUF_ENCLOSE64(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64)
+#  define DBUF_ENCLOSE64_B(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64_B)
+#  define DBUF_ENCLOSE64_L(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64_L)
 #endif
-#define BUF_ENCLOSEZ(buf) BUF_ENCLOSEZTAG(encl, buf)
+#define DBUF_ENCLOSEZ(db) DBUF_ENCLOSEZTAG(encl, (db))
 
-/* --- @buf_vputstrf@ --- *
+/* --- @{,d}buf_putstrf@, @{,d}buf_vputstrf@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @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*/, ...);
+extern PRINTF_LIKE(2, 3)
+  int buf_putstrf(buf */*b*/, const char */*p*/, ...);
+extern PRINTF_LIKE(2, 3)
+  int dbuf_putstrf(dbuf */*db*/, const char */*p*/, ...);
+#if __STDC__ >= 199901
+#  define dbuf_putstrf(db, /*p*/...) (buf_putstr(DBUF_BUF(db), __VA_ARGS__))
+#endif
+extern int buf_vputstrf(buf */*b*/, const char */*p*/, va_list */*ap*/);
+extern int dbuf_vputstrf(dbuf */*db*/, const char */*p*/, va_list */*ap*/);
+#define dbuf_vputstrf(db, p, ap) (buf_vputstrf(DBUF_BUF(db), (p), (ap)))
 
-/* --- @buf_{,v}putstrf{8,{16,24,32,64}{,b,l},z}@ --- *
+/* --- @{,d}buf_{,v}putstrf{8,{16,24,32,64}{,b,l},z}@ --- *
  *
- * Arguments:  @buf *b@ = pointer to a buffer
+ * Arguments:  @buf *b@ or @dbuf *db@ = pointer to a buffer block
  *             @const char *p@ = pointer to @printf@-style format string
  *             @va_list *ap@ = argument handle
  *
@@ -562,12 +820,59 @@ extern PRINTF_LIKE(2, 3) int buf_putstrf(buf */*b*/, const char */*p*/, ...);
  */
 
 #define BUF_DECL_PUTSTRF_(n, W, w)                                     \
+  extern PRINTF_LIKE(2, 3)                                             \
+    int buf_putstrf##w(buf */*b*/, const char */*p*/, ...);            \
+  extern PRINTF_LIKE(2, 3)                                             \
+    int dbuf_putstrf##w(dbuf */*db*/, const char */*p*/, ...);         \
   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*/, ...);
+  extern int dbuf_vputstrf##w(dbuf */*db*/,                            \
+                             const char */*p*/, va_list */*ap*/);
 BUF_DOSUFFIXES(BUF_DECL_PUTSTRF_)
-#undef BUF_DECL_PUTSTRF_
+#if __STDC__ >= 199901
+#  define dbuf_putstrf8(db, /*p*/...)                                  \
+     (buf_putstrf8(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf16(db, /*p*/...)                                 \
+     (buf_putstrf16(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf16l(db, /*p*/...)                                        \
+     (buf_putstrf16l(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf16b(db, /*p*/...)                                        \
+     (buf_putstrf16b(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf24(db, /*p*/...)                                 \
+     (buf_putstrf24(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf24l(db, /*p*/...)                                        \
+     (buf_putstrf24l(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf24b(db, /*p*/...)                                        \
+     (buf_putstrf24b(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf32(db, /*p*/...)                                 \
+     (buf_putstrf32(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf32l(db, /*p*/...)                                        \
+     (buf_putstrf32l(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf32b(db, /*p*/...)                                        \
+     (buf_putstrf32b(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf64(db, /*p*/...)                                 \
+     (buf_putstrf64(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf64l(db, /*p*/...)                                        \
+     (buf_putstrf64l(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrf64b(db, /*p*/...)                                        \
+     (buf_putstrf64b(DBUF_BUF(db), __VA_ARGS__))
+#  define dbuf_putstrfz(db, /*p*/...)                                  \
+     (buf_putstrfz(DBUF_BUF(db), __VA_ARGS__))
+#endif
+#define dbuf_vputstrf8(db, p, ap) (buf_vputstrf8(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf16(db, p, ap) (buf_vputstrf16(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf16l(db, p, ap) (buf_vputstrf16l(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf16b(db, p, ap) (buf_vputstrf16b(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf24(db, p, ap) (buf_vputstrf24(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf24l(db, p, ap) (buf_vputstrf24l(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf24b(db, p, ap) (buf_vputstrf24b(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf32(db, p, ap) (buf_vputstrf32(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf32l(db, p, ap) (buf_vputstrf32l(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf32b(db, p, ap) (buf_vputstrf32b(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf64(db, p, ap) (buf_vputstrf64(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf64l(db, p, ap) (buf_vputstrf64l(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrf64b(db, p, ap) (buf_vputstrf64b(DBUF_BUF(db), (p), (ap)))
+#define dbuf_vputstrfz(db, p, ap) (buf_vputstrfz(DBUF_BUF(db), (p), (ap)))
 
 /*----- That's all, folks -------------------------------------------------*/
 
index fedc48c70219759844f50ec34fa538d756181a0f..93c60e2d0ece96247daa0d9362c64a29ade2c950 100644 (file)
@@ -232,8 +232,7 @@ extern int dstr_vputf(dstr */*d*/, const char */*p*/, va_list */*ap*/);
  *             itself is malicious.
  */
 
-extern int PRINTF_LIKE(2, 3)
-  dstr_putf(dstr */*d*/, const char */*p*/, ...);
+extern PRINTF_LIKE(2, 3) int dstr_putf(dstr */*d*/, const char */*p*/, ...);
 
 /* --- @dstr_putd@ --- *
  *
index 044da71685085bf3976e91224a50d9b67a691dba..13fe155db205287dd2df90b97d7e90a453f69c35 100644 (file)
@@ -18,7 +18,7 @@ static char strbuf[1024];
 
 #define TESTGROUP(name) TVEC_TESTGROUP_TAG(grp, &tvstate, name)
 
-static int PRINTF_LIKE(1, 2) format(const char *fmt, ...)
+static PRINTF_LIKE(1, 2) int format(const char *fmt, ...)
 {
   va_list ap;
   int n;
@@ -30,7 +30,7 @@ static int PRINTF_LIKE(1, 2) format(const char *fmt, ...)
   return (n);
 }
 
-static void PRINTF_LIKE(1, 2) prepare(const char *fmt, ...)
+static PRINTF_LIKE(1, 2) void prepare(const char *fmt, ...)
 {
   va_list ap;
   int n;
index ee7816a9eb6e2e495d9907f92de0800c7c7a4230..e4cb2603207fc8c27b25140b41b3bbba0e9e63ff 100644 (file)
@@ -137,8 +137,9 @@ enum {
 
 #define D(x) x
 
-static void PRINTF_LIKE(4, 5) IGNORABLE
-  dump(mdup_fdinfo *v, size_t n, mdup_fdinfo *dhead, const char *fmt, ...)
+static PRINTF_LIKE(4, 5) IGNORABLE
+  void dump(mdup_fdinfo *v, size_t n, mdup_fdinfo *dhead,
+           const char *fmt, ...)
 {
   int i;
   mdup_fdinfo *f, *g;
index 7990ab7d2d6cece6bb8345bdf68a718a50570ac3..5a4b2ffd7f0cea49aa648bc1af820cbd9dadd9d1 100644 (file)
@@ -51,6 +51,7 @@ libtest_la_SOURCES    += tvec-main.c
 
 libtest_la_SOURCES     += tvec-bench.c
 libtest_la_SOURCES     += tvec-remote.c
+libtest_la_SOURCES     += tvec-timeout.c
 
 check_PROGRAMS         += t/tvec.t
 t_tvec_t_SOURCES        = t/tvec-test.c
index 9e65a6bcf9a9ce3c9966b291eba191c8b3ef002d..345944bca82bc5bf3bc9d50686564654af3b7dcb 100644 (file)
@@ -69,7 +69,7 @@ struct timer_ops {
  * Use:                Maybe report a debugging message to standard error.
  */
 
-static void PRINTF_LIKE(1, 2) debug(const char *fmt, ...)
+static PRINTF_LIKE(1, 2) void debug(const char *fmt, ...)
 {
   const char *p;
   va_list ap;
index a9ef269bb195b94cf3ad23e1324c07091426a42a..ac316b6e8441233030a19956e4670d5d274f544a 100644 (file)
 
 /*----- Header files ------------------------------------------------------*/
 
+#include "tv.h"
 #include "tvec.h"
 
+#include <sys/select.h>
+#include <sys/time.h>
+
 /*----- Register definitions ----------------------------------------------*/
 
 static const struct tvec_iassoc ienum_assocs[] = {
@@ -173,8 +177,7 @@ static void common_setup(struct tvec_state *tv,
   tctx->f = 0;
 }
 
-static int common_set(struct tvec_state *tv, const char *name,
-                     const struct tvec_env *env, void *ctx)
+static int common_set(struct tvec_state *tv, const char *name, void *ctx)
 {
   struct test_context *tctx = ctx;
   union tvec_regval rv;
@@ -442,7 +445,8 @@ static const struct tvec_env multi_serialize_testenv = {
 
 /*----- Crash test --------------------------------------------------------*/
 
-static void test_crash(const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
+static void test_crash(const struct tvec_reg *in, struct tvec_reg *out,
+                      void *ctx)
 {
   out[RVOUT].v.u = in[RV].v.u;
   if (in[RSAB].v.i) abort();
@@ -458,6 +462,38 @@ static const struct tvec_regdef crash_regs[] = {
   TVEC_ENDREGS
 };
 
+/*----- Sleep test --------------------------------------------------------*/
+
+static void test_sleep(const struct tvec_reg *in, struct tvec_reg *out,
+                      void *ctx)
+{
+  struct timeval now, when, tv;
+  int rc;
+
+  rc = gettimeofday(&now, 0); assert(!rc);
+  tv.tv_sec = in[RV].v.f; tv.tv_usec = 1e6*(in[RV].v.f - tv.tv_sec);
+
+  TV_ADD(&when, &now, &tv);
+  for (;;) {
+    rc = select(0, 0, 0, 0, &tv); assert(!rc);
+    rc = gettimeofday(&now, 0); assert(!rc);
+    if (TV_CMP(&now, >=, &when)) break;
+    TV_SUB(&tv, &when, &now);
+  }
+  out[RVOUT].v.f = in[RV].v.f;
+}
+
+static const struct tvec_timeoutenv sleep_subenv =
+  { TVEC_TIMEOUTINIT(ITIMER_REAL, 0.25) };
+static const struct tvec_remotefork sleep_testenv =
+  { TVEC_REMOTEFORK(&sleep_subenv._env, 0) };
+
+static const struct tvec_regdef sleep_regs[] = {
+  { "time",    RV,     &tvty_float,    0,              { &tvflt_nonneg } },
+  { "z",       RVOUT,  &tvty_float,    0,              { &tvflt_nonneg } },
+  TVEC_ENDREGS
+};
+
 /*----- Front end ---------------------------------------------------------*/
 
 static const struct tvec_test tests[] = {
@@ -473,7 +509,8 @@ static const struct tvec_test tests[] = {
   TYPEREGS(DEFSINGLE)
 #undef DEFSINGLE
 
-  { "crash",   crash_regs,     &crash_testenv._env, test_crash } ,
+  { "crash",   crash_regs,     &crash_testenv._env, test_crash },
+  { "sleep",   sleep_regs,     &sleep_testenv._env, test_sleep },
 
   TVEC_ENDTESTS
 };
index 5c9c0690cbdac82c83a21f753bc75c98dde9c202..fbb62bb2231f1b2640920975da6d7057e2b3eb29 100644 (file)
@@ -68,14 +68,14 @@ AT_DATA([tv],
 $1 = $2
 ])
 check_template([BUILDDIR/t/tvec.t -fh tv], [2],
-[tv:$3: $4
-tv:={N:\d+}: required register `$1' not set in test `copy-$1'
+[tv:$3: ERROR: $4
+tv:={N:\d+}: ERROR: required register `$1' not set in test `copy-$1'
 copy-$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 `copy-$1'
+[tvec.t: tv:$3: ERROR: $4
+tvec.t: tv:={N:\d+}: ERROR: required register `$1' not set in test `copy-$1'
 ])])
 
 ###--------------------------------------------------------------------------
index 8c8cfdf627f5b1be4e22a5e7c67a53d6bf6f5154..7086e14e2d8b3ba4302a122bfac6a4538449d7ff 100644 (file)
@@ -143,7 +143,6 @@ fail_timer:
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
  *             @const char *var@ = variable name to set
- *             @const struct tvec_env *env@ = environment description
  *             @void *ctx@ = context pointer
  *
  * Returns:    Zero on success, @-1@ on failure.
@@ -158,11 +157,10 @@ fail_timer:
  *             environment, if there is one.
  */
 
-int tvec_benchset(struct tvec_state *tv, const char *var,
-                 const struct tvec_env *env, void *ctx)
+int tvec_benchset(struct tvec_state *tv, const char *var, void *ctx)
 {
   struct tvec_benchctx *bc = ctx;
-  const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
+  const struct tvec_benchenv *be = bc->be;
   const struct tvec_env *subenv = be->env;
   union tvec_regval rv;
   static const struct tvec_floatinfo fi = { TVFF_NOMAX, 0.0, 0.0, 0.0 };
@@ -170,11 +168,11 @@ int tvec_benchset(struct tvec_state *tv, const char *var,
     { "@target", -1, &tvty_float, 0, { &fi } };
 
   if (STRCMP(var, ==, "@target")) {
+    if (bc->f&TVBF_SETTRG) return (tvec_dupreg(tv, var));
     if (tvty_float.parse(&rv, &rd, tv)) return (-1);
-    if (bc) bc->bst->target_s = rv.f;
-    return (1);
+    bc->bst->target_s = rv.f; bc->f |= TVBF_SETTRG; return (1);
   } else if (subenv && subenv->set)
-    return (subenv->set(tv, var, subenv, bc->subctx));
+    return (subenv->set(tv, var, bc->subctx));
   else
     return (0);
 }
@@ -219,6 +217,7 @@ void tvec_benchafter(struct tvec_state *tv, void *ctx)
 
   /* Restore the benchmark state's old target. */
   bc->bst->target_s = bc->dflt_target;
+  bc->f &= ~TVBF_SETTRG;
 
   /* Pass the call on to the subsidiary environment. */
   if (subenv && subenv->after) subenv->after(tv, bc->subctx);
@@ -240,11 +239,10 @@ void tvec_benchteardown(struct tvec_state *tv, void *ctx)
   const struct tvec_benchenv *be;
   const struct tvec_env *subenv;
 
-  if (!bc) return;
   be = bc->be; subenv = be->env;
 
   /* Tear down any subsidiary environment. */
-  if (subenv && subenv->teardown && bc->subctx)
+  if (subenv && subenv->teardown)
     subenv->teardown(tv, bc->subctx);
 
   /* If the benchmark state was temporary, then dispose of it. */
index 36a54edd7c7ce6167aeabdf8dedc1daec390668e..f6d7918520f263e5dba84eb3a1c60599ff59910b 100644 (file)
 
 /*----- Output ------------------------------------------------------------*/
 
+/* --- @tvec_strlevel@ --- *
+ *
+ * Arguments:  @unsigned level@ = level code
+ *
+ * Returns:    A human-readable description.
+ *
+ * Use:                Converts a level code into something that you can print in a
+ *             message.
+ */
+
+const char *tvec_strlevel(unsigned level)
+{
+  switch (level) {
+#define CASE(tag, name, val)                                           \
+    case TVLEV_##tag: return (name);
+    TVEC_LEVELS(CASE)
+#undef CASE
+    default: return ("??");
+  }
+}
+
 /* --- @tvec_report@, @tvec_report_v@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @unsigned level@ = severity level (@TVlEV_...@)
  *             @const char *msg@, @va_list ap@ = error message
  *
  * Returns:    ---
@@ -94,12 +116,25 @@ void tvec_notice(struct tvec_state *tv, const char *msg, ...)
 
 /*----- Test processing ---------------------------------------------------*/
 
+/* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *excuse@, @va_list *ap@ = reason why 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.
+ */
+
 void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
 {
   va_list ap;
 
   va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
 }
+
 void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
 {
   if (!(tv->f&TVSF_SKIP)) {
@@ -108,17 +143,38 @@ void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
   }
 }
 
+/* --- @set_outcome@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @unsigned out@ = the new outcome
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the outcome bits in the test state flags, and clears
+ *             @TVSF_ACTIVE@.
+ */
+
 static void set_outcome(struct tvec_state *tv, unsigned out)
-{
-  tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK);
-  tv->f |= out << TVSF_OUTSHIFT;
-}
+  { tv->f = (tv->f&~(TVSF_ACTIVE | TVSF_OUTMASK)) | (out << TVSF_OUTSHIFT); }
+
+/* --- @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.
+ */
 
 void tvec_skip(struct tvec_state *tv, const char *excuse, ...)
 {
   va_list ap;
   va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap);
 }
+
 void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap)
 {
   assert(tv->f&TVSF_ACTIVE);
@@ -126,11 +182,29 @@ void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap)
   tv->output->ops->skip(tv->output, excuse, ap);
 }
 
+/* --- @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.
+ */
+
 void tvec_fail(struct tvec_state *tv, const char *detail, ...)
 {
   va_list ap;
   va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap);
 }
+
 void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap)
 {
   assert((tv->f&TVSF_ACTIVE) ||
@@ -138,11 +212,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);
 }
 
+/* --- @tvec_dumpreg@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @unsigned disp@ = the register disposition (@TVRD_...@)
+ *             @const union tvec_regval *tv@ = register value, or null
+ *             @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.
+ */
+
 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); }
 
+/* --- @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.
+ */
+
 void tvec_mismatch(struct tvec_state *tv, unsigned f)
 {
   const struct tvec_regdef *rd;
@@ -170,6 +276,20 @@ void tvec_mismatch(struct tvec_state *tv, unsigned f)
 
 /*----- Parsing -----------------------------------------------------------*/
 
+/* --- @tvec_syntax@, @tvec_syntax_v@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @int ch@ = the character found (in @fgetc@ format)
+ *             @const char *expect@, @va_list ap@ = what was expected
+ *
+ * Returns:    %$-1$%.
+ *
+ * Use:                Report a syntax error quoting @ch@ and @expect@.  If @ch@ is
+ *             a newline, then back up so that it can be read again (e.g.,
+ *             by @tvec_flushtoeol@ or @tvec_nexttoken@, which will also
+ *             advance the line number).
+ */
+
 int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
 {
   va_list ap;
@@ -177,6 +297,7 @@ int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
   va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
   return (-1);
 }
+
 int tvec_syntax_v(struct tvec_state *tv, int ch,
                  const char *expect, va_list *ap)
 {
@@ -196,6 +317,31 @@ int tvec_syntax_v(struct tvec_state *tv, int ch,
   dstr_destroy(&d); return (-1);
 }
 
+/* --- @tvec_dupreg@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *name@ = register or pseudoregister name
+ *
+ * Returns:    %$-1$%.
+ *
+ * Use:                Reports an error that the register or pseudoregister has been
+ *             assigned already in the current test.
+ */
+
+int tvec_dupreg(struct tvec_state *tv, const char *name)
+  { return (tvec_error(tv, "register `%s' is already set", name)); }
+
+/* --- @tvec_skipspc@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                Advance over any whitespace characters other than newlines.
+ *             This will stop at `;', end-of-file, or any other kind of
+ *             non-whitespace; and it won't consume a newline.
+ */
+
 void tvec_skipspc(struct tvec_state *tv)
 {
   int ch;
@@ -207,6 +353,24 @@ void tvec_skipspc(struct tvec_state *tv)
   }
 }
 
+/* --- @tvec_flushtoeol@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @unsigned f@ = flags (@TVFF_...@)
+ *
+ * Returns:    Zero on success, @-1@ on error.
+ *
+ * Use:                Advance to the start of the next line, consuming the
+ *             preceding newline.
+ *
+ *             A syntax error is reported if no newline character is found,
+ *             i.e., the file ends in mid-line.  A syntax error is also
+ *             reported if material other than whitespace or a comment is
+ *             found before the end of the line end, and @TVFF_ALLOWANY@ is
+ *             not set in @f@.  The line number count is updated
+ *             appropriately.
+ */
+
 int tvec_flushtoeol(struct tvec_state *tv, unsigned f)
 {
   int ch, rc = 0;
@@ -227,6 +391,29 @@ int tvec_flushtoeol(struct tvec_state *tv, unsigned f)
   }
 }
 
+/* --- @tvec_nexttoken@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero if there is a next token which can be read; @-1@ if no
+ *             token is available.
+ *
+ * Use:                Advance to the next whitespace-separated token, which may be
+ *             on the next line.
+ *
+ *             Tokens are separated by non-newline whitespace, comments, and
+ *             newlines followed by whitespace; a newline /not/ followed by
+ *             whitespace instead begins the next assignment, and two
+ *             newlines separated only by whitespace terminate the data for
+ *             a test.
+ *
+ *             If this function returns zero, then the next character in the
+ *             file begins a suitable token which can be read and
+ *             processed.  If it returns @-1@ then there is no such token,
+ *             and the file position is left correctly.  The line number
+ *             count is updated appropriately.
+ */
+
 int tvec_nexttoken(struct tvec_state *tv)
 {
   enum { TAIL, NEWLINE, INDENT, COMMENT };
@@ -261,6 +448,34 @@ int tvec_nexttoken(struct tvec_state *tv)
   }
 }
 
+/* --- @tvec_readword@, @tvec_readword_v@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @dstr *d@ = string to append the word to
+ *             @const char *delims@ = additional delimiters to stop at
+ *             @const char *expect@, @va_list ap@ = what was expected
+ *
+ * Returns:    Zero on success, @-1@ on failure.
+ *
+ * Use:                A `word' consists of characters other than whitespace, null
+ *             characters, and other than those listed in @delims@;
+ *             furthermore, a word does not begin with a `;'.  (If you want
+ *             reading to stop at comments not preceded by whitespace, then
+ *             include `;' in @delims@.  This is a common behaviour.)
+ *
+ *             If there is no word beginning at the current file position,
+ *             then return @-1@; furthermore, if @expect@ is not null, then
+ *             report an appropriate error via @tvec_syntax@.
+ *
+ *             Otherwise, the word is accumulated in @d@ and zero is
+ *             returned; if @d@ was not empty at the start of the call, the
+ *             newly read word is separated from the existing material by a
+ *             single space character.  Since null bytes are never valid
+ *             word constituents, a null terminator is written to @d@, and
+ *             it is safe to treat the string in @d@ as being null-
+ *             terminated.
+ */
+
 int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
                  const char *expect, ...)
 {
@@ -272,6 +487,7 @@ int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
   va_end(ap);
   return (rc);
 }
+
 int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
                    const char *expect, va_list *ap)
 {
@@ -296,24 +512,21 @@ int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
 /*----- Main machinery ----------------------------------------------------*/
 
 struct groupstate {
-  void *ctx;
+  void *ctx;                           /* test environment context */
 };
 #define GROUPSTATE_INIT { 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;
-  }
-}
+/* --- @tvec_initregs@, @tvec_releaseregs@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize or release, respectively, the registers required
+ *             by the current test.  All of the registers, both input and
+ *             output, are effected.  Initialized registers are not marked
+ *             live.
+ */
 
 void tvec_initregs(struct tvec_state *tv)
 {
@@ -341,6 +554,51 @@ void tvec_releaseregs(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.
+ *             Output registers are marked live if and only if the
+ *             corresponding input register is live.
+ */
+
+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;
+  }
+}
+
+/* --- @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.
+ */
+
 int tvec_checkregs(struct tvec_state *tv)
 {
   const struct tvec_regdef *rd;
@@ -356,17 +614,44 @@ int tvec_checkregs(struct tvec_state *tv)
   return (0);
 }
 
+/* --- @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.
+ */
+
 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)
 {
   if (tvec_checkregs(tv))
     { tvec_fail_v(tv, detail, ap); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
 }
 
+/* --- @open_test@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                Note that we are now collecting data for a new test.  The
+ *             current line number is recorded in @test_lno@.  The
+ *             @TVSF_OPEN@ flag is set, and @TVSF_XFAIL@ is reset.
+ *
+ *             If a test is already open, then do nothing.
+ */
+
 static void open_test(struct tvec_state *tv)
 {
   if (!(tv->f&TVSF_OPEN)) {
@@ -375,12 +660,34 @@ static void open_test(struct tvec_state *tv)
   }
 }
 
+/* --- @begin_test@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                Note that we're about to try running a state.  This is called
+ *             before the test environment's @before@ function.  Mark the
+ *             test as active, clear the outcome, and inform the output
+ *             driver.
+ */
+
 static void begin_test(struct tvec_state *tv)
 {
   tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK;
   tv->output->ops->btest(tv->output);
 }
 
+/* --- @tvec_endtest@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                End an ad-hoc test case,  The statistics are updated and the
+ *             outcome is reported to the output formatter.
+ */
+
 void tvec_endtest(struct tvec_state *tv)
 {
   unsigned out;
@@ -394,6 +701,46 @@ void tvec_endtest(struct tvec_state *tv)
   tv->f &= ~TVSF_OPEN;
 }
 
+/* --- @tvec_xfail@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                Mark the current test as an `expected failure'.  That is, the
+ *             behaviour -- if everything works as expected -- is known to
+ *             be incorrect, perhaps as a result of a longstanding bug, so
+ *             calling it a `success' would be inappropriate.  A failure, as
+ *             reported by @tvec_fail@, means that the behaviour is not as
+ *             expected -- either the long-standing bug has been fixed, or a
+ *             new bug has been introduced -- so investigation is required.
+ *
+ *             An expected failure doesn't cause the test group or the
+ *             session as a whole to fail, but it isn't counted as a win
+ *             either.
+ */
+
+void tvec_xfail(struct tvec_state *tv)
+  { tv->f |= TVSF_XFAIL; }
+
+/* --- @check@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct groupstate *g@ = private test group state
+ *
+ * Returns:    ---
+ *
+ * Use:                Run the current test.
+ *
+ *             This function is called once the data for a test has been
+ *             collected.  It's responsible for checking that all of the
+ *             necessary registers have been assigned values.  It marks the
+ *             output registers as live if the corresponding inputs are
+ *             live.  It calls the environment's @before@, @run@, and
+ *             @after@ functions if provided; if there is no @run@ function,
+ *             it calls @tvec_check@ to verify the output values.
+ */
+
 static void check(struct tvec_state *tv, struct groupstate *g)
 {
   const struct tvec_test *t = tv->test;
@@ -424,14 +771,27 @@ static void check(struct tvec_state *tv, struct groupstate *g)
       t->fn(tv->in, tv->out, g->ctx);
       tvec_check(tv, 0);
     }
-    if (env && env->after) env->after(tv, g->ctx);
     tvec_endtest(tv);
   }
+  if (env && env->after) env->after(tv, g->ctx);
 
 end:
   tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv);
 }
 
+/* --- @begin_test_group@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct groupstate *g@ = private test group state
+ *
+ * Returns:    ---
+ *
+ * Use:                Begins a test group.  Expects @tv->test@ to have been set
+ *             already.  Calls the output driver, initializes the registers,
+ *             clears the @tv->curr@ counters, allocates the environment
+ *             context and calls the environment @setup@ function.
+ */
+
 static void begin_test_group(struct tvec_state *tv, struct groupstate *g)
 {
   const struct tvec_test *t = tv->test;
@@ -439,13 +799,26 @@ static void begin_test_group(struct tvec_state *tv, struct groupstate *g)
   unsigned i;
 
   tv->output->ops->bgroup(tv->output);
-  tv->f &= ~TVSF_SKIP;
+  tv->f &= ~(TVSF_SKIP | TVSF_MUFFLE);
   tvec_initregs(tv);
   for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
   if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz);
   if (env && env->setup) env->setup(tv, env, 0, g->ctx);
 }
 
+/* --- @report_group@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports the result of the test group to the output driver.
+ *
+ *             If all of the tests have been skipped then report this as a
+ *             group skip.  Otherwise, determine and report the group
+ *             outcome.
+ */
+
 static void report_group(struct tvec_state *tv)
 {
   unsigned i, out, nrun;
@@ -462,6 +835,24 @@ static void report_group(struct tvec_state *tv)
   }
 }
 
+/* --- @end_test_group@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct groupstate *g@ = private test group state
+ *
+ * Returns:    ---
+ *
+ * Use:                Handles the end of a test group.  Called at the end of the
+ *             input file or when a new test group header is found.
+ *
+ *             If a test is open, call @check@ to see whether it worked.  If
+ *             the test group is not being skipped, report the group
+ *             result.  Call the test environment @teardown@ function.  Free
+ *             the environment context and release the registers.
+ *
+ *             If there's no test group active, then nothing happens.
+ */
+
 static void end_test_group(struct tvec_state *tv, struct groupstate *g)
 {
   const struct tvec_test *t = tv->test;
@@ -474,6 +865,20 @@ static void end_test_group(struct tvec_state *tv, struct groupstate *g)
   tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
 }
 
+/* --- @tvec_read@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *infile@ = the name of the input file
+ *             @FILE *fp@ = stream to read from
+ *
+ * Returns:    Zero on success, @-1@ on error.
+ *
+ * Use:                Read test vector data from @fp@ and exercise test functions.
+ *             THe return code doesn't indicate test failures: it's only
+ *             concerned with whether there were problems with the input
+ *             file or with actually running the tests.
+ */
+
 enum { WIN, XFAIL, NOUT };
 static const struct tvec_uassoc outcome_assoc[] = {
   { "success",         WIN },
@@ -499,84 +904,162 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
   union tvec_regval rv;
   int ch, ret, rc = 0;
 
+  /* Set the initial location. */
   tv->infile = infile; tv->lno = 1; tv->fp = fp;
 
   for (;;) {
+
+    /* Get the next character and dispatch.  Note that we're always at the
+     * start of a line here.
+     */
     ch = getc(tv->fp);
     switch (ch) {
 
       case EOF:
+       /* End of the file.  Exit the loop. */
+
        goto end;
 
       case '[':
+       /* A test group header. */
+
+       /* End the current group, if there is one. */
        end_test_group(tv, &g);
+
+       /* Read the group name.  There may be leading and trailing
+        * whitespace.
+        */
        tvec_skipspc(tv);
        DRESET(&d); tvec_readword(tv, &d, "];", "group name");
        tvec_skipspc(tv);
        ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
+
+       /* Find the matching test definition. */
        for (test = tv->tests; test->name; test++)
          if (STRCMP(d.buf, ==, test->name)) goto found_test;
-       tvec_error(tv, "unknown test group `%s'", d.buf); goto flush_line;
+
+       /* There wasn't one.  Report the error.  Muffle errors about the
+        * contents of this section because they won't be interesting.
+        */
+       tvec_error(tv, "unknown test group `%s'", d.buf);
+       tv->f |= TVSF_MUFFLE; goto flush_line;
+
       found_test:
-       tvec_flushtoeol(tv, 0); tv->test = test; begin_test_group(tv, &g);
+       /* Eat trailing whitespace and comments. */
+       tvec_flushtoeol(tv, 0);
+
+       /* Set up the new test group. */
+       tv->test = test; begin_test_group(tv, &g);
        break;
 
       case '\n':
+       /* A newline, so this was a completely empty line.  Advance the line
+        * counter, and run the current test.
+        */
+
        tv->lno++;
        if (tv->f&TVSF_OPEN) check(tv, &g);
        break;
 
+      case ';':
+       /* A semicolon.  Skip the comment. */
+
+       tvec_flushtoeol(tv, TVFF_ALLOWANY);
+       break;
+
       default:
+       /* Something else. */
+
        if (isspace(ch)) {
-         tvec_skipspc(tv);
-         ch = getc(tv->fp);
+         /* Whitespace.  Skip and see what we find. */
+
+         tvec_skipspc(tv); ch = getc(tv->fp);
+
+         /* If the file ends, then we're done.  If we find a comment then we
+          * skip it.  If there's some non-whitespace, then report an error.
+          * Otherwise the line was effectively blank, so run the test.
+          */
          if (ch == EOF) goto end;
          else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY);
          else if (tvec_flushtoeol(tv, 0)) rc = -1;
          else check(tv, &g);
-       } else if (ch == ';')
-         tvec_flushtoeol(tv, TVFF_ALLOWANY);
-       else {
+       } else {
+         /* Some non-whitespace thing. */
+
+         /* Put the character back and read a word, which ought to be a
+          * register name.
+          */
          ungetc(ch, tv->fp);
          DRESET(&d);
          if (tvec_readword(tv, &d, "=:;", "register name")) goto flush_line;
+
+         /* Now there should be a separator. */
          tvec_skipspc(tv); ch = getc(tv->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 there's no test, then report an error.  Set the muffle flag,
+          * because there's no point in complaining about every assignment
+          * in this block.
+          */
+         if (!tv->test) {
+           if (!(tv->f&TVSF_MUFFLE)) tvec_error(tv, "no current test");
+           tv->f |= TVSF_MUFFLE; goto flush_line;
+         }
+
+         /* Open the test.  This is syntactically a stanza of settings, so
+          * it's fair to report on missing register assignments.
+          */
+         open_test(tv);
+
          if (d.buf[0] == '@') {
+           /* A special register assignment.  */
            env = tv->test->env;
+
+           /* See if it's one of the core settings. */
            if (STRCMP(d.buf, ==, "@outcome")) {
+
+             /* Parse the value. */
              if (tvty_uenum.parse(&rv, &outcome_regdef, tv))
                ret = -1;
              else {
-               if (rv.u == XFAIL) tv->f |= TVSF_XFAIL;
+
+               /* Act on the result. */
+               if (rv.u == XFAIL) tvec_xfail(tv);
                ret = 1;
              }
-           } else if (!env || !env->set) ret = 0;
-           else ret = env->set(tv, d.buf, env, g.ctx);
+           }
+
+           /* If there's no environment, this is an unknown setting. */
+           else if (!env || !env->set) ret = 0;
+
+           /* Otherwise pass the setting on to the environment. */
+           else ret = env->set(tv, d.buf, g.ctx);
+
+           /* If it wasn't understood, report an error and flush. */
            if (ret <= 0) {
              if (!ret)
                tvec_error(tv, "unknown special register `%s'", d.buf);
              goto flush_line;
            }
-           open_test(tv);
          } else {
+           /* A standard register. */
+
+           /* Find the definition. */
            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'",
                       d.buf, tv->test->name);
            goto flush_line;
+
          found_reg:
-           open_test(tv);
-           tvec_skipspc(tv);
+           /* Complain if the register is already set. */
            r = TVEC_REG(tv, in, rd->i);
-           if (r->f&TVRF_LIVE) {
-             tvec_error(tv, "register `%s' already set", rd->name);
-             goto flush_line;
-           }
+           if (r->f&TVRF_LIVE)
+             { tvec_dupreg(tv, rd->name); goto flush_line; }
+
+           /* Parse a value and mark the register as live. */
            if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
            r->f |= TVRF_LIVE;
          }
@@ -586,12 +1069,24 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
     continue;
 
   flush_line:
+    /* This is a general parse-failure handler.  Skip to the next line and
+     * remember that things didn't go so well.
+     */
     tvec_flushtoeol(tv, TVFF_ALLOWANY); rc = -1;
   }
+
+  /* We reached the end.  If that was actually an I/O error then report it.
+   */
   if (ferror(tv->fp))
     { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; }
+
 end:
+  /* Process the final test, if there was one, and wrap up the final
+   * group.
+   */
   end_test_group(tv, &g);
+
+  /* Clean up. */
   tv->infile = 0; tv->fp = 0;
   dstr_destroy(&d);
   return (rc);
@@ -599,6 +1094,17 @@ end:
 
 /*----- Session lifecycle -------------------------------------------------*/
 
+/* --- @tvec_begin@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv_out@ = state structure to fill in
+ *             @const struct tvec_config *config@ = test configuration
+ *             @struct tvec_output *o@ = output driver
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a state structure ready to do some testing.
+ */
+
 void tvec_begin(struct tvec_state *tv_out,
                const struct tvec_config *config,
                struct tvec_output *o)
@@ -625,10 +1131,22 @@ void tvec_begin(struct tvec_state *tv_out,
   tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out);
 }
 
+/* --- @tvec_end@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    A proposed exit code.
+ *
+ * Use:                Conclude testing and suggests an exit code to be returned to
+ *             the calling program.  (The exit code comes from the output
+ *             driver's @esession@ method.)
+ */
+
 int tvec_end(struct tvec_state *tv)
 {
   int rc = tv->output->ops->esession(tv->output);
 
+  if (tv->test) tvec_releaseregs(tv);
   tv->output->ops->destroy(tv->output);
   xfree(tv->in); xfree(tv->out);
   return (rc);
@@ -636,6 +1154,25 @@ int tvec_end(struct tvec_state *tv)
 
 /*----- Serialization and deserialization ---------------------------------*/
 
+/* --- @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 serialized output is written to the buffer @b@.  Failure
+ *             can be caused by running out of buffer space, or a failing
+ *             type handler.
+ */
+
 int tvec_serialize(const struct tvec_reg *rv, buf *b,
                   const struct tvec_regdef *regs,
                   unsigned nr, size_t regsz)
@@ -660,6 +1197,30 @@ int tvec_serialize(const struct tvec_reg *rv, buf *b,
   return (0);
 }
 
+/* --- @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.
+ *
+ *             Failure results only from a failing register type handler.
+ */
+
 int tvec_deserialize(struct tvec_reg *rv, buf *b,
                     const struct tvec_regdef *regs,
                     unsigned nr, size_t regsz)
@@ -690,12 +1251,42 @@ static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
 static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p)
   { assert(!"fake test function"); }
 
+/* --- @tvec_adhoc@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_test *t@ = space for a test definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Begin ad-hoc testing, i.e., without reading a file of
+ *             test-vector data.
+ *
+ *             The structure at @t@ will be used to record information about
+ *             the tests underway, which would normally come from a static
+ *             test definition.  The other functions in this section assume
+ *             that @tvec_adhoc@ has been called.
+ */
+
 void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
 {
   t->name = "<unset>"; t->regs = &no_regs; t->env = 0; t->fn = fakefn;
   tv->tests = t;
 }
 
+/* --- @tvec_begingroup@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *name@ = name for this test group
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *
+ * Returns:    ---
+ *
+ * Use:                Begin an ad-hoc test group with the given name.  The @file@
+ *             and @lno@ can be anything, but it's usually best if they
+ *             refer to the source code performing the test: the macro
+ *             @TVEC_BEGINGROUP@ does this automatically.
+ */
+
 void tvec_begingroup(struct tvec_state *tv, const char *name,
                     const char *file, unsigned lno)
 {
@@ -706,16 +1297,39 @@ void tvec_begingroup(struct tvec_state *tv, const char *name,
   begin_test_group(tv, 0);
 }
 
+/* --- @tvec_endgroup@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                End an ad-hoc test group.  The statistics are updated and the
+ *             outcome is reported to the output formatter.
+ */
+
 void tvec_endgroup(struct tvec_state *tv)
 {
   if (!(tv->f&TVSF_SKIP)) report_group(tv);
   tv->test = 0;
 }
 
+/* --- @tvec_begintest@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *
+ * Returns:    ---
+ *
+ * Use:                Begin an ad-hoc test case.  The @file@ and @lno@ can be
+ *             anything, but it's usually best if they refer to the source
+ *             code performing the test: the macro @TVEC_BEGINGROUP@ does
+ *             this automatically.
+ */
+
 void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
 {
   tv->infile = file; tv->lno = tv->test_lno = lno;
-  begin_test(tv); tv->f |= TVSF_OPEN;
+  open_test(tv); begin_test(tv);
 }
 
 struct adhoc_claim {
@@ -752,6 +1366,31 @@ static void adhoc_claim_teardown(struct tvec_state *tv,
   if (ck->f&ACF_FRESH) tvec_endtest(tv);
 }
 
+/* --- @tvec_claim@, @tvec_claim_v@, @TVEC_CLAIM@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @int ok@ = a flag
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *             @const char *msg@, @va_list *ap@ = message to report on
+ *                     failure
+ *
+ * Returns:    The value @ok@.
+ *
+ * Use:                Check that a claimed condition holds, as (part of) a test.
+ *             If no test case is underway (i.e., if @TVSF_OPEN@ is reset in
+ *             @tv->f@), then a new test case is begun and ended.  The
+ *             @file@ and @lno@ are passed to the output formatter to be
+ *             reported in case of a failure.  If @ok@ is nonzero, then
+ *             nothing else happens; so, in particular, if @tvec_claim@
+ *             established a new test case, then the test case succeeds.  If
+ *             @ok@ is zero, then a failure is reported, quoting @msg@.
+ *
+ *             The @TVEC_CLAIM@ macro is similar, only it (a) identifies the
+ *             file and line number of the call site automatically, and (b)
+ *             implicitly quotes the source text of the @ok@ condition in
+ *             the failure message.
+ */
+
 int tvec_claim_v(struct tvec_state *tv, int ok,
                 const char *file, unsigned lno,
                 const char *msg, va_list *ap)
index c0573f1fbf11a068b6eaf1a86724c712728c2ec2..5210d11e25b1b63305a3d4c3b359bd8100a9229b 100644 (file)
@@ -151,8 +151,8 @@ struct layout {
    * file.  Return immediately on error.                               \
    */                                                                  \
                                                                        \
-  size_t n = limit - base;                                             \
-  if (fwrite(base, 1, n, lyt->fp) < n) return (-1);                    \
+  size_t _n = limit - base;                                            \
+  if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1);            \
 } while (0)
 
 #define PUT_CHAR(ch) do {                                              \
@@ -170,8 +170,8 @@ struct layout {
 #define PUT_SAVED do {                                                 \
   /* Output the saved trailing blank material in the buffer. */                \
                                                                        \
-  size_t n = lyt->w.len;                                               \
-  if (n && fwrite(lyt->w.buf, 1, n, lyt->fp) < n) return (-1);         \
+  size_t _n = lyt->w.len;                                              \
+  if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1);      \
 } while (0)
 
 #define PUT_PFXINB do {                                                        \
@@ -189,6 +189,31 @@ struct layout {
     DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail);          \
 } while (0)
 
+/* --- @set_layout_prefix@ --- *
+ *
+ * Arguments:  @struct layout *lyt@ = layout state
+ *             @const char *prefix@ = new prefix string or null
+ *
+ * Returns:    ---
+ *
+ * Use:                Change the configured prefix string.  The change takes effect
+ *             at the start of the next line (or the current line if it's
+ *             empty or only whitespace so far).
+ */
+
+static void set_layout_prefix(struct layout *lyt, const char *prefix)
+{
+  const char *q, *l;
+
+ if (!prefix || !*prefix)
+    lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
+  else {
+    lyt->prefix = prefix;
+    l = lyt->pfxlim = prefix + strlen(prefix);
+    SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
+  }
+}
+
 /* --- @init_layout@ --- *
  *
  * Arguments:  @struct layout *lyt@ = layout state to initialize
@@ -202,21 +227,10 @@ struct layout {
 
 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
 {
-  const char *q, *l;
-
-  /* Basics. */
   lyt->fp = fp;
   lyt->f = LYTF_NEWL;
   dstr_create(&lyt->w);
-
-  /* Prefix portions. */
-  if (!prefix || !*prefix)
-    lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
-  else {
-    lyt->prefix = prefix;
-    l = lyt->pfxlim = prefix + strlen(prefix);
-    SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
-  }
+  set_layout_prefix(lyt, prefix);
 }
 
 /* --- @destroy_layout@ --- *
@@ -467,6 +481,9 @@ static const struct tvec_outops ..._ops = {
 #define HA_PLAIN 0                  /* nothing special: terminal defaults */
 #define HA_LOC (HFG(CYAN))             /* filename or line number */
 #define HA_LOCSEP (HFG(BLUE))          /* location separator `:' */
+#define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */
+#define HA_NOTE (HFG(YELLOW))          /* notices */
+#define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */
 #define HA_UNSET (HFG(YELLOW))         /* register not set */
 #define HA_FOUND (HFG(RED))            /* incorrect output value */
 #define HA_EXPECT (HFG(GREEN))         /* what the value should have been */
@@ -474,7 +491,6 @@ static const struct tvec_outops ..._ops = {
 #define HA_LOSE (HFG(RED) | HAF_BOLD)  /* reporting failure */
 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD)        /* reporting expected failure */
 #define HA_SKIP (HFG(YELLOW))          /* reporting a skipped test/group */
-#define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* reporting an error */
 
 /* Scoreboard indicators. */
 #define HSB_WIN '.'                    /* test passed */
@@ -656,60 +672,6 @@ static void show_progress(struct human_output *h)
   }
 }
 
-/* --- @report_location@ --- *
- *
- * Arguments:  @struct human_output *h@ = output state
- *             @FILE *fp@ = stream to write the location on
- *             @const char *file@ = filename
- *             @unsigned lno@ = line number
- *
- * Returns:    ---
- *
- * Use:                Print the filename and line number to the output stream @fp@.
- *             Also, if appropriate, print interleaved highlighting control
- *             codes to our usual output stream.  If @file@ is null then do
- *             nothing.
- */
-
-static void report_location(struct human_output *h, FILE *fp,
-                           const char *file, unsigned lno)
-{
-  unsigned f = 0;
-#define f_flush 1u
-
-  /* We emit highlighting if @fp@ is our usual output stream, or the
-   * duplicate-errors flag is clear indicating that (we assume) they're
-   * secretly going to the same place anyway.  If they're different streams,
-   * though, we have to be careful to keep the highlighting and the actual
-   * text synchronized.
-   */
-
-  if (!file)
-    /* nothing to do */;
-  else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
-    fprintf(fp, "%s:%u: ", file, lno);
-  else {
-    if (fp != h->lyt.fp) f |= f_flush;
-
-#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
-
-    setattr(h, HA_LOC);                FLUSH(h->lyt.fp);
-    fputs(file, fp);           FLUSH(fp);
-    setattr(h, HA_LOCSEP);     FLUSH(h->lyt.fp);
-    fputc(':', fp);            FLUSH(fp);
-    setattr(h, HA_LOC);                FLUSH(h->lyt.fp);
-    fprintf(fp, "%u", lno);    FLUSH(fp);
-    setattr(h, HA_LOCSEP);     FLUSH(h->lyt.fp);
-    fputc(':', fp);            FLUSH(fp);
-    setattr(h, HA_PLAIN);      FLUSH(h->lyt.fp);
-    fputc(' ', fp);
-
-#undef FLUSH
-  }
-
-#undef f_flush
-}
-
 /* --- @human_writech@, @human_write@, @human_writef@ --- *
  *
  * Arguments:  @void *go@ = output sink, secretly a @struct human_output@
@@ -809,7 +771,7 @@ static void report_unusual(struct human_output *h,
  * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
  *                     human_output@
  *
- * Returns:    Suggested exit status.
+ * Returns:    Suggested exit code.
  *
  * Use:                End a test session.
  *
@@ -960,6 +922,60 @@ static void human_egroup(struct tvec_output *o)
 static void human_btest(struct tvec_output *o)
   { struct human_output *h = (struct human_output *)o; show_progress(h); }
 
+/* --- @report_location@ --- *
+ *
+ * Arguments:  @struct human_output *h@ = output state
+ *             @FILE *fp@ = stream to write the location on
+ *             @const char *file@ = filename
+ *             @unsigned lno@ = line number
+ *
+ * Returns:    ---
+ *
+ * Use:                Print the filename and line number to the output stream @fp@.
+ *             Also, if appropriate, print interleaved highlighting control
+ *             codes to our usual output stream.  If @file@ is null then do
+ *             nothing.
+ */
+
+static void report_location(struct human_output *h, FILE *fp,
+                           const char *file, unsigned lno)
+{
+  unsigned f = 0;
+#define f_flush 1u
+
+  /* We emit highlighting if @fp@ is our usual output stream, or the
+   * duplicate-errors flag is clear indicating that (we assume) they're
+   * secretly going to the same place anyway.  If they're different streams,
+   * though, we have to be careful to keep the highlighting and the actual
+   * text synchronized.
+   */
+
+  if (!file)
+    /* nothing to do */;
+  else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
+    fprintf(fp, "%s:%u: ", file, lno);
+  else {
+    if (fp != h->lyt.fp) f |= f_flush;
+
+#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
+
+    setattr(h, HA_LOC);                FLUSH(h->lyt.fp);
+    fputs(file, fp);           FLUSH(fp);
+    setattr(h, HA_LOCSEP);     FLUSH(h->lyt.fp);
+    fputc(':', fp);            FLUSH(fp);
+    setattr(h, HA_LOC);                FLUSH(h->lyt.fp);
+    fprintf(fp, "%u", lno);    FLUSH(fp);
+    setattr(h, HA_LOCSEP);     FLUSH(h->lyt.fp);
+    fputc(':', fp);            FLUSH(fp);
+    setattr(h, HA_PLAIN);      FLUSH(h->lyt.fp);
+    fputc(' ', fp);
+
+#undef FLUSH
+  }
+
+#undef f_flush
+}
+
 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
  *
  * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
@@ -1116,7 +1132,7 @@ static void human_ebench(struct tvec_output *o,
 {
   struct human_output *h = (struct human_output *)o;
 
-  tvec_benchreport(&human_printops, h->lyt.fp, unit, tm);
+  tvec_benchreport(&human_printops, h, unit, tm);
   fputc('\n', h->lyt.fp);
 }
 
@@ -1142,20 +1158,43 @@ static void human_report(struct tvec_output *o, unsigned level,
 {
   struct human_output *h = (struct human_output *)o;
   struct tvec_state *tv = h->tv;
+  const char *levstr; unsigned levattr;
   dstr d = DSTR_INIT;
+  unsigned f = 0;
+#define f_flush 1u
+#define f_progress 2u
 
   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
 
-  clear_progress(h); fflush(h->lyt.fp);
+  switch (level) {
+#define CASE(tag, name, val)                                           \
+    case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
+    TVEC_LEVELS(CASE)
+    default: levstr = "??"; levattr = HA_UNKLEV; break;
+  }
+
+  if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush;
+
+#define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0)
+
+  if (h->f^HOF_PROGRESS)
+    { clear_progress(h); fflush(h->lyt.fp); f |= f_progress; }
   fprintf(stderr, "%s: ", QUIS);
   report_location(h, stderr, tv->infile, tv->lno);
-  fwrite(d.buf, 1, d.len, stderr);
+  setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH;
+  fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr);
+
+#undef FLUSH
 
   if (h->f&HOF_DUPERR) {
     report_location(h, h->lyt.fp, tv->infile, tv->lno);
+    fprintf(h->lyt.fp, "%s: ", levstr);
     fwrite(d.buf, 1, d.len, h->lyt.fp);
   }
-  show_progress(h);
+  if (f&f_progress) show_progress(h);
+
+#undef f_flush
+#undef f_progress
 }
 
 /* --- @human_destroy@ --- *
@@ -1314,12 +1353,14 @@ static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
  * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
  *                     tap_output@
  *
- * Returns:    Suggested exit status.
+ * Returns:    Suggested exit code.
  *
  * Use:                End a test session.
  *
  *             The TAP driver prints a final summary of the rest results
- *             and returns a suitable exit code.
+ *             and returns a suitable exit code.  If errors occurred, it
+ *             instead prints a `Bail out!' line forcing the reader to
+ *             report a failure.
  */
 
 static int tap_esession(struct tvec_output *o)
@@ -1496,6 +1537,7 @@ static void tap_dumpreg(struct tvec_output *o,
   struct tap_output *t = (struct tap_output *)o;
   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
 
+  set_layout_prefix(&t->lyt, "    ## ");
   gprintf(&tap_printops, t, "%*s%s %s = ",
          10 + t->maxlen - n, "", ds, rd->name);
   if (!rv) gprintf(&tap_printops, t, "#<unset>");
@@ -1572,6 +1614,7 @@ static void tap_ebench(struct tvec_output *o,
   struct tap_output *t = (struct tap_output *)o;
   struct tvec_state *tv = t->tv;
 
+  set_layout_prefix(&t->lyt, "    ## ");
   gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
   tvec_benchreport(&tap_printops, t, unit, tm);
   layout_char(&t->lyt, '\n');
@@ -1589,8 +1632,11 @@ static void tap_ebench(struct tvec_output *o,
  *
  * Use:                Report a message to the user.
  *
- *             The TAP driver converts error reports into TAP `Bail out!'
- *             errors.  Less critical notices end up as comments.
+ *             Messages are reported as comments, so that they can be
+ *             accumulated by the reader.  An error will cause a later
+ *             bailout or, if we crash before then, a missing plan line,
+ *             either of which will cause the reader to report a serious
+ *             problem.
  */
 
 static void tap_report(struct tvec_output *o, unsigned level,
@@ -1598,16 +1644,14 @@ static void tap_report(struct tvec_output *o, unsigned level,
 {
   struct tap_output *t = (struct tap_output *)o;
   struct tvec_state *tv = t->tv;
-  const struct gprintf_ops *gops; void *go;
 
-  if (level >= TVLEV_ERR) {
-    fputs("Bail out!  ", t->lyt.fp);
-    gops = &file_printops; go = t->lyt.fp;
-  } else {
-    gops = &tap_printops; go = t;
-  }
-  if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
-  gprintf(gops, go, msg, ap); gops->putch(go, '\n');
+  if (tv->test) set_layout_prefix(&t->lyt, "    ## ");
+  else set_layout_prefix(&t->lyt, "## ");
+
+  if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
+  gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
+  vgprintf(&tap_printops, t, msg, ap);
+  layout_char(&t->lyt, '\n');
 }
 
 /* --- @tap_destroy@ --- *
@@ -1665,7 +1709,7 @@ struct tvec_output *tvec_tapoutput(FILE *fp)
   struct tap_output *t;
 
   t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
-  init_layout(&t->lyt, fp, "    ## ");
+  init_layout(&t->lyt, fp, 0);
   t->outbuf = 0; t->outsz = 0;
   return (&t->_o);
 }
index 3dbb5d37fdea76e1276e0118defef9de3466faa1..62dd1509d965a48657124c282a3160d7d15e12ad 100644 (file)
 #include "quis.h"
 #include "tvec.h"
 
+/*----- Preliminaries -----------------------------------------------------*/
+
+/* The control macros I'm using below provoke `dangling-else' warnings from
+ * compilers.  Suppress them.  I generally don't care.
+ */
+
 #if GCC_VERSION_P(7, 1)
 #  pragma GCC diagnostic ignored "-Wdangling-else"
 #elif GCC_VERSION_P(4, 2)
 
 /*----- Basic I/O ---------------------------------------------------------*/
 
+/* --- @init_comms@ --- *
+ *
+ * Arguments:  @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a communication state.  This doesn't allocate any
+ *             resurces: it just ensures that everything is set up so that
+ *             subsequent operations -- in particular @release_comms@ --
+ *             behave sensibly.
+ */
+
 static void init_comms(struct tvec_remotecomms *rc)
 {
-  dbuf_create(&rc->bin); dbuf_create(&rc->bout);
+  rc->bin = 0; rc->binsz = 0; dbuf_create(&rc->bout);
   rc->infd = rc->outfd = -1; rc->f = 0;
 }
 
+/* --- @close_comms@ --- *
+ *
+ * Arguments:  @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns:    ---
+ *
+ * Use:                Close the input and output descriptors.
+ *
+ *             If the descriptors are already closed -- or were never opened
+ *             -- then nothing happens.
+ */
+
 static void close_comms(struct tvec_remotecomms *rc)
 {
   if (rc->infd >= 0) { close(rc->infd); rc->infd = -1; }
   if (rc->outfd >= 0) { close(rc->outfd); rc->outfd = -1; }
 }
 
+/* --- @release_comms@ --- *
+ *
+ * Arguments:  @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases the resources -- most notably the input and output
+ *             buffers -- held by the communication state.  Also calls
+ *             @close_comms@.
+ */
+
 static void release_comms(struct tvec_remotecomms *rc)
-  { close_comms(rc); dbuf_destroy(&rc->bin); dbuf_destroy(&rc->bout); }
+  { close_comms(rc); xfree(rc->bin); dbuf_destroy(&rc->bout); }
+
+/* --- @setup_comms@ --- *
+ *
+ * Arguments:  @struct tvec_remotecomms *rc@ = communication state
+ *             @int infd, outfd@ = input and output file descriptors
+ *
+ * Returns:    ---
+ *
+ * Use:                Use the given descriptors for communication.
+ *
+ *             Clears the private flags.
+ */
 
 static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd)
 {
-  rc->infd = infd; rc->outfd = outfd; rc->f &= ~0xffu;
-  dbuf_reset(&rc->bin); dbuf_reset(&rc->bout);
+  rc->infd = infd; rc->outfd = outfd;
+  rc->binoff = rc->binlen = 0;
+  rc->f &= ~0xffu;
 }
 
-static int PRINTF_LIKE(3, 4)
-  ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
-       const char *msg, ...)
+/* --- @ioerr@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotecomms *rc@ = communication state
+ *             @const char *msg, ...@ = format string and arguments
+ *
+ * Returns:    %$-1$%.
+ *
+ * Use:                Reports the message as an error, closes communications and
+ *             marks them as broken.
+ */
+
+static PRINTF_LIKE(3, 4)
+  int ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
+           const char *msg, ...)
 {
   va_list ap;
 
@@ -95,18 +161,24 @@ static int PRINTF_LIKE(3, 4)
   return (-1);
 }
 
+/* --- @send_all@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotecomms *rc@ = communication state
+ *             @const unsigned char *p@, @size_t sz@ = output buffer
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Send the output buffer over the communication state's output
+ *             descriptor, even if it has to be written in multiple pieces.
+ */
+
 static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
                    const unsigned char *p, size_t sz)
 {
-  void (*opipe)(int) = SIG_ERR;
   ssize_t n;
   int ret;
 
-  opipe = signal(SIGPIPE, SIG_IGN);
-    if (opipe == SIG_ERR) {
-      ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
-      goto end;
-    }
   while (sz) {
     n = write(rc->outfd, p, sz);
     if (n > 0)
@@ -119,131 +191,384 @@ static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
   }
   ret = 0;
 end:
-  if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
   return (ret);
 }
 
+/* --- @recv_all@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotecomms *rc@ = communication state
+ *             @unsigned f@ = flags (@RCVF_...@)
+ *             @unsigned char *p@, @size_t sz@ = input buffer
+ *             @size_t min@ = minimum acceptable size to read
+ *             @size_t *n_out@ = size read
+ *
+ * Returns:    An @RECV_...@ code.
+ *
+ * Use:                Receive data on the communication state's input descriptor to
+ *             read at least @min@ bytes into the input buffer, even if it
+ *             has to be done in multiple pieces.  If more data is readily
+ *             available, then up to @sz@ bytes will be read in total.
+ *
+ *             If the descriptor immediately reports end-of-file, and
+ *             @RCVF_ALLOWEOF@ is set in @f@, then return @RECV_EOF@.
+ *             Otherwise, EOF is treated as an I/O error, resulting in a
+ *             call to @ioerr@ and a return code of @RECV_FAIL@.  If the
+ *             read succeeded, then set @*n_out@ to the number of bytes read
+ *             and return @RECV_OK@.
+ */
+
 #define RCVF_ALLOWEOF 1u
+
 enum {
   RECV_FAIL = -1,
   RECV_OK = 0,
   RECV_EOF = 1
 };
+
 static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
-                   unsigned char *p, size_t sz, unsigned f)
+                   unsigned f, unsigned char *p, size_t sz,
+                   size_t min, size_t *n_out)
 {
+  size_t tot = 0;
   ssize_t n;
-  unsigned ff = 0;
-#define f_any 1u
 
   while (sz) {
     n = read(rc->infd, p, sz);
-    if (n > 0)
-      { p += n; sz -= n; ff |= f_any; }
-    else if (!n && (f&RCVF_ALLOWEOF) && !(ff&f_any))
+    if (n > 0) {
+      p += n; sz -= n; tot += n;
+      if (tot >= min) break;
+    } else if (!n && !tot && (f&RCVF_ALLOWEOF))
       return (RECV_EOF);
     else
       return (ioerr(tv, rc, "failed to receive: %s",
                    n ? strerror(errno) : "unexpected end-of-file"));
   }
-  return (RECV_OK);
+  *n_out = tot; return (RECV_OK);
 
 #undef f_any
 }
 
+/* --- @buferr@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns:    %$-$%.
+ *
+ * Use:                Report a problem preparing the output buffer.
+ */
+
+static int buferr(struct tvec_state *tv, struct tvec_remotecomms *rc)
+  { return (ioerr(tv, rc, "failed to build output packet")); }
+
+/* --- @malformed@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns:    %$-$%.
+ *
+ * Use:                Report an I/O error that the incoming packet is malformed.
+ */
+
+static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
+  { return (ioerr(tv, rc, "received malformed packet")); }
+
+/* --- @remote_send@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Send the accuulated contents of the output buffer @rc->bout@.
+ *
+ *             The function arranges to convert @SIGPIPE@ into an error.
+ *
+ *             If the output buffer is broken, report this as an I/O error.
+ */
+
+#define SENDBUFSZ 4096
+
 static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc)
 {
-  kludge64 k; unsigned char lenbuf[8];
-  const unsigned char *p; size_t sz;
+  void (*opipe)(int) = SIG_ERR;
+  int ret;
+
+  /* Various preflight checks. */
+  if (rc->f&TVRF_BROKEN) { ret = -1; goto end; }
+  if (DBBAD(&rc->bout)) { ret = buferr(tv, rc); goto end; }
 
-  if (rc->f&TVRF_BROKEN) return (-1);
-  if (BBAD(&rc->bout._b))
-    return (ioerr(tv, rc, "failed to build output packet buffer"));
+  /* Arrange to trap broken-pipe errors. */
+  opipe = signal(SIGPIPE, SIG_IGN);
+    if (opipe == SIG_ERR) {
+      ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
+      goto end;
+    }
 
-  p = BBASE(&rc->bout._b); sz = BLEN(&rc->bout._b);
-  ASSIGN64(k, sz); STORE64_L_(lenbuf, k);
-  if (send_all(tv, rc, lenbuf, sizeof(lenbuf))) return (-1);
-  if (send_all(tv, rc, p, sz)) return (-1);
+  /* Transmit the packet. */
+  if (send_all(tv, rc, DBBASE(&rc->bout), DBLEN(&rc->bout)))
+    { ret = -1; goto end; }
 
-  return (0);
+  /* Done.  Put things back the way we found them. */
+  ret = 0;
+end:
+  DBRESET(&rc->bout);
+  if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
+  return (ret);
 }
 
+/* --- @receive_buffered@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotecomms *rc@ = communication state
+ *             @unsigned f@ = flags (@RCVF_...@)
+ *             @size_t want@ = data block size required
+ *
+ * Returns:    An @RECV_...@ code.
+ *
+ * Use:                Reads a block of data from the input descriptor into the
+ *             input buffer.
+ *
+ *             This is the main machinery for manipulating the input buffer.
+ *             The buffer has three regions:
+ *
+ *               * from the buffer start to @rc->binoff@ is `consumed';
+ *               * from @rc->binoff@ to @rc->binlen@ is `available'; and
+ *               * from @rc->binlen@ to @rc->binsz@ is `free'.
+ *
+ *             Data is read into the start of the `free' region, and the
+ *             `available' region is extended to include it.  Data in the
+ *             `consumed' region is periodically discarded by moving the
+ *             data from the `available' region to the start of the buffer
+ *             and decreasing @rc->binoff@ and @rc->binlen@.
+ *
+ *             This function ensures that the `available' region contains at
+ *             least @want@ bytes, by (a) extending the buffer, if
+ *             necessary, so that @rc->binsz >= rc->binoff + want@, and (b)
+ *             reading fresh data from the input descriptor to extend the
+ *             `available' region.
+ *
+ *             If absolutely no data is available, and @RCVF_ALLOWEOF@ is
+ *             set in @f@, then return @RECV_EOF@.  On I/O errors, including
+ *             a short read or end-of-file if @RCVF_ALLOWEOF@ is clear,
+ *             return @RECV_FAIL@.  On success, return @RECV_OK@.  The
+ *             amount of data read is indicated by updating the input buffer
+ *             variables as described above.
+ */
+
+#define RECVBUFSZ 4096u
+
+static int receive_buffered(struct tvec_state *tv,
+                           struct tvec_remotecomms *rc,
+                           unsigned f, size_t want)
+{
+  size_t sz;
+  int ret;
+
+  /* If we can supply the caller's requirement from the buffer then do
+   * that.
+   */
+  if (rc->binlen - rc->binoff >= want) return (RECV_OK);
+
+  /* If the buffer is too small then we must grow it. */
+  if (want > rc->binsz) {
+    sz = rc->binsz; if (!sz) sz = RECVBUFSZ;
+    while (sz < want) { assert(sz < (size_t)-1/2); sz *= 2; }
+    if (!rc->bin) rc->bin = xmalloc(sz);
+    else rc->bin = xrealloc(rc->bin, sz, rc->binsz);
+    rc->binsz = sz;
+  }
+
+  /* Shunt the unused existing material to the start of the buffer. */
+  memmove(rc->bin, rc->bin + rc->binoff, rc->binlen - rc->binoff);
+  rc->binlen -= rc->binoff; rc->binoff = 0;
+
+  /* Satisfy the caller from the input stream, and try to fill up as much of
+   * the rest of the buffer as we can.
+   */
+  ret = recv_all(tv, rc, rc->binlen ? 0 : f,
+                rc->bin + rc->binlen, rc->binsz - rc->binlen,
+                want - rc->binlen, &sz);
+    if (ret) return (ret);
+
+  /* Note how much material we have and return. */
+  rc->binlen += sz; return (RECV_OK);
+}
+
+/* --- @remote_recv@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @unsigned f@ = flags (@RCVF_...@)
+ *             @buf *b_out@ = buffer to establish around the packet contents
+ *
+ * Returns:    An @RECV_...@ code.
+ *
+ * Use:                Receive a packet into the input buffer @rc->bin@ and
+ *             establish @*b_out@ to read from it.
+ */
+
 static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc,
                       unsigned f, buf *b_out)
 {
-  kludge64 k, szmax; unsigned char lenbuf[8];
-  unsigned char *p;
-  size_t sz;
+  kludge64 k, szmax;
+  size_t want;
   int ret;
 
-  if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
   ASSIGN64(szmax, (size_t)-1);
-  ret = recv_all(tv, rc, lenbuf, sizeof(lenbuf), f);
-    if (ret) return (ret);
-  LOAD64_L_(k, lenbuf);
+
+  /* Preflight checks. */
+  if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
+
+  /* See if we can read the next packet length from what we already have. */
+  ret = receive_buffered(tv, rc, f, 8); if (ret) return (ret);
+  LOAD64_L_(k, rc->bin + rc->binoff); rc->binoff += 8;
   if (CMP64(k, >, szmax))
     return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range",
                  (unsigned long)HI64(k), (unsigned long)LO64(k)));
+  want = GET64(size_t, k);
 
-  sz = GET64(size_t, k); dbuf_reset(&rc->bin); p = buf_get(&rc->bin._b, sz);
-    if (!p) return (ioerr(tv, rc, "failed to allocate receive buffer"));
-  if (recv_all(tv, rc, p, sz, 0)) return (RECV_FAIL);
-  buf_init(b_out, p, sz); return (RECV_OK);
+  /* Read the next packet payload. */
+  ret = receive_buffered(tv, rc, 0, want); if (ret) return (ret);
+  buf_init(b_out, rc->bin + rc->binoff, want); rc->binoff += want;
+  return (RECV_OK);
 }
 
-#define SENDPK(tv, rc, pk)                                             \
-       if ((rc)->f&TVRF_BROKEN) MC_GOELSE(body); else                  \
-       MC_BEFORE(setpk,                                                \
-         { dbuf_reset(&(rc)->bout);                                    \
-           buf_putu16l(&(rc)->bout._b, (pk)); })                       \
-       MC_ALLOWELSE(body)                                              \
-       MC_AFTER(send,                                                  \
-         { if (remote_send(tv, rc)) MC_GOELSE(body); })                \
+/* --- @QUEUEPK_TAG@, @QUEUEPK@ --- *
+ *
+ * Arguments:  @tag@ = control structure tag
+ *             @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotecomms *rc@ = communication state
+ *             @unsigned fl@ = flags (@QF_...@)
+ *             @unsigned pk@ = packet type
+ *
+ * Use:                This is syntactically a statement head: the syntax is
+ *             @QUEUEPK(tv, rc, f) body [else alt]@.  The @body@ should
+ *             write material to the output buffer @rc->bout@.  The macro
+ *             applies appropriate framing.  If enough material has been
+ *             collected, or if @QF_FORCE@ is set in @fl@, then
+ *             @remote_send@ is invoked to transmit the buffered packets.
+ *             If there is an error of any kind, then the @alt@ statement,
+ *             if any, is executed.
+ */
 
-static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
-  { return (ioerr(tv, rc, "received malformed packet")); }
+#define QF_FORCE 1u
+#define QUEUEPK_TAG(tag, tv, rc, fl, pk)                               \
+       if ((rc)->f&TVRF_BROKEN) MC_GOELSE(tag##__else); else           \
+       MC_ALLOWELSE(tag##__else)                                       \
+       MC_AFTER(tag##__send, {                                         \
+         if ((DBBAD(&(rc)->bout) && (buferr((tv), (rc)), 1)) ||        \
+             ((((fl)&QF_FORCE) || DBLEN(&(rc)->bout) >= SENDBUFSZ) &&  \
+              remote_send(tv, rc)))                                    \
+           MC_GOELSE(tag##__else);                                     \
+       })                                                              \
+       DBUF_ENCLOSEITAG(tag##__frame, &(rc)->bout, (rc)->t, 64_L)      \
+       MC_BEFORE(tag##__pkty, {                                        \
+         dbuf_putu16l(&(rc)->bout, (pk));                              \
+       })
+
+#define QUEUEPK(tv, rc, fl, pk) QUEUEPK_TAG(queue, (tv), (rc), (fl), (pk))
 
 /*----- Packet types ------------------------------------------------------*/
 
 #define TVPF_ACK       0x0001u
 
-#define TVPK_VER       0x0000u         /* --> min, max: u16 */
-                                       /* <-- ver: u16 */
+#define TVPK_VER       0x0000u         /* --> min, max: u16 *
+                                        * <-- ver: u16 */
+#define TVPK_BGROUP    0x0002u         /* --> name: str16
+                                        * <-- --- */
+#define TVPK_TEST      0x0004u         /* --> in: regs
+                                        * <-- --- */
+#define TVPK_EGROUP    0x0006u         /* --> --- *
+                                        * <-- --- */
 
 #define TVPK_REPORT    0x0100u         /* <-- level: u16; msg: string */
 #define TVPK_PROGRESS  0x0102u         /* <-- st: str16 */
 
-#define TVPK_BGROUP    0x0200u         /* --> name: str16
-                                        * <-- --- */
-#define TVPK_TEST      0x0202u         /* --> in: regs
-                                        * <-- --- */
-#define TVPK_EGROUP    0x0204u         /* --> --- */
-
-#define TVPK_SKIPGRP   0x0300u         /* <-- excuse: str16 */
-#define TVPK_SKIP      0x0302u         /* <-- excuse: str16 */
-#define TVPK_FAIL      0x0304u         /* <-- flag: u8, detail: str16 */
-#define TVPK_DUMPREG   0x0306u         /* <-- ri: u16; disp: u16;
+#define TVPK_SKIPGRP   0x0104u         /* <-- excuse: str16 */
+#define TVPK_SKIP      0x0106u         /* <-- excuse: str16 */
+#define TVPK_FAIL      0x0108u         /* <-- flag: u8, detail: str16 */
+#define TVPK_DUMPREG   0x010au         /* <-- ri: u16; disp: u16;
                                         *     flag: u8, rv: value */
-#define TVPK_BBENCH    0x0308u         /* <-- ident: str32; unit: u16 */
-#define TVPK_EBENCH    0x030au         /* <-- ident: str32; unit: u16;
+#define TVPK_BBENCH    0x010cu         /* <-- ident: str32; unit: u16 */
+#define TVPK_EBENCH    0x010eu         /* <-- ident: str32; unit: u16;
                                         *     flags: u16; n, t, cy: f64 */
 
 /*----- Server ------------------------------------------------------------*/
 
+/* Forward declaration of output operations. */
 static const struct tvec_outops remote_ops;
 
-static struct tvec_state srvtv;
-static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT;
-static struct tvec_output srvout = { &remote_ops };
+static struct tvec_state srvtv;                /* server's test-vector state */
+static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT; /* comms */
+static struct tvec_output srvout = { &remote_ops }; /* output state */
 
-int tvec_setprogress(const char *status)
+/* --- @tvec_setprogress@, @tvec_setprogress_v@ --- *
+ *
+ * Arguments:  @const char *status@ = progress status token format
+ *             @va_list ap@ = argument tail
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports the progress of a test execution to the client.
+ *
+ *             The framework makes use of tokens beginning with %|%|%:
+ *
+ *               * %|%IDLE|%: during the top-level server code;
+ *
+ *               * %|%SETUP|%: during the enclosing environment's @before@
+ *                 function;
+ *
+ *               * %|%RUN|%: during the environment's @run@ function, or the
+ *                 test function; and
+ *
+ *               * %|%DONE|%: during the enclosing environment's @after@
+ *                 function.
+ *
+ *             The intent is that a test can use the progress token to check
+ *             that a function which is expected to crash does so at the
+ *             correct point, so it's expected that more complex test
+ *             functions and/or environments will set their own progress
+ *             tokens to reflect what's going on.
+ */
+
+int tvec_setprogress(const char *status, ...)
+{
+  va_list ap;
+  int rc;
+
+  va_start(ap, status); rc = tvec_setprogress_v(status, &ap); va_end(ap);
+  return (rc);
+}
+
+int tvec_setprogress_v(const char *status, va_list *ap)
 {
-  SENDPK(&srvtv, &srvrc, TVPK_PROGRESS)
-    buf_putstr16l(&srvrc.bout._b, status);
+  /* Force immediate output in case we crash before the buffer is output
+   * organically.
+   */
+  QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_PROGRESS)
+    dbuf_vputstrf16l(&srvrc.bout, status, ap);
   else return (-1);
   return (0);
 }
 
+/* --- @tvec_remoteserver@ --- *
+ *
+ * Arguments:  @int infd@, @int outfd@ = input and output file descriptors
+ *             @const struct tvec_config *config@ = test configuration
+ *
+ * Returns:    Suggested exit code.
+ *
+ * Use:                Run a test server, reading packets from @infd@ and writing
+ *             responses and notifications to @outfd@, and invoking tests as
+ *             described by @config@.
+ *
+ *             This function is not particularly general purpose.  It
+ *             expects to `take over' the process, and makes use of private
+ *             global variables.
+ */
+
 int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
 {
   uint16 pk, u, v;
@@ -252,14 +577,18 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
   const struct tvec_test *t;
   void *p; size_t sz;
   const struct tvec_env *env = 0;
-  unsigned f = 0;
-#define f_regslive 1u
   void *ctx = 0;
   int rc;
 
+  /* Initialize the communication machinery. */
   setup_comms(&srvrc, infd, outfd);
+
+  /* Begin a test session using our custom output driver. */
   tvec_begin(&srvtv, config, &srvout);
 
+  /* Version negotiation.  Expect a @TVPK_VER@ packet.  At the moment,
+   * there's only version zero, so we return that.
+   */
   if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
   if (buf_getu16l(&b, &pk)) goto bad;
   if (pk != TVPK_VER) {
@@ -269,12 +598,33 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
     goto end;
   }
   if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad;
-  SENDPK(&srvtv, &srvrc, TVPK_VER | TVPF_ACK) buf_putu16l(&srvrc.bout._b, 0);
+  QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_VER | TVPF_ACK)
+    dbuf_putu16l(&srvrc.bout, 0);
   else { rc = -1; goto end; }
 
-  tvec_setprogress("%IDLE");
-
+  /* Handle packets until the server closes the connection.
+   *
+   * The protocol looks much simpler from our point of view than from the
+   * client.
+   *
+   *   * Receive @TVPK_VER@; respond with @TVPK_VER | TVPF_ACK@.
+   *
+   *   * Receive zero or more @TVPK_BGROUP@.  Open a test group, producing
+   *    output packets, and eventually answer with @TVPK_BGROUP | TVPF_ACK@.
+   *
+   *      -- Receive zero or more @TVPK_TEST@.  Run a test, producing output
+   *         packets, and eventually answer with @TVPK_TEST | TVPF_ACK@.
+   *
+   *      -- Receive @TVPK_EGROUP@.  Maybe produce output packets, and
+   *         answer with @TVPK_EGROUP | TVPF_ACK@.
+   *
+   *  * Read EOF.  Stop.
+   */
   for (;;) {
+
+    /* Read a packet.  End-of-file is expected here (and pretty much nowhere
+     * else).   Otherwise, we expect to see @TVPK_BGROUP@.
+     */
     rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b);
       if (rc == RECV_EOF) break;
       else if (rc == RECV_FAIL) goto end;
@@ -283,8 +633,13 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
     switch (pk) {
 
       case TVPK_BGROUP:
+       /* Start a group. */
+
+       /* Parse the packet payload. */
        p = buf_getmem16l(&b, &sz); if (!p) goto bad;
        if (BLEFT(&b)) goto bad;
+
+       /* Find the group given its name. */
        for (t = srvtv.tests; t->name; t++)
          if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz))
            goto found_group;
@@ -293,6 +648,7 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
        goto end;
 
       found_group:
+       /* Set up the test environment. */
        srvtv.test = t; env = t->env;
        if (env && env->setup == tvec_remotesetup)
          env = ((struct tvec_remoteenv *)env)->r.env;
@@ -300,36 +656,64 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
        else ctx = xmalloc(env->ctxsz);
        if (env && env->setup) env->setup(&srvtv, env, 0, ctx);
 
-       SENDPK(&srvtv, &srvrc, TVPK_BGROUP | TVPF_ACK);
+       /* Initialize the registers. */
+       tvec_initregs(&srvtv);
+
+       /* Report that the group has been opened and that we're ready to run
+        * tests.
+        */
+       QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_BGROUP | TVPF_ACK);
        else { rc = -1; goto end; }
 
+       /* Handle packets until we're told to end the group. */
        for (;;) {
+
+         /* Read a packet.  We expect @TVPK_EGROUP@ or @TVPK_TEST@. */
          if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
          if (buf_getu16l(&b, &pk)) goto bad;
+
          switch (pk) {
 
            case TVPK_EGROUP:
+             /* End the group. */
+
+             /* Check the payload. */
              if (BLEFT(&b)) goto bad;
+
+             /* Leave the group loop. */
              goto endgroup;
 
            case TVPK_TEST:
-             tvec_initregs(&srvtv); f |= f_regslive;
+             /* Run a test. */
+
+             /* Parse the packet payload. */
              if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs,
                                   srvtv.nreg, srvtv.regsz))
                goto bad;
              if (BLEFT(&b)) goto bad;
 
+             /* If we're not skipping the test group, then actually try to
+              * run the test.
+              */
              if (!(srvtv.f&TVSF_SKIP)) {
+
+               /* Prepare the output registers and reset the test outcome.
+                * (The environment may force a skip.)
+                */
+               for (i = 0; i < srvtv.nrout; i++)
+                 if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
+                   TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
                srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK;
-               tvec_setprogress("%SETUP");
+
+               /* Invoke the environment @before@ function. */
+               tvec_setprogress("%%SETUP");
                if (env && env->before) env->before(&srvtv, ctx);
+
+               /* Run the actual test. */
                if (!(srvtv.f&TVSF_ACTIVE))
                  /* setup forced a skip */;
                else {
-                 for (i = 0; i < srvtv.nrout; i++)
-                   if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
-                     TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
-                 tvec_setprogress("%RUN");
+                 tvec_setprogress("%%RUN");
                  if (env && env->run)
                    env->run(&srvtv, t->fn, ctx);
                  else {
@@ -337,46 +721,61 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
                    tvec_check(&srvtv, 0);
                  }
                }
-               tvec_setprogress("%DONE");
+
+               /* Conclude the test. */
+               tvec_setprogress("%%DONE");
                if (env && env->after) env->after(&srvtv, ctx);
                tvec_endtest(&srvtv);
              }
-             tvec_releaseregs(&srvtv); f &= ~f_regslive;
-             SENDPK(&srvtv, &srvrc, TVPK_TEST | TVPF_ACK);
+
+             /* Reset the input registers and report completion. */
+             tvec_releaseregs(&srvtv); tvec_initregs(&srvtv);
+             QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_TEST | TVPF_ACK);
              else { rc = -1; goto end; }
-             tvec_setprogress("%IDLE");
              break;
 
            default:
+             /* Some other kind of packet.  Complain. */
+
              rc = ioerr(&srvtv, &srvrc,
-                        "unexpected packet type 0x%04x", pk);
+                        "unexpected packet type 0x%04x during test group",
+                        pk);
              goto end;
 
          }
        }
 
       endgroup:
+       /* The test group completed. */
+
+       /* Tear down the environment and release other resources. */
        if (env && env->teardown) env->teardown(&srvtv, ctx);
-       xfree(ctx); t = 0; env = 0; ctx = 0;
+       tvec_releaseregs(&srvtv);
+       xfree(ctx); srvtv.test = 0; env = 0; ctx = 0;
+
+       /* Report completion. */
+       QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_EGROUP | TVPF_ACK);
+       else { rc = -1; goto end; }
        break;
 
       default:
-       goto bad;
+       rc = ioerr(&srvtv, &srvrc,
+                  "unexpected packet type 0x%04x at top level", pk);
     }
   }
   rc = 0;
 
 end:
+  /* Clean up and return. */
   if (env && env->teardown) env->teardown(&srvtv, ctx);
   xfree(ctx);
-  if (f&f_regslive) tvec_releaseregs(&srvtv);
-  release_comms(&srvrc);
+  if (srvtv.test) tvec_releaseregs(&srvtv);
+  release_comms(&srvrc); tvec_end(&srvtv);
   return (rc ? 2 : 0);
 
 bad:
+  /* Miscellaneous malformed packet. */
   rc = malformed(&srvtv, &srvrc); goto end;
-
-#undef f_regslive
 }
 
 /*----- Server output driver ----------------------------------------------*/
@@ -384,26 +783,26 @@ bad:
 static void remote_skipgroup(struct tvec_output *o,
                             const char *excuse, va_list *ap)
 {
-  SENDPK(&srvtv, &srvrc, TVPK_SKIPGRP)
-    buf_vputstrf16l(&srvrc.bout._b, excuse, ap);
+  QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIPGRP)
+    dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
 }
 
 static void remote_skip(struct tvec_output *o,
                        const char *excuse, va_list *ap)
 {
-  SENDPK(&srvtv, &srvrc, TVPK_SKIP)
-    buf_vputstrf16l(&srvrc.bout._b, excuse, ap);
+  QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIP)
+    dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
 }
 
 static void remote_fail(struct tvec_output *o,
                        const char *detail, va_list *ap)
 {
-  SENDPK(&srvtv, &srvrc, TVPK_FAIL)
+  QUEUEPK(&srvtv, &srvrc, 0, TVPK_FAIL)
     if (!detail)
-      buf_putbyte(&srvrc.bout._b, 0);
+      dbuf_putbyte(&srvrc.bout, 0);
     else {
-      buf_putbyte(&srvrc.bout._b, 1);
-      buf_vputstrf16l(&srvrc.bout._b, detail, ap);
+      dbuf_putbyte(&srvrc.bout, 1);
+      dbuf_vputstrf16l(&srvrc.bout, detail, ap);
     }
 }
 
@@ -420,14 +819,14 @@ static void remote_dumpreg(struct tvec_output *o,
   assert(!"unexpected register definition");
 
 found:
-  SENDPK(&srvtv, &srvrc, TVPK_DUMPREG) {
-    buf_putu16l(&srvrc.bout._b, r);
-    buf_putu16l(&srvrc.bout._b, disp);
+  QUEUEPK(&srvtv, &srvrc, 0, TVPK_DUMPREG) {
+    dbuf_putu16l(&srvrc.bout, r);
+    dbuf_putu16l(&srvrc.bout, disp);
     if (!rv)
-      buf_putbyte(&srvrc.bout._b, 0);
+      dbuf_putbyte(&srvrc.bout, 0);
     else {
-      buf_putbyte(&srvrc.bout._b, 1);
-      rd->ty->tobuf(&srvrc.bout._b, rv, rd);
+      dbuf_putbyte(&srvrc.bout, 1);
+      rd->ty->tobuf(DBUF_BUF(&srvrc.bout), rv, rd);
     }
   }
 }
@@ -435,9 +834,9 @@ found:
 static void remote_bbench(struct tvec_output *o,
                          const char *ident, unsigned unit)
 {
-  SENDPK(&srvtv, &srvrc, TVPK_BBENCH) {
-    buf_putstr32l(&srvrc.bout._b, ident);
-    buf_putu16l(&srvrc.bout._b, unit);
+  QUEUEPK(&srvtv, &srvrc, 0, TVPK_BBENCH) {
+    dbuf_putstr32l(&srvrc.bout, ident);
+    dbuf_putu16l(&srvrc.bout, unit);
   }
 }
 
@@ -445,16 +844,16 @@ static void remote_ebench(struct tvec_output *o,
                          const char *ident, unsigned unit,
                          const struct bench_timing *t)
 {
-  SENDPK(&srvtv, &srvrc, TVPK_EBENCH) {
-    buf_putstr32l(&srvrc.bout._b, ident);
-    buf_putu16l(&srvrc.bout._b, unit);
+  QUEUEPK(&srvtv, &srvrc, 0, TVPK_EBENCH) {
+    dbuf_putstr32l(&srvrc.bout, ident);
+    dbuf_putu16l(&srvrc.bout, unit);
     if (!t || !(t->f&BTF_ANY))
-      buf_putu16l(&srvrc.bout._b, 0);
+      dbuf_putu16l(&srvrc.bout, 0);
     else {
-      buf_putu16l(&srvrc.bout._b, t->f);
-      buf_putf64l(&srvrc.bout._b, t->n);
-      if (t->f&BTF_TIMEOK) buf_putf64l(&srvrc.bout._b, t->t);
-      if (t->f&BTF_CYOK) buf_putf64l(&srvrc.bout._b, t->cy);
+      dbuf_putu16l(&srvrc.bout, t->f);
+      dbuf_putf64l(&srvrc.bout, t->n);
+      if (t->f&BTF_TIMEOK) dbuf_putf64l(&srvrc.bout, t->t);
+      if (t->f&BTF_CYOK) dbuf_putf64l(&srvrc.bout, t->cy);
     }
   }
 }
@@ -462,18 +861,11 @@ static void remote_ebench(struct tvec_output *o,
 static void remote_report(struct tvec_output *o, unsigned level,
                          const char *msg, va_list *ap)
 {
-  const char *what;
-
-  SENDPK(&srvtv, &srvrc, TVPK_REPORT) {
-    buf_putu16l(&srvrc.bout._b, level);
-    buf_vputstrf16l(&srvrc.bout._b, msg, ap);
+  QUEUEPK(&srvtv, &srvrc, 0, TVPK_REPORT) {
+    dbuf_putu16l(&srvrc.bout, level);
+    dbuf_vputstrf16l(&srvrc.bout, msg, ap);
   } else {
-    switch (level) {
-      case TVLEV_NOTE: what = "notice"; break;
-      case TVLEV_ERR: what = "ERROR"; break;
-      default: what = "(?level)"; break;
-    }
-    fprintf(stderr, "%s %s: ", QUIS, what);
+    fprintf(stderr, "%s %s: ", QUIS, tvec_strlevel(level));
     vfprintf(stderr, msg, *ap);
     fputc('\n', stderr);
   }
@@ -726,6 +1118,7 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
   for (;;) {
     rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end;
     if (buf_getu16l(b, &pk)) goto bad;
+    if (pk == end) break;
 
     switch (pk) {
 
@@ -831,8 +1224,10 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
       case TVPK_EBENCH:
        p = buf_getmem32l(b, &n); if (!p) goto bad;
        if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
-       if (u >= TVBU_LIMIT)
-         { rc = ioerr(tv, &r->rc, "unit code %u out of range", u); goto end; }
+       if (u >= TVBU_LIMIT) {
+         rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
+         goto end;
+       }
        if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad;
        if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad;
        if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad;
@@ -843,12 +1238,12 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
        break;
 
       default:
-       if (pk == end) { rc = 0; goto end; }
        rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk);
        goto end;
     }
   }
 
+  rc = RECV_OK;
 end:
   DDESTROY(&d);
   xfree(reg);
@@ -908,6 +1303,7 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
   ssize_t n;
   int rc;
 
+  if (r->errfd < 0) { rc = 0; goto end; }
   if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE;
   else r->rc.f &= ~TVRF_MUFFLE;
   if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) {
@@ -934,7 +1330,7 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
 end:
   if (f&ERF_CLOSE) {
     lbuf_close(&r->errbuf);
-    close(r->errfd);
+    close(r->errfd); r->errfd = -1;
   }
   return (rc);
 }
@@ -943,10 +1339,8 @@ end:
 static void disconnect_remote(struct tvec_state *tv,
                              struct tvec_remotectx *r, unsigned f)
 {
-  if (r->kid < 0) return;
   if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM);
   close_comms(&r->rc);
-  if (r->kid > 0) kill(r->kid, SIGTERM);
   drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
 }
 
@@ -966,9 +1360,9 @@ static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
   lbuf_init(&r->errbuf, report_errline, r);
   r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN;
 
-  SENDPK(tv, &r->rc, TVPK_VER) {
-    buf_putu16l(&r->rc.bout._b, 0);
-    buf_putu16l(&r->rc.bout._b, 0);
+  QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_VER) {
+    dbuf_putu16l(&r->rc.bout, 0);
+    dbuf_putu16l(&r->rc.bout, 0);
   } else { rc = -1; goto end; }
 
   if (handle_packets(tv, r, 0, TVPK_VER | TVPF_ACK, &b))
@@ -980,8 +1374,8 @@ static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
     goto end;
   }
 
-  SENDPK(tv, &r->rc, TVPK_BGROUP)
-    buf_putstr16l(&r->rc.bout._b, tv->test->name);
+  QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_BGROUP)
+    dbuf_putstr16l(&r->rc.bout, tv->test->name);
   else { rc = -1; goto end; }
   if (handle_packets(tv, r, 0, TVPK_BGROUP | TVPF_ACK, &b))
     { rc = -1; goto end; }
@@ -1033,7 +1427,8 @@ static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
 
 static void reset_vars(struct tvec_remotectx *r)
 {
-  r->exwant = TVXST_RUN; r->rc.f = (r->rc.f&~TVRF_RCNMASK) | TVRCN_DEMAND;
+  r->exwant = TVXST_RUN;
+  r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) | TVRCN_DEMAND;
   DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
 }
 
@@ -1054,28 +1449,31 @@ void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
   reset_vars(r);
 }
 
-int tvec_remoteset(struct tvec_state *tv, const char *var,
-                  const struct tvec_env *env, void *ctx)
+int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx)
 {
   struct tvec_remotectx *r = ctx;
   union tvec_regval rv;
   int rc;
 
   if (STRCMP(var, ==, "@exit")) {
+    if (r->rc.f&TVRF_SETEXIT) { rc = tvec_dupreg(tv, var); goto end; }
     if (tvty_flags.parse(&rv, &exit_regdef, tv)) { rc = -1; goto end; }
-    if (r) r->exwant = rv.u;
-    rc = 1;
+    r->exwant = rv.u; r->rc.f |= TVRF_SETEXIT; rc = 1;
   } else if (STRCMP(var, ==, "@progress")) {
+    if (r->rc.f&TVRF_SETPRG) { rc = tvec_dupreg(tv, var); goto end; }
     tvty_string.init(&rv, &progress_regdef);
     rc = tvty_string.parse(&rv, &progress_regdef, tv);
-    if (r && !rc)
-      { DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz); }
+    if (!rc) {
+      DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz);
+      r->rc.f |= TVRF_SETPRG;
+    }
     tvty_string.release(&rv, &progress_regdef);
     if (rc) { rc = -1; goto end; }
     rc = 1;
   } else if (STRCMP(var, ==, "@reconnect")) {
+    if (r->rc.f&TVRF_SETRCN) { rc = tvec_dupreg(tv, var); goto end; }
     if (tvty_uenum.parse(&rv, &reconn_regdef, tv)) { rc = -1; goto end; }
-    if (r) r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK);
+    r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK) | TVRF_SETRCN;
     rc = 1;
   } else
     rc = 0;
@@ -1109,8 +1507,9 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
       tvec_skip(tv, "no connection"); return;
   }
 
-  SENDPK(tv, &r->rc, TVPK_TEST)
-    tvec_serialize(tv->in, &r->rc.bout._b,
+  DRESET(&r->progress); DPUTS(&r->progress, "%IDLE");
+  QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_TEST)
+    tvec_serialize(tv->in, DBUF_BUF(&r->rc.bout),
                   tv->test->regs, tv->nreg, tv->regsz);
   else { rc = -1; goto end; }
   rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
@@ -1168,11 +1567,15 @@ end:
 void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
 {
   struct tvec_remotectx *r = ctx;
+  buf b;
 
-  if (r) {
-    disconnect_remote(tv, r, 0); release_comms(&r->rc);
-    DDESTROY(&r->prgwant); DDESTROY(&r->progress);
+  if (r->rc.outfd >= 0) {
+    QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_EGROUP);
+    if (!handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_EGROUP | TVPF_ACK, &b))
+      if (BLEFT(&b)) malformed(tv, &r->rc);
   }
+  disconnect_remote(tv, r, 0); release_comms(&r->rc);
+  DDESTROY(&r->prgwant); DDESTROY(&r->progress);
 }
 
 /*----- Connectors --------------------------------------------------------*/
@@ -1238,6 +1641,7 @@ int tvec_fork(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
 
   if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
   if (!kid) {
+    if (tv->fp) fclose(tv->fp);
     config.tests = rf->f.tests ? rf->f.tests : tv->tests;
     config.nrout = tv->nrout; config.nreg = tv->nreg;
     config.regsz = tv->regsz;
diff --git a/test/tvec-timeout.c b/test/tvec-timeout.c
new file mode 100644 (file)
index 0000000..ca86b5c
--- /dev/null
@@ -0,0 +1,313 @@
+/* -*-c-*-
+ *
+ * Timeout extension for test vector framework
+ *
+ * (c) 2024 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 <ctype.h>
+#include <errno.h>
+#include <string.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "tvec.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void reset(struct tvec_timeoutctx *tc)
+{
+  const struct tvec_timeoutenv *te = tc->te;
+
+  tc->timer = te->timer; tc->t = te->t; tc->f &= ~TVTF_SETMASK;
+}
+
+/* --- @tvec_timeoutsetup@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @const struct tvec_env *env@ = environment description
+ *             @void *pctx@ = parent context (ignored)
+ *             @void *ctx@ = context pointer to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a timeout environment context.
+ *
+ *             The environment description should be a @struct
+ *             tvec_timeoutenv@.
+ */
+
+void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env,
+                      void *pctx, void *ctx)
+{
+  struct tvec_timeoutctx *tc = ctx;
+  const struct tvec_timeoutenv *te = (struct tvec_timeoutenv *)env;
+  const struct tvec_env *subenv = te->env;
+
+  tc->te = te;
+
+  reset(tc);
+
+  tc->subctx = 0;
+  if (subenv && subenv->ctxsz) tc->subctx = xmalloc(subenv->ctxsz);
+  if (subenv && subenv->setup) subenv->setup(tv, subenv, tc, tc->subctx);
+}
+
+/* --- @tvec_timeoutset@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @const char *var@ = variable name to set
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    Zero on success, @-1@ on failure.
+ *
+ * Use:                Set a special variable.  The following special variables are
+ *             supported.
+ *
+ *               * %|@timeout|% is the number of seconds (or other unit) to
+ *                 wait before giving up and killing the test process.  The
+ *                 string may also include a keyword %|REAL|%, %|VIRTUAL|%,
+ *                 or %|PROF|% to select the timer.
+ *
+ *             Unrecognized variables are passed to the subordinate
+ *             environment, if there is one.
+ */
+
+int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
+{
+  struct tvec_timeoutctx *tc = ctx;
+  const struct tvec_timeoutenv *te = tc->te;
+  const struct tvec_env *subenv = te->env;
+  dstr d = DSTR_INIT;
+  double t = 0.0; unsigned tmr = 0;
+  const char *p; char *q; size_t pos;
+  int rc;
+  unsigned f = 0;
+#define f_time 1u
+#define f_timer 2u
+#define f_all (f_time | f_timer)
+
+  if (STRCMP(var, ==, "@timeout")) {
+    if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; }
+
+    for (;;) {
+      DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break;
+    timeout_primed:
+      p = d.buf;
+      if (!(f&f_timer) && STRCMP(p, ==, "REAL"))
+       { tmr = ITIMER_REAL; f |= f_timer;  }
+      else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL"))
+       { tmr = ITIMER_VIRTUAL; f |= f_timer; }
+      else if (!(f&f_timer) && STRCMP(p, ==, "PROF"))
+       { tmr = ITIMER_PROF; f |= f_timer; }
+
+      else if (!(f&f_time)) {
+       if (*p == '+' || *p == '-') p++;
+       if (*p == '.') p++;
+       if (!ISDIGIT(*p)) {
+         tvec_syntax(tv, *d.buf, "floating-point number");
+         rc = -1; goto end;
+       }
+       errno = 0; t = strtod(p, &q); f |= f_time;
+       if (errno) {
+         tvec_error(tv, "invalid floating-point number `%s': %s",
+                    d.buf, strerror(errno));
+         rc = -1; goto end;
+       }
+
+       if (!*q) {
+         tvec_skipspc(tv); pos = d.len;
+         if (!tvec_readword(tv, &d, ";", 0)) pos++;
+         q = d.buf + pos;
+       }
+
+       if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec"))
+         /* nothing to do */;
+
+       else if (STRCMP(q, ==, "ds")) t *= 1e-1;
+       else if (STRCMP(q, ==, "cs")) t *= 1e-2;
+       else if (STRCMP(q, ==, "ms")) t *= 1e-3;
+       else if (STRCMP(q, ==, "us") || STRCMP(q, ==, "µs")) t *= 1e-6;
+       else if (STRCMP(q, ==, "ns")) t *= 1e-9;
+       else if (STRCMP(q, ==, "ps")) t *= 1e-12;
+       else if (STRCMP(q, ==, "fs")) t *= 1e-15;
+       else if (STRCMP(q, ==, "as")) t *= 1e-18;
+       else if (STRCMP(q, ==, "zs")) t *= 1e-21;
+       else if (STRCMP(q, ==, "ys")) t *= 1e-24;
+
+       else if (STRCMP(q, ==, "das")) t *= 1e+1;
+       else if (STRCMP(q, ==, "hs")) t *= 1e+2;
+       else if (STRCMP(q, ==, "ks")) t *= 1e+3;
+       else if (STRCMP(q, ==, "Ms")) t *= 1e+6;
+       else if (STRCMP(q, ==, "Gs")) t *= 1e+9;
+       else if (STRCMP(q, ==, "Ts")) t *= 1e+12;
+       else if (STRCMP(q, ==, "Ps")) t *= 1e+15;
+       else if (STRCMP(q, ==, "Es")) t *= 1e+18;
+       else if (STRCMP(q, ==, "Zs")) t *= 1e+21;
+       else if (STRCMP(q, ==, "Ys")) t *= 1e+24;
+
+       else if (STRCMP(q, ==, "m") || STRCMP(q, ==, "min")) t *= 60;
+       else if (STRCMP(q, ==, "h") || STRCMP(q, ==, "hr")) t *= 3600;
+       else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400;
+       else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600;
+
+       else {
+         if (f&f_timer)
+           { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; }
+         pos = q - d.buf; d.len -= pos;
+         memmove(d.buf, q, d.len);
+         goto timeout_primed;
+       }
+      }
+
+      if (!(~f&f_all)) break;
+    }
+
+    if (!f) {
+      rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword");
+      goto end;
+    }
+    rc = tvec_flushtoeol(tv, 0); if (rc) goto end;
+    if (f&f_time) tc->t = t;
+    if (f&f_timer) tc->timer = tmr;
+    tc->f |= TVTF_SETTMO;
+
+  } else if (subenv && subenv->set)
+    rc = subenv->set(tv, var, tc->subctx);
+  else
+    rc = 0;
+
+end:
+  dstr_destroy(&d);
+  return (rc);
+
+#undef f_time
+#undef f_timer
+#undef f_all
+}
+
+/* --- @tvec_timeoutbefore@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Invoke the subordinate environment's @before@ function to
+ *             prepare for the test.
+ */
+
+void tvec_timeoutbefore(struct tvec_state *tv, void *ctx)
+{
+  struct tvec_timeoutctx *tc = ctx;
+  const struct tvec_timeoutenv *te = tc->te;
+  const struct tvec_env *subenv = te->env;
+
+  /* Just call the subsidiary environment. */
+  if (subenv && subenv->before) subenv->before(tv, tc->subctx);
+}
+
+/* --- @tvec_timeoutafter@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Invoke the subordinate environment's @after@ function to
+ *             clean up after the test.
+ */
+
+void tvec_timeoutafter(struct tvec_state *tv, void *ctx)
+{
+  struct tvec_timeoutctx *tc = ctx;
+  const struct tvec_timeoutenv *te = tc->te;
+  const struct tvec_env *subenv = te->env;
+
+  /* Reset variables. */
+  reset(tc);
+
+  /* Pass the call on to the subsidiary environment. */
+  if (subenv && subenv->after) subenv->after(tv, tc->subctx);
+}
+
+/* --- @tvec_timeoutteardown@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Tear down the timeoutmark environment.
+ */
+
+void tvec_timeoutteardown(struct tvec_state *tv, void *ctx)
+{
+  struct tvec_timeoutctx *tc = ctx;
+  const struct tvec_timeoutenv *te = tc->te;
+  const struct tvec_env *subenv = te->env;
+
+  /* Just call the subsidiary environment. */
+  if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx);
+}
+
+/* --- @tvec_timeoutrun@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @tvec_testfn *fn@ = test function to run
+ *             @void *ctx@ = context pointer for the test function
+ *
+ * Returns:    ---
+ *
+ * Use:                Runs a test with a timeout attached.
+ */
+
+void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
+{
+  struct tvec_timeoutctx *tc = ctx;
+  const struct tvec_timeoutenv *te = tc->te;
+  const struct tvec_env *subenv = te->env;
+  struct itimerval itv;
+
+  itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
+  itv.it_value.tv_sec = tc->t;
+  itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6;
+
+  if (setitimer(tc->timer, &itv, 0))
+    tvec_skip(tv, "failed to set interval timer: %s", strerror(errno));
+  else {
+    if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx);
+    else fn(tv->in, tv->out, tc->subctx);
+
+    itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
+    itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0;
+    if (setitimer(tc->timer, &itv, 0))
+      tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno));
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
index 3afa8086b0eab1b116585877aa534a2d83845e68..681409d0bb31f85649d56254b28e227737907f80 100644 (file)
@@ -1562,6 +1562,10 @@ int tvec_claimeq_float(struct tvec_state *tv,
                                file, lno, expr));
 }
 
+const struct tvec_floatinfo
+  tvflt_finite = { TVFF_EXACT, -DBL_MAX, DBL_MAX, 0.0 },
+  tvflt_nonneg = { TVFF_EXACT, 0, DBL_MAX, 0.0 };
+
 /*----- Enumerations ------------------------------------------------------*/
 
 #define init_ienum init_int
index 123743e5d835c05fd17a1b8696901af29a913c92..38c462b960cd9e521313f2628679ac0977878f7d 100644 (file)
@@ -57,8 +57,8 @@
  *                             -> @tvec_mismatch@
  *                                     -> output @dumpreg@
  *                                             -> type @dump@
- *                     -> env @after@
  *                     -> output @etest@
+ *                     -> env @after@
  *   finally
  *                     -> output @egroup@
  *                     -> env @teardown@
@@ -177,16 +177,16 @@ enum {
  */
 
 union tvec_regval {
-       /* The actual register value.  This is what the type handler sees.
-        * Additional members can be added by setting `TVEC_REGSLOTS' before
-        * including this file.
-        *
-        * A register value can be /initialized/, which simply means that its
-        * contents represent a valid value according to its type -- the
-        * register can be compared, dumped, serialized, parsed into, etc.
-        * You can't do anything safely to an uninitialized register value
-        * other than initialize it.
-        */
+  /* The actual register value.  This is what the type handler sees.
+   * Additional members can be added by setting `TVEC_REGSLOTS' before
+   * including this file.
+   *
+   * A register value can be /initialized/, which simply means that its
+   * contents represent a valid value according to its type -- the register
+   * can be compared, dumped, serialized, parsed into, etc.  You can't do
+   * anything safely to an uninitialized register value other than initialize
+   * it.
+   */
 
   long i;                              /* signed integer */
   unsigned long u;                     /* unsigned integer */
@@ -200,24 +200,22 @@ union tvec_regval {
 };
 
 struct tvec_reg {
-       /* A register.
-        *
-        * Note that all of the registers listed as being used by a
-        * particular test group are initialized at all times[1] while that
-        * test group is being processed.  (The other register slots don't
-        * even have types associated with them, so there's nothing useful we
-        * could do with them.)
-        *
-        * The `TVRF_LIVE' flag indicates that the register was assigned a
-        * value by the test vector file: it's the right thing to use to
-        * check whether an optional register is actually present.  Even
-        * `dead' registers are still initialized, though.
-        *
-        * [1] This isn't quite true.  Between individual tests, the
-        *     registers are released and reinitialized in order to reset
-        *     them to known values ready for the next test.  But you won't
-        *     see them at this point.
-        */
+  /* A register.
+   *
+   * Note that all of the registers listed as being used by a particular test
+   * group are initialized at all times[1] while that test group is being
+   * processed.  (The other register slots don't even have types associated
+   * with them, so there's nothing useful we could do with them.)
+   *
+   * The `TVRF_LIVE' flag indicates that the register was assigned a value by
+   * the test vector file: it's the right thing to use to check whether an
+   * optional register is actually present.  Even `dead' registers are still
+   * initialized, though.
+   *
+   * [1] This isn't quite true.  Between individual tests, the registers are
+   *    released and reinitialized in order to reset them to known values
+   *    ready for the next test.  But you won't see them at this point.
+   */
 
   unsigned f;                          /* flags */
 #define TVRF_LIVE 1u                   /*   used in current test  */
@@ -225,13 +223,12 @@ struct tvec_reg {
 };
 
 struct tvec_regdef {
-       /* A register definition.  Register definitions list the registers
-        * which are used by a particular test group (see `struct tvec_test'
-        * below).
-        *
-        * A vector of register definitions is terminated by a definition
-        * whose `name' slot is null.
-        */
+  /* A register definition.  Register definitions list the registers which
+   * are used by a particular test group (see `struct tvec_test' below).
+   *
+   * A vector of register definitions is terminated by a definition whose
+   * `name' slot is null.
+   */
 
   const char *name;                    /* register name (for input files) */
   unsigned i;                          /* register index */
@@ -266,55 +263,55 @@ struct tvec_regty {
   /* A register type. */
 
   void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/);
-       /* Initialize the value in @*rv@.  This will be called before any
-        * other function acting on the value, including @release@.
-        */
+    /* Initialize the value in @*rv@.  This will be called before any other
+     * function acting on the value, including @release@.
+     */
 
   void (*release)(union tvec_regval */*rv*/,
                  const struct tvec_regdef */*rd*/);
-       /* Release any resources associated with the value in @*rv@. */
+    /* Release any resources associated with the value in @*rv@. */
 
   int (*eq)(const union tvec_regval */*rv0*/,
            const union tvec_regval */*rv1*/,
            const struct tvec_regdef */*rd*/);
-       /* Return nonzero if @*rv0@ and @*rv1@ are equal values.
-        * Asymmetric criteria are permitted: @tvec_checkregs@ calls @eq@
-        * with the input register as @rv0@ and the output as @rv1@.
-        */
+    /* Return nonzero if @*rv0@ and @*rv1@ are equal values.  Asymmetric
+     * criteria are permitted: @tvec_checkregs@ calls @eq@ with the input
+     * register as @rv0@ and the output as @rv1@.
+     */
 
   int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/,
               const struct tvec_regdef */*rd*/);
-       /* Serialize the value @*rv@, writing the result to @b@.  Return
-        * zero on success, or @-1@ on error.
-        */
+    /* Serialize the value @*rv@, writing the result to @b@.  Return zero on
+     * success, or %$-1$% on error.
+     */
 
   int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/,
                 const struct tvec_regdef */*rd*/);
-       /* Deserialize a value from @b@, storing it in @*rv@.  Return zero on
-        * success, or @-1@ on error.
-        */
+    /* Deserialize a value from @b@, storing it in @*rv@.  Return zero on
+     * success, or %$-1$% on error.
+     */
 
   int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/,
               struct tvec_state */*tv*/);
-       /* Parse a value from @tv->fp@, storing it in @*rv@.  Return zero on
-        * success, or @-1@ on error, having reported one or more errors via
-        * @tvec_error@ or @tvec_syntax@.  A successful return should leave
-        * the input position at the start of the next line; the caller will
-        * flush the remainder of the line itself.
-        */
+    /* Parse a value from @tv->fp@, storing it in @*rv@.  Return zero on
+     * success, or %$-1$% on error, having reported one or more errors via
+     * @tvec_error@ or @tvec_syntax@.  A successful return should leave the
+     * input position at the start of the next line; the caller will flush
+     * the remainder of the line itself.
+     */
 
   void (*dump)(const union tvec_regval */*rv*/,
               const struct tvec_regdef */*rd*/,
               unsigned /*style*/,
               const struct gprintf_ops */*gops*/, void */*go*/);
 #define TVSF_COMPACT 1u
-       /* Write a human-readable representation of the value @*rv@ using
-        * @gprintf@ on @gops@ and @go@.  The @style@ is a collection of
-        * flags: if @TVSF_COMPACT@ is set, then output should be minimal,
-        * and must fit on a single line; otherwise, output may consist of
-        * multiple lines and may contain redundant information if that is
-        * likely to be useful to a human reader.
-        */
+    /* Write a human-readable representation of the value @*rv@ using
+     * @gprintf@ on @gops@ and @go@.  The @style@ is a collection of flags:
+     * if @TVSF_COMPACT@ is set, then output should be minimal, and must fit
+     * on a single line; otherwise, output may consist of multiple lines and
+     * may contain redundant information if that is likely to be useful to a
+     * human reader.
+     */
 };
 
 /*----- Test descriptions -------------------------------------------------*/
@@ -346,12 +343,13 @@ typedef void tvec_envsetupfn(struct tvec_state */*tv*/,
   /* Initialize the context; called at the start of a test group; @pctx@ is
    * null for environments called by the core, but may be non-null for
    * subordinate environments.  If setup fails, the function should call
-   * @tvec_skipgroup@ with a suitable excuse.  The @set@ and @teardown@ entry
-   * points will still be called, but @before@, @run@, and @after@ will not.
+   * @tvec_skipgroup@ with a suitable excuse.  The @set@, @after@, and
+   * @teardown@ entry points will still be called, but @before@ and @run@
+   * will not.
    */
 
-typedef int tvec_envsetfn(struct tvec_state */*tv*/, const char */*var*/,
-                         const struct tvec_env */*env*/, void */*ctx*/);
+typedef int tvec_envsetfn(struct tvec_state */*tv*/,
+                         const char */*var*/, 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.
@@ -376,8 +374,10 @@ typedef void tvec_envrunfn(struct tvec_state */*tv*/,
 
 typedef void tvec_envafterfn(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.
+   * resetting whatever things were established by @set@.  This function
+   * %%\emph{is}%% called if the test group is skipped, so that the test
+   * environment can reset variables set by the @set@ entry point.  It should
+   * check the @TVSF_SKIP@ flag if necessary.
    */
 
 typedef void tvec_envteardownfn(struct tvec_state */*tv*/, void */*ctx*/);
@@ -447,6 +447,7 @@ struct tvec_state {
 #define TVSF_OUTMASK 0x00f0u           /*   test outcome (@TVOUT_...@) */
 #define TVSF_OUTSHIFT 4                        /*   shift applied to outcome */
 #define TVSF_XFAIL 0x0100u             /*   test expected to fail */
+#define TVSF_MUFFLE 0x0200u            /*   muffle errors */
 
   /* Registers.  Available to execution environments. */
   unsigned nrout, nreg;                       /* number of output/total registers */
@@ -502,106 +503,110 @@ struct tvec_outops {
   /* Output operations. */
 
   void (*bsession)(struct tvec_output */*o*/, struct tvec_state */*tv*/);
-       /* Begin a test session.  The output driver will probably want to
-        * save @tv@, because this isn't provided to any other methods.
-        */
+    /* Begin a test session.  The output driver will probably want to
+     * save @tv@, because this isn't provided to any other methods.
+     */
 
   int (*esession)(struct tvec_output */*o*/);
-       /* End a session, and return the suggested exit code. */
+    /* End a session, and return the suggested exit code. */
 
   void (*bgroup)(struct tvec_output */*o*/);
-       /* Begin a test group.  The test group description is @tv->test@. */
+    /* Begin a test group.  The test group description is @tv->test@. */
 
   void (*skipgroup)(struct tvec_output */*o*/,
                    const char */*excuse*/, va_list */*ap*/);
-       /* The group is being skipped; @excuse@ may be null or a format
-        * string explaining why.  The @egroup@ method will not be called
-        * separately.
-        */
+    /* The group is being skipped; @excuse@ may be null or a format
+     * string explaining why.  The @egroup@ method will not be called
+     * separately.
+     */
 
   void (*egroup)(struct tvec_output */*o*/);
-       /* End a test group.  At least one test was attempted or @skipgroup@
-        * would have been called instead.  If @tv->curr[TVOUT_LOSE]@ is
-        * nonzero then the test group as a whole failed; otherwise it
-        * passed.
-        */
+    /* End a test group.  At least one test was attempted or @skipgroup@
+     * would have been called instead.  If @tv->curr[TVOUT_LOSE]@ is nonzero
+     * then the test group as a whole failed; otherwise it passed.
+     */
 
   void (*btest)(struct tvec_output */*o*/);
-       /* Begin a test case. */
+    /* Begin a test case. */
 
   void (*skip)(struct tvec_output */*o*/,
               const char */*excuse*/, va_list */*ap*/);
-       /* The test case is being skipped; @excuse@ may be null or a format
-        * string explaining why.  The @etest@ function will still be called
-        * (so this works differently from @skipgroup@ and @egroup@).  A test
-        * case can be skipped at most once, and, if skipped, it cannot fail.
-        */
+    /* The test case is being skipped; @excuse@ may be null or a format
+     * string explaining why.  The @etest@ function will still be called (so
+     * this works differently from @skipgroup@ and @egroup@).  A test case
+     * can be skipped at most once, and, if skipped, it cannot fail.
+     */
 
   void (*fail)(struct tvec_output */*o*/,
               const char */*detail*/, va_list */*ap*/);
-       /* The test case failed.
-        *
-        * The output driver should preferably report the filename (@infile@)
-        * and line number (@test_lno@, not @lno@) for the failing test.
-        *
-        * The @detail@ may be null or a format string describing detail
-        * about how the failing test was run which can't be determined from
-        * the registers; a @detail@ is usually provided when (and only when)
-        * the test environment potentially invokes the test function more
-        * than once.
-        *
-        * A single test case can fail multiple times!
-        */
+    /* The test case failed.
+     *
+     * The output driver should preferably report the filename (@infile@) and
+     * line number (@test_lno@, not @lno@) for the failing test.
+     *
+     * The @detail@ may be null or a format string describing detail about
+     * how the failing test was run which can't be determined from the
+     * registers; a @detail@ is usually provided when (and only when) the
+     * test environment potentially invokes the test function more than once.
+     *
+     * A single test case can fail multiple times!
+     */
 
   void (*dumpreg)(struct tvec_output */*o*/,
                  unsigned /*disp*/, const union tvec_regval */*rv*/,
                  const struct tvec_regdef */*rd*/);
-       /* Dump a register.  The `disposition' @disp@ explains what condition
-        * the register is in; see @tvec_dumpreg@ and the @TVRD_...@ codes.
-        * The register value is at @rv@, and its definition, including its
-        * type, at @rd@.  Note that this function may be called on virtual
-        * registers which aren't in either of the register vectors or
-        * mentioned by the test description.  It may also be called with
-        * @rv@ null, indicating that the register is not live.
-        */
+    /* Dump a register.  The `disposition' @disp@ explains what condition the
+     * register is in; see @tvec_dumpreg@ and the @TVRD_...@ codes.  The
+     * register value is at @rv@, and its definition, including its type, at
+     * @rd@.  Note that this function may be called on virtual registers
+     * which aren't in either of the register vectors or mentioned by the
+     * test description.  It may also be called with @rv@ null, indicating
+     * that the register is not live.
+     */
 
   void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/);
-       /* The test case concluded with the given @outcome@ (one of the
-        * @TVOUT_...@ codes.
-        */
+    /* The test case concluded with the given @outcome@ (one of the
+     * @TVOUT_...@ codes.
+     */
 
   void (*bbench)(struct tvec_output */*o*/,
                 const char */*ident*/, unsigned /*unit*/);
-       /* Begin a benchmark.  The @ident@ is a formatted string identifying
-        * the benchmark based on the values of the input registered marked
-        * @TVRF_ID@; the output driver is free to use this or come up with
-        * its own way to identify the test, e.g., by inspecting the register
-        * values for itself.  The @unit@ is one of the @TVBU_...@ constants
-        * explaining what sort of thing is being measured.
-        */
+    /* Begin a benchmark.  The @ident@ is a formatted string identifying the
+     * benchmark based on the values of the input registered marked
+     * @TVRF_ID@; the output driver is free to use this or come up with its
+     * own way to identify the test, e.g., by inspecting the register values
+     * for itself.  The @unit@ is one of the @TVBU_...@ constants explaining
+     * what sort of thing is being measured.
+     */
 
   void (*ebench)(struct tvec_output */*o*/,
                 const char */*ident*/, unsigned /*unit*/,
                 const struct bench_timing */*tm*/);
-       /* End a benchmark.  The @ident@ and @unit@ arguments are as for
-        * @bbench@.  If @tm@ is zero then the measurement failed; otherwise
-        * @tm->n@ counts total number of things (operations or bytes, as
-        * indicated by @unit@) processed in the indicated time.
-        */
+    /* End a benchmark.  The @ident@ and @unit@ arguments are as for
+     * @bbench@.  If @tm@ is zero then the measurement failed; otherwise
+     * @tm->n@ counts total number of things (operations or bytes, as
+     * indicated by @unit@) processed in the indicated time.
+     */
 
   void (*report)(struct tvec_output */*o*/, unsigned /*level*/,
                 const char */*msg*/, va_list */*ap*/);
-       /* Report a message.  The driver should ideally report the filename
-        * (@infile@) and line number (@lno@) prompting the error.
-        */
+    /* Report a message.  The driver should ideally report the filename
+     * (@infile@) and line number (@lno@) prompting the error.
+     */
 
   void (*destroy)(struct tvec_output */*o*/);
-       /* Release any resources acquired by the driver. */
+    /* Release any resources acquired by the driver. */
 };
 
+#define TVEC_LEVELS(_)                                                 \
+       _(NOTE, "notice", 4)                                            \
+       _(ERR, "ERROR", 8)
+
 enum {
-  TVLEV_NOTE = 4,                      /* notice */
-  TVLEV_ERR = 8                                /* error */
+#define TVEC_DEFLEVEL(tag, name, val) TVLEV_##tag = val,
+  TVEC_LEVELS(TVEC_DEFLEVEL)
+#undef TVEC_DEFLEVEL
+  TVLEV_LIMIT
 };
 
 /*----- Session lifecycle -------------------------------------------------*/
@@ -640,7 +645,7 @@ extern int tvec_end(struct tvec_state */*tv*/);
  *             @const char *infile@ = the name of the input file
  *             @FILE *fp@ = stream to read from
  *
- * Returns:    Zero on success, @-1@ on error.
+ * Returns:    Zero on success, %$-1$% on error.
  *
  * Use:                Read test vector data from @fp@ and exercise test functions.
  *             THe return code doesn't indicate test failures: it's only
@@ -684,7 +689,7 @@ extern void tvec_parseargs(int /*argc*/, char */*argv*/[],
  *             @const char *file@ = pathname of file to read
  *             @const char *arg@ = argument to interpret
  *
- * Returns:    Zero on success, @-1@ on error.
+ * Returns:    Zero on success, %$-1$% on error.
  *
  * Use:                Read test vector data from stdin or a named file.  The
  *             @tvec_readarg@ function reads from stdin if @arg@ is `%|-|%',
@@ -700,7 +705,7 @@ extern int tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/);
  * Arguments:  @struct tvec_state *tv@ = test vector state
  *             @const char *dflt@ = defsault filename or null
  *
- * Returns:    Zero on success, @-1@ on error.
+ * Returns:    Zero on success, %$-1$% on error.
  *
  * Use:                Reads from the default test vector data.  If @file@ is null,
  *             then read from standard input, unless that's a terminal; if
@@ -719,7 +724,7 @@ extern int tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/);
  *             @int *argpos_inout@ = current argument position (updated)
  *             @const char *dflt@ = default filename or null
  *
- * Returns:    Zero on success, @-1@ on error.
+ * Returns:    Zero on success, %$-1$% on error.
  *
  * Use:                Reads from the sources indicated by the command-line
  *             arguments, in order, interpreting each as for @tvec_readarg@;
@@ -764,8 +769,9 @@ extern int tvec_main(int /*argc*/, char */*argv*/[],
  *             the @setup@ function fails.
  */
 
-extern void PRINTF_LIKE(2, 3)
-  tvec_skipgroup(struct tvec_state */*tv*/, const char */*excuse*/, ...);
+extern PRINTF_LIKE(2, 3)
+  void tvec_skipgroup(struct tvec_state */*tv*/,
+                     const char */*excuse*/, ...);
 extern void tvec_skipgroup_v(struct tvec_state */*tv*/,
                             const char */*excuse*/, va_list */*ap*/);
 
@@ -781,46 +787,11 @@ extern void tvec_skipgroup_v(struct tvec_state */*tv*/,
  *             the @before@ function fails.
  */
 
-extern void PRINTF_LIKE(2, 3)
-  tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...);
+extern PRINTF_LIKE(2, 3)
+  void tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...);
 extern void tvec_skip_v(struct tvec_state */*tv*/,
                        const char */*excuse*/, va_list */*ap*/);
 
-/* --- @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.
- */
-
-extern void tvec_resetoutputs(struct tvec_state */*tv*/);
-
-extern void tvec_initregs(struct tvec_state */*tv*/);
-extern void tvec_releaseregs(struct tvec_state */*tv*/);
-
-/* --- @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_checkregs(struct tvec_state */*tv*/);
-
 /* --- @tvec_fail@, @tvec_fail_v@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
@@ -838,8 +809,8 @@ extern int tvec_checkregs(struct tvec_state */*tv*/);
  *             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 PRINTF_LIKE(2, 3)
+  void tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...);
 extern void tvec_fail_v(struct tvec_state */*tv*/,
                        const char */*detail*/, va_list */*ap*/);
 
@@ -865,6 +836,55 @@ extern void tvec_dumpreg(struct tvec_state */*tv*/,
                         unsigned /*disp*/, const union tvec_regval */*rv*/,
                         const struct tvec_regdef */*rd*/);
 
+/* --- @tvec_initregs@, @tvec_releaseregs@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize or release, respectively, the registers required
+ *             by the current test.  All of the registers, both input and
+ *             output, are effected.  Initialized registers are not marked
+ *             live.
+ */
+
+extern void tvec_initregs(struct tvec_state */*tv*/);
+extern void tvec_releaseregs(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.
+ *             Output registers are marked live if and only if the
+ *             corresponding input register is live.
+ */
+
+extern void tvec_resetoutputs(struct tvec_state */*tv*/);
+
+/* --- @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_checkregs(struct tvec_state */*tv*/);
+
 /* --- @tvec_mismatch@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
@@ -896,8 +916,8 @@ extern void tvec_mismatch(struct tvec_state */*tv*/, unsigned /*f*/);
  *             obvious way.
  */
 
-extern void PRINTF_LIKE(2, 3)
-  tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...);
+extern PRINTF_LIKE(2, 3)
+  void tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...);
 extern void tvec_check_v(struct tvec_state */*tv*/,
                         const char */*detail*/, va_list */*ap*/);
 
@@ -994,13 +1014,13 @@ extern void tvec_begintest(struct tvec_state */*tv*/,
 #define TVEC_BEGINTEST(tv)                                             \
        do tvec_begintest(tv, __FILE__, __LINE__); while (0)
 
-/* --- *tvec_endtest@ --- *
+/* --- @tvec_endtest@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *
  * Returns:    ---
  *
- * Use:                End a ad-hoc test case,  The statistics are updated and the
+ * Use:                End an ad-hoc test case,  The statistics are updated and the
  *             outcome is reported to the output formatter.
  */
 
@@ -1053,10 +1073,10 @@ extern void tvec_endtest(struct tvec_state */*tv*/);
  *             the failure message.
  */
 
-extern int PRINTF_LIKE(5, 6)
-  tvec_claim(struct tvec_state */*tv*/, int /*ok*/,
-            const char */*file*/, unsigned /*lno*/,
-            const char */*msg*/, ...);
+extern PRINTF_LIKE(5, 6)
+  int tvec_claim(struct tvec_state */*tv*/, int /*ok*/,
+                const char */*file*/, unsigned /*lno*/,
+                const char */*msg*/, ...);
 extern int tvec_claim_v(struct tvec_state */*tv*/, int /*ok*/,
                        const char */*file*/, unsigned /*lno*/,
                        const char */*msg*/, va_list */*ap*/);
@@ -1106,6 +1126,9 @@ struct tvec_benchctx {
   const struct tvec_benchenv *be;      /* environment configuration */
   struct bench_state *bst;             /* benchmark state */
   double dflt_target;                  /* default time in seconds */
+  unsigned f;                          /* flags */
+#define TVBF_SETTRG 1u                 /*   set `@target' */
+#define TVBF_SETMASK (TVBF_SETTRG))    /*   mask of @TVBF_SET...@ */
   void *subctx;                                /* subsidiary environment context */
 };
 
@@ -1161,12 +1184,14 @@ extern void tvec_benchreport
 
 struct tvec_remotecomms {
   int infd, outfd;                     /* input and output descriptors */
-  dbuf bin, bout;                      /* input and output buffers */
+  dbuf bout;                           /* output buffer */
+  unsigned char *bin;                  /* input buffer */
+  size_t binoff, binlen, binsz;                /* input offset, length, and size */
+  size_t t;                            /* temporary offset */
   unsigned f;                          /* flags */
 #define TVRF_BROKEN 0x0001u            /*   communications have failed */
-                                       /*   bits 8--15 for upper layer */
 };
-#define TVEC_REMOTECOMMS_INIT { -1, -1, DBUF_INIT, DBUF_INIT, 0 }
+#define TVEC_REMOTECOMMS_INIT { -1, -1, DBUF_INIT, 0, 0, 0, 0, 0, 0 }
 
 struct tvec_remotectx {
   struct tvec_state *tv;               /* test vector state */
@@ -1183,6 +1208,11 @@ struct tvec_remotectx {
 #define TVRCN_DEMAND 0x0100u           /*     connect on demand */
 #define TVRCN_FORCE 0x0200u            /*     force reconnection */
 #define TVRF_MUFFLE 0x0400u            /*   muffle child stderr */
+#define TVRF_SETEXIT 0x0800u           /*   set `@exit' */
+#define TVRF_SETPRG 0x1000u            /*   set `@progress' */
+#define TVRF_SETRCN 0x2000u            /*   set `@reconnect' */
+#define TVRF_SETMASK (TVRF_SETEXIT | TVRF_SETPRG | TVRF_SETRCN)
+                                       /*   mask of @TVTF_SET...@ */
 };
 
 typedef int tvec_connectfn(pid_t */*kid_out*/, int */*infd_out*/,
@@ -1241,21 +1271,69 @@ extern tvec_envteardownfn tvec_remoteteardown;
     tvec_remoteafter,                                                  \
     tvec_remoteteardown }
 
-extern int tvec_setprogress(const char */*status*/);
+extern PRINTF_LIKE(1, 2) int tvec_setprogress(const char */*status*/, ...);
+extern int tvec_setprogress_v(const char */*status*/, va_list */*ap*/);
 
 extern int tvec_remoteserver(int /*infd*/, int /*outfd*/,
                             const struct tvec_config */*config*/);
 
 extern tvec_connectfn tvec_fork, tvec_exec;
 
-#define TVEC_REMOTEFORK( subenv, tests)                                        \
+#define TVEC_REMOTEFORK(subenv, tests)                                 \
        TVEC_REMOTEENV, { tvec_fork, subenv }, { tests }
 
 #define TVEC_REMOTEEXEC(subenv, args)                                  \
        TVEC_REMOTEENV, { tvec_exec, subenv }, { args }
 
+/*----- Timeouts ----------------------------------------------------------*/
+
+struct tvec_timeoutenv {
+  struct tvec_env _env;
+  unsigned timer;
+  double t;
+  const struct tvec_env *env;
+};
+
+struct tvec_timeoutctx {
+  const struct tvec_timeoutenv *te;
+  unsigned timer;
+  double t;
+  unsigned f;                          /* flags */
+#define TVTF_SETTMO 1u                 /*   set `@timeout' */
+#define TVTF_SETMASK (TVTF_SETTMO)     /*   mask of @TVTF_SET...@ */
+  void *subctx;
+};
+
+extern tvec_envsetupfn tvec_timeoutsetup;
+extern tvec_envsetfn tvec_timeoutset;
+extern tvec_envbeforefn tvec_timeoutbefore;
+extern tvec_envrunfn tvec_timeoutrun;
+extern tvec_envafterfn tvec_timeoutafter;
+extern tvec_envteardownfn tvec_timeoutteardown;
+#define TVEC_TIMEOUTENV                                                        \
+       { sizeof(struct tvec_timeoutctx),                               \
+         tvec_timeoutsetup,                                            \
+         tvec_timeoutset,                                              \
+         tvec_timeoutbefore,                                           \
+         tvec_timeoutrun,                                              \
+         tvec_timeoutafter,                                            \
+         tvec_timeoutteardown }
+#define TVEC_TIMEOUTINIT(timer, t) TVEC_TIMEOUTENV, timer, t
+
 /*----- Output functions --------------------------------------------------*/
 
+/* --- @tvec_strlevel@ --- *
+ *
+ * Arguments:  @unsigned level@ = level code
+ *
+ * Returns:    A human-readable description.
+ *
+ * Use:                Converts a level code into something that you can print in a
+ *             message.
+ */
+
+extern  const char *tvec_strlevel(unsigned /*level*/);
+
 /* --- @tvec_report@, @tvec_report_v@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
@@ -1267,9 +1345,9 @@ extern tvec_connectfn tvec_fork, tvec_exec;
  *             @TVLEV_ERR@ or higher force a nonzero exit code.
  */
 
-extern void PRINTF_LIKE(3, 4)
-  tvec_report(struct tvec_state */*tv*/, unsigned /*level*/,
-             const char */*msg*/, ...);
+extern PRINTF_LIKE(3, 4)
+  void tvec_report(struct tvec_state */*tv*/, unsigned /*level*/,
+                  const char */*msg*/, ...);
 extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/,
                          const char */*msg*/, va_list */*ap*/);
 
@@ -1278,7 +1356,7 @@ extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/,
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @const char *msg@, @va_list ap@ = error message
  *
- * Returns:    The @tvec_error@ function returns @-1@ as a trivial
+ * Returns:    The @tvec_error@ function returns %$-1$% as a trivial
  *             convenience; @tvec_notice@ does not return a value.
  *
  * Use:                Report an error or a notice.  Errors are distinct from test
@@ -1288,10 +1366,10 @@ extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/,
  *             category.
  */
 
-extern int PRINTF_LIKE(2, 3)
-  tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...);
-extern void PRINTF_LIKE(2, 3)
-  tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...);
+extern PRINTF_LIKE(2, 3)
+  int tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...);
+extern PRINTF_LIKE(2, 3)
+  void tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...);
 
 /* --- @tvec_humanoutput@ --- *
  *
@@ -1377,7 +1455,7 @@ extern struct tvec_output *tvec_dfltout(FILE */*fp*/);
  *             @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.
+ * Returns:    Zero on success, %$-1$% on failure.
  *
  * Use:                Serialize a collection of register values.
  *
@@ -1400,7 +1478,7 @@ extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/,
  *             @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.
+ * Returns:    Zero on success, %$-1$% on failure.
  *
  * Use:                Deserialize a collection of register values.
  *
@@ -1435,26 +1513,13 @@ extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/,
  *     current line number.
  */
 
-/* --- @tvec_skipspc@ --- *
- *
- * Arguments:  @struct tvec_state *tv@ = test-vector state
- *
- * Returns:    ---
- *
- * Use:                Advance over any whitespace characters other than newlines.
- *             This will stop at `;', end-of-file, or any other kind of
- *             non-whitespace; and it won't consume a newline.
- */
-
-extern void tvec_skipspc(struct tvec_state */*tv*/);
-
 /* --- @tvec_syntax@, @tvec_syntax_v@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @int ch@ = the character found (in @fgetc@ format)
  *             @const char *expect@, @va_list ap@ = what was expected
  *
- * Returns:    @-1@
+ * Returns:    %$-1$%.
  *
  * Use:                Report a syntax error quoting @ch@ and @expect@.  If @ch@ is
  *             a newline, then back up so that it can be read again (e.g.,
@@ -1462,18 +1527,44 @@ extern void tvec_skipspc(struct tvec_state */*tv*/);
  *             advance the line number).
  */
 
-extern int PRINTF_LIKE(3, 4)
-  tvec_syntax(struct tvec_state */*tv*/, int /*ch*/,
-             const char */*expect*/, ...);
+extern PRINTF_LIKE(3, 4)
+  int tvec_syntax(struct tvec_state */*tv*/, int /*ch*/,
+                 const char */*expect*/, ...);
 extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/,
                         const char */*expect*/, va_list */*ap*/);
 
+/* --- @tvec_dupreg@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *name@ = register or pseudoregister name
+ *
+ * Returns:    %$-1$%.
+ *
+ * Use:                Reports an error that the register or pseudoregister has been
+ *             assigned already in the current test.
+ */
+
+extern int tvec_dupreg(struct tvec_state */*tv*/, const char */*name*/);
+
+/* --- @tvec_skipspc@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    ---
+ *
+ * Use:                Advance over any whitespace characters other than newlines.
+ *             This will stop at `;', end-of-file, or any other kind of
+ *             non-whitespace; and it won't consume a newline.
+ */
+
+extern void tvec_skipspc(struct tvec_state */*tv*/);
+
 /* --- @tvec_flushtoeol@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @unsigned f@ = flags (@TVFF_...@)
  *
- * Returns:    Zero on success, @-1@ on error.
+ * Returns:    Zero on success, %$-1$% on error.
  *
  * Use:                Advance to the start of the next line, consuming the
  *             preceding newline.
@@ -1493,7 +1584,7 @@ extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/);
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *
- * Returns:    Zero if there is a next token which can be read; @-1@ if no
+ * Returns:    Zero if there is a next token which can be read; %$-1$% if no
  *             token is available.
  *
  * Use:                Advance to the next whitespace-separated token, which may be
@@ -1507,21 +1598,21 @@ extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/);
  *
  *             If this function returns zero, then the next character in the
  *             file begins a suitable token which can be read and
- *             processed.  If it returns @-1@ then there is no such token,
+ *             processed.  If it returns %$-1$% then there is no such token,
  *             and the file position is left correctly.  The line number
  *             count is updated appropriately.
  */
 
 extern int tvec_nexttoken(struct tvec_state */*tv*/);
 
-/* --- @tvec_readword@ --- *
+/* --- @tvec_readword@, @tvec_readword_v@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @dstr *d@ = string to append the word to
  *             @const char *delims@ = additional delimiters to stop at
  *             @const char *expect@, @va_list ap@ = what was expected
  *
- * Returns:    Zero on success, @-1@ on failure.
+ * Returns:    Zero on success, %$-1$% on failure.
  *
  * Use:                A `word' consists of characters other than whitespace, null
  *             characters, and other than those listed in @delims@;
@@ -1530,8 +1621,8 @@ extern int tvec_nexttoken(struct tvec_state */*tv*/);
  *             include `;' in @delims@.  This is a common behaviour.)
  *
  *             If there is no word beginning at the current file position,
- *             then return @-1@; furthermore, if @expect@ is not null, then
- *             report an appropriate error via @tvec_syntax@.
+ *             then return %$-1$%; furthermore, if @expect@ is not null,
+ *             then report an appropriate error via @tvec_syntax@.
  *
  *             Otherwise, the word is accumulated in @d@ and zero is
  *             returned; if @d@ was not empty at the start of the call, the
@@ -1542,9 +1633,9 @@ extern int tvec_nexttoken(struct tvec_state */*tv*/);
  *             terminated.
  */
 
-extern int PRINTF_LIKE(4, 5)
-  tvec_readword(struct tvec_state */*tv*/, dstr */*d*/,
-               const char */*delims*/, const char */*expect*/, ...);
+extern PRINTF_LIKE(4, 5)
+  int 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*/);
@@ -1755,6 +1846,8 @@ extern int tvec_claimeq_float(struct tvec_state */*tv*/,
 #define TVEC_CLAIMEQ_FLOAT(tv, f0, f1)                                 \
        (tvec_claimeq_float(tv, f0, f1, __FILE__, __LINE__, #f0 " /= " #f1))
 
+extern const struct tvec_floatinfo tvflt_finite, tvflt_nonneg;
+
 /*----- Enumerated types --------------------------------------------------*/
 
 /* An enumeration describes a set of values of some underlying type, each of
index a7edc37d6790baba6e07e95cdcdc03b38caa6a2c..0bb5622bfe1fd0b0b76515736441342bd759fd30 100644 (file)
@@ -61,8 +61,7 @@ typedef struct trace_opt {
  * Use:                Reports a message to the trace output.
  */
 
-extern void PRINTF_LIKE(2, 3)
-  trace(unsigned /*l*/, const char */*f*/, ...);
+extern PRINTF_LIKE(2, 3) void trace(unsigned /*l*/, const char */*f*/, ...);
 
 /* --- @trace_block@ --- *
  *
index 4266cc2f6bafb2158120917a5ef0df046976f8b4..19368775f93f216dfc710bb396eb20ae06307711 100644 (file)
@@ -50,8 +50,7 @@
  * Use:                Reports an error.
  */
 
-extern void PRINTF_LIKE(1, 2)
-  moan(const char *f, ...);
+extern PRINTF_LIKE(1, 2) void moan(const char *f, ...);
 
 /* --- @die@ --- *
  *
@@ -65,8 +64,7 @@ extern void PRINTF_LIKE(1, 2)
  *             permanent.
  */
 
-extern void PRINTF_LIKE(2, 3) NORETURN
-  die(int status, const char *f, ...);
+extern PRINTF_LIKE(2, 3) NORETURN void die(int status, const char *f, ...);
 
 /*----- That's all, folks -------------------------------------------------*/
 
index 4d1d61a0f4f18393573c23d65e7dfbf1b48e6eb9..28c7dd453916dac73e67ba3b6076407db452cb6e 100644 (file)
@@ -36,7 +36,7 @@
 
 #include <limits.h>
 #include <stddef.h>
-#if __STDC_VERSION__ >= 199900l
+#if __STDC_VERSION__ >= 199901
 #  include <stdint.h>
 #endif
 
@@ -184,21 +184,21 @@ typedef unsigned char octet, uint8;
 /* --- List macros --- */
 
 #ifdef HAVE_UINT64
-#  define  DOUINTCONV(_)                                               \
-     _(8, 8, 8)                                                                \
-     _(16, 16, 16) _(16, 16_L, 16l) _(16, 16_B, 16b)                   \
-     _(24, 24, 24) _(24, 24_L, 24l) _(24, 24_B, 24b)                   \
-     _(32, 32, 32) _(32, 32_L, 32l) _(32, 32_B, 32b)                   \
+#  define  DOUINTCONV_64(_)                                            \
      _(64, 64, 64) _(64, 64_L, 64l) _(64, 64_B, 64b)
-#  define DOUINTSZ(_) _(8) _(16) _(24) _(32) _(64)
+#  define  DOUINTSZ_64(_) _(64)
 #else
-#  define  DOUINTCONV(_)                                               \
+#  define DOUINTCONV_64(_)
+#  define DOUINTSZ_64(_)
+#endif
+
+#define  DOUINTCONV(_)                                                 \
      _(8, 8, 8)                                                                \
      _(16, 16, 16) _(16, 16_L, 16l) _(16, 16_B, 16b)                   \
      _(24, 24, 24) _(24, 24_L, 24l) _(24, 24_B, 24b)                   \
-     _(32, 32, 32) _(32, 32_L, 32l) _(32, 32_B, 32b)
-#  define DOUINTSZ(_) _(8) _(16) _(24) _(32)
-#endif
+     _(32, 32, 32) _(32, 32_L, 32l) _(32, 32_B, 32b)                   \
+     DOUINTCONV_64(_)
+#define DOUINTSZ(_) _(8) _(16) _(24) _(32) _DOUINTSZ_64(_)
 
 /* --- Type coercions --- */
 
index 0f06dd38ed4d01062e79e13283c34142f38be17f..8c0f87c903f44320771a30c3aedb13a992cff697 100644 (file)
@@ -45,8 +45,8 @@
 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*/, ...);
+  PRINTF_LIKE(3, 4)
+    int (*nputf)(void */*out*/, size_t /*maxsz*/, const char */*p*/, ...);
 };
 
 extern const struct gprintf_ops file_printops;
@@ -82,9 +82,9 @@ extern int vgprintf(const struct gprintf_ops */*ops*/, void */*out*/,
  *             for @dstr_putf@, for example.
  */
 
-extern int PRINTF_LIKE(3, 4)
-  gprintf(const struct gprintf_ops */*ops*/, void */*out*/,
-         const char */*p*/, ...);
+extern PRINTF_LIKE(3, 4)
+  int gprintf(const struct gprintf_ops */*ops*/, void */*out*/,
+             const char */*p*/, ...);
 
 /* --- @gprintf_memputf@ --- *
  *