/*----- 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.
#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 -------------------------------------------------*/
* 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
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
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 -------------------------------------------------*/
/* --- @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
*
* 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@ --- *
*
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.
*
*/
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: ---
*
* 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.
*/
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.
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.
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
*
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.
*
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.
*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.
*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.
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
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
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.
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.
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)
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
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
*
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
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)
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.
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.
#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.
#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 -------------------------------------------------*/
#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))
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
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.
*
*/
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: ---
*
*/
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.
*/
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.
*/
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.
*/
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
*
*/
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.
*
*/
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.
*/
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.
*/
#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.
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
*/
#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
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.
*/
#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
*
*/
#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.
*/
#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.
*/
#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.
*/
#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.
*/
#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.
*/
#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).
*/
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)
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
*
*/
#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 -------------------------------------------------*/
* 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@ --- *
*
#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;
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;
#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;
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
* 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;
/*----- 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[] = {
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;
/*----- 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();
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[] = {
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
};
$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'
])])
###--------------------------------------------------------------------------
*
* 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.
* 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 };
{ "@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);
}
/* 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);
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. */
/*----- 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: ---
/*----- 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)) {
}
}
+/* --- @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);
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) ||
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;
/*----- 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;
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)
{
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;
}
}
+/* --- @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;
}
}
+/* --- @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 };
}
}
+/* --- @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, ...)
{
va_end(ap);
return (rc);
}
+
int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
const char *expect, va_list *ap)
{
/*----- 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)
{
}
}
+/* --- @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;
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)) {
}
}
+/* --- @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;
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;
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;
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;
}
}
+/* --- @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;
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 },
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;
}
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);
/*----- 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)
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);
/*----- 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)
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)
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)
{
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 {
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)
* 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 { \
#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 { \
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
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@ --- *
#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 */
#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 */
}
}
-/* --- @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@
* 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.
*
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
{
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);
}
{
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@ --- *
* 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)
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>");
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');
*
* 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,
{
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@ --- *
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);
}
#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;
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)
}
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;
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) {
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;
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;
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;
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 {
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 ----------------------------------------------*/
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);
}
}
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);
}
}
}
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);
}
}
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);
}
}
}
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);
}
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) {
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;
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);
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)) {
end:
if (f&ERF_CLOSE) {
lbuf_close(&r->errbuf);
- close(r->errfd);
+ close(r->errfd); r->errfd = -1;
}
return (rc);
}
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);
}
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))
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; }
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");
}
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;
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);
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 --------------------------------------------------------*/
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;
--- /dev/null
+/* -*-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 -------------------------------------------------*/
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
* -> @tvec_mismatch@
* -> output @dumpreg@
* -> type @dump@
- * -> env @after@
* -> output @etest@
+ * -> env @after@
* finally
* -> output @egroup@
* -> env @teardown@
*/
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 */
};
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 */
};
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 */
/* 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 -------------------------------------------------*/
/* 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.
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*/);
#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 */
/* 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 -------------------------------------------------*/
* @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
* @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 `%|-|%',
* 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
* @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@;
* 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*/);
* 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
* 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*/);
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
* 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*/);
#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.
*/
* 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*/);
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 */
};
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 */
#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*/,
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
* @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*/);
* 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
* 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@ --- *
*
* @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.
*
* @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.
*
* 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.,
* 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.
*
* 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
*
* 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@;
* 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
* 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*/);
#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
* 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@ --- *
*
* 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@ --- *
*
* 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 -------------------------------------------------*/
#include <limits.h>
#include <stddef.h>
-#if __STDC_VERSION__ >= 199900l
+#if __STDC_VERSION__ >= 199901
# include <stdint.h>
#endif
/* --- 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 --- */
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;
* 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@ --- *
*