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