dnl Libraries.
mdw_ORIG_LIBS=$LIBS LIBS=$MLIB_LIBS
+AC_SEARCH_LIBS([sqrt], [m])
AC_SEARCH_LIBS([socket], [socket])
AC_SEARCH_LIBS([gethostbyname], [nsl resolv])
MLIB_LIBS=$LIBS LIBS=$mdw_ORIG_LIBS
esac
AM_CONDITIONAL([WITH_ADNS], [test "$use_adns" = yes])
+dnl--------------------------------------------------------------------------
+dnl Timers.
+
+AC_CHECK_HEADERS([linux/perf_event.h])
+
+mdw_ORIG_LIBS=$LIBS LIBS=$MLIB_LIBS
+AC_SEARCH_LIBS([clock_gettime], [rt])
+MLIB_LIBS=$LIBS LIBS=$mdw_ORIG_LIBS
+if test "$ac_cv_search_clock_gettime" != no; then
+ AC_DEFINE([HAVE_CLOCK_GETTIME], [1],
+ [Define if you have the \`clock_gettime' function.])
+fi
+
dnl--------------------------------------------------------------------------
dnl Python (used for testing).
mv $@.new $@
endif
-check_PROGRAMS += t/crc32.t
-t_crc32_t_SOURCES = t/crc32-test.c
-t_crc32_t_CPPFLAGS = $(TEST_CPPFLAGS)
-t_crc32_t_LDFLAGS = -static
-
EXTRA_DIST += t/crc32.tests
## Universal hashing.
-o$@.new && mv $@.new $@
endif
-check_PROGRAMS += t/unihash.t
-t_unihash_t_SOURCES = t/unihash-test.c
-t_unihash_t_CPPFLAGS = $(TEST_CPPFLAGS)
-t_unihash_t_LDFLAGS = -static
-
EXTRA_DIST += t/unihash-testgen.py
+## Test program.
+check_PROGRAMS += t/hash.t
+t_hash_t_SOURCES = t/hash-test.c
+t_hash_t_CPPFLAGS = $(TEST_CPPFLAGS)
+t_hash_t_LDFLAGS = -static
+
###----- That's all, folks --------------------------------------------------
+++ /dev/null
-/* -*-c-*-
- *
- * Test driver for CRC
- *
- * (c) 2009 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 <stdio.h>
-
-#include "crc32.h"
-#include "testrig.h"
-
-/*----- Main code ---------------------------------------------------------*/
-
-static int verify(dstr *v)
-{
- uint32 h, hh;
- size_t n;
- int i, c;
- const char *p;
- int ok = 1;
-
- static const int step[] = { 0, 1, 5, 6, 7, 8, 23, -1 };
-
- /* --- Set up for using this key --- */
-
- h = *(uint32 *)v[1].buf;
-
- /* --- Hash the data a lot --- */
-
- for (i = 0; step[i] >= 0; i++) {
- c = step[i];
- if (!c)
- hh = crc32(0, v[0].buf, v[0].len);
- else {
- hh = 0;
- p = v[0].buf;
- n = v[0].len;
- while (n) {
- if (c > n) c = n;
- hh = crc32(hh, p, c);
- p += c;
- n -= c;
- }
- }
- if (h != hh) {
- ok = 0;
- fprintf(stderr, "\ncrc32 failed\n");
- fprintf(stderr, " data = %s\n", v[0].buf);
- fprintf(stderr, " step = %d\n", step[i]);
- fprintf(stderr, " expected = %08lx\n", (unsigned long)h);
- fprintf(stderr, " computed = %08lx\n", (unsigned long)hh);
- }
- }
- return (ok);
-}
-
-static const test_chunk tests[] = {
- { "hash", verify, { &type_string, &type_uint32 } },
- { 0, 0, { 0 } }
-};
-
-int main(int argc, char *argv[])
-{
- test_run(argc, argv, tests, "crc32.in");
- return (0);
-}
-
-/*----- That's all, folks -------------------------------------------------*/
-### -*-conf-*-
-### crc32 tests
-
-hash {
- "" 0;
- "foo" 0x8c736521;
- "anything you like" 0x2c090211;
- "an exaple test string" 0x686a05aa;
- "The quick brown fox jumps over the lazy dog." 0x519025e9;
- "A man, a plan, a canal: Panama!" 0x27f3faee;
-}
+;;; -*-conf-*-
+;;; crc32 tests
+
+[crc32]
+
+m = ""
+h = 0
+
+m = "foo"
+h = 0x8c736521
+
+m = "anything you like"
+h = 0x2c090211
+
+m = "an exaple test string"
+h = 0x686a05aa
+
+m = "The quick brown fox jumps over the lazy dog."
+h = 0x519025e9
+
+m = "A man, a plan, a canal: Panama!"
+h = 0x27f3faee
--- /dev/null
+/* -*-c-*-
+ *
+ * Test driver for universal hashing
+ *
+ * (c) 2009 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 "crc32.h"
+#include "unihash.h"
+#include "tvec.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+enum {
+ RH, NROUT,
+ RK = NROUT, RM, NREG
+};
+
+struct step { size_t s; };
+
+static void test_crc32(const struct tvec_reg *in, struct tvec_reg *out,
+ void *ctx)
+{
+ const struct step *step = ctx;
+ const unsigned char *p = in[RM].v.bytes.p; size_t sz = in[RM].v.bytes.sz;
+ uint32 h;
+
+ if (!step)
+ out[RH].v.u = crc32(0, p, sz);
+ else {
+ for (h = 0; sz > step->s; p += step->s, sz -= step->s)
+ h = crc32(h, p, step->s);
+ out[RH].v.u = crc32(h, p, sz);
+ }
+}
+
+static void bench_crc32(const struct tvec_reg *in, struct tvec_reg *out,
+ void *ctx)
+ { crc32(0, in[RM].v.bytes.p, in[RM].v.bytes.sz); }
+
+static void test_unihash(const struct tvec_reg *in, struct tvec_reg *out,
+ void *ctx)
+{
+ const struct step *step = ctx;
+ unihash_info ui;
+ const unsigned char *p = in[RM].v.bytes.p; size_t sz = in[RM].v.bytes.sz;
+ uint32 h;
+
+ unihash_setkey(&ui, in[RK].v.u);
+ if (!step)
+ out[RH].v.u = unihash(&ui, p, sz);
+ else {
+ for (h = UNIHASH_INIT(&ui); sz > step->s; p += step->s, sz -= step->s)
+ h = unihash_hash(&ui, h, p, step->s);
+ out[RH].v.u = unihash_hash(&ui, h, p, sz);
+ }
+}
+
+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)
+ { unihash_setkey(ctx, 0); return (0); }
+
+static void run_step(struct tvec_state *tv)
+{
+ 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);
+ tvec_check(tv, "whole buffer");
+
+ for (i = 0; i < N(steps); i++) {
+ step.s = steps[i];
+ tv->test->fn(tv->in, tv->out, &step);
+ tvec_check(tv, "step = %lu", (unsigned long)steps[i]);
+ }
+}
+
+static const struct tvec_regdef unihash_regs[] = {
+ { "k", RK, &tvty_uint, 0, { &tvrange_u32 } },
+ { "m", RM, &tvty_bytes, 0 },
+ { "h", RH, &tvty_uint, 0, { &tvrange_u32 } },
+ { 0, 0, 0, 0 }
+};
+
+static const struct tvec_regdef crc32_regs[] = {
+ { "m", RM, &tvty_bytes, 0 },
+ { "h", RH, &tvty_uint, 0, { &tvrange_u32 } },
+ { 0, 0, 0, 0 }
+};
+
+static const struct tvec_regdef bench_regs[] = {
+ { "msz", RM, &tvty_buffer, TVRF_ID },
+ { 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_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 }
+};
+
+static const struct tvec_info testinfo =
+ { tests, NROUT, NREG, sizeof(struct tvec_reg) };
+
+int main(int argc, char *argv[])
+ { return (tvec_main(argc, argv, &testinfo, 0)); }
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+;;; -*-conf-*-
+;;; hash benchmarking
+
+[crc32-bench]
+
+msz = 16
+
+msz = 256
+
+msz = 1 kB
+
+msz = 16 kB
+
+msz = 1 MB
+
+[unihash-bench]
+
+msz = 16
+
+msz = 256
+
+msz = 1 kB
+
+msz = 16 kB
+
+msz = 1 MB
+++ /dev/null
-/* -*-c-*-
- *
- * Test driver for universal hashing
- *
- * (c) 2009 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 <stdio.h>
-
-#include "unihash.h"
-#include "testrig.h"
-
-/*----- Main code ---------------------------------------------------------*/
-
-static int verify(dstr *v)
-{
- unihash_info ui;
- uint32 k;
- uint32 h, hh;
- size_t n;
- int i, c;
- const char *p;
- int ok = 1;
-
- static const int step[] = { 0, 1, 5, 6, 7, 8, 23, -1 };
-
- /* --- Set up for using this key --- */
-
- k = *(uint32 *)v[0].buf;
- h = *(uint32 *)v[2].buf;
- unihash_setkey(&ui, k);
-
- /* --- Hash the data a lot --- */
-
- for (i = 0; step[i] >= 0; i++) {
- c = step[i];
- if (!c)
- hh = unihash(&ui, v[1].buf, v[1].len);
- else {
- hh = UNIHASH_INIT(&ui);
- p = v[1].buf;
- n = v[1].len;
- while (n) {
- if (c > n) c = n;
- hh = unihash_hash(&ui, hh, p, c);
- p += c;
- n -= c;
- }
- }
- if (h != hh) {
- ok = 0;
- fprintf(stderr, "\nunihash failed\n");
- fprintf(stderr, " key = %08lx\n", (unsigned long)k);
- fprintf(stderr, " data = %s\n", v[1].buf);
- fprintf(stderr, " step = %d\n", step[i]);
- fprintf(stderr, " expected = %08lx\n", (unsigned long)h);
- fprintf(stderr, " computed = %08lx\n", (unsigned long)hh);
- }
- }
- return (ok);
-}
-
-static const test_chunk tests[] = {
- { "hash", verify, { &type_uint32, &type_string, &type_uint32 } },
- { 0, 0, { 0 } }
-};
-
-int main(int argc, char *argv[])
-{
- test_run(argc, argv, tests, "unihash.in");
- return (0);
-}
-
-/*----- That's all, folks -------------------------------------------------*/
def hashtest(k, m):
h = k
for ch in m: h = gfmul(h ^ ord(ch), k)
- print(' 0x%08x "%s" 0x%08x;' % (k, m, h))
+ print('')
+ print('k = 0x%08x' % k)
+ print('m = "%s"' % m)
+ print('h = 0x%08x' % h)
print('''\
-### Test vectors for universal hashing
-### [generated]
+;;; -*-conf-*- Test vectors for universal hashing
+;;; [generated]
-hash {''')
+[unihash]''')
for k, m in [(0x00000000, 'anything you like'),
(0x12345678, 'an exaple test string'),
(0xb8a171f0, 'The quick brown fox jumps over the lazy dog.'),
+ (0xcc825ed5, 'Jackdaws love my big sphinx of quartz.'),
+ (0x16e98e46, 'Waltz, bad nymph, for quick jigs vex!'),
(0x2940521b, 'A man, a plan, a canal: Panama!')]:
hashtest(k, m)
for i in xrange(48):
hashtest(k, "If we don't succeed, we run the risk of failure.")
k = gfmul(k, m)
-
-print('}')
AT_SETUP([hash: crc32])
AT_KEYWORDS([hash crc32])
-AT_CHECK([BUILDDIR/t/crc32.t -f SRCDIR/t/crc32.tests],
- [0], [ignore], [ignore])
+AT_CHECK([BUILDDIR/t/hash.t SRCDIR/t/crc32.tests], [0], [ignore], [ignore])
AT_CLEANUP
AT_KEYWORDS([hash unihash])
$PYTHON SRCDIR/t/unihash-testgen.py >unihash.tests
-AT_CHECK([BUILDDIR/t/unihash.t -f unihash.tests],
- [0], [ignore], [ignore])
+AT_CHECK([BUILDDIR/t/hash.t unihash.tests], [0], [ignore], [ignore])
AT_CLEANUP
}
DOUINTCONV(BUF_GETU_)
+/* --- @buf_getk64{,l,b}@ --- *
+ *
+ * Arguments: @buf *b@ = pointer to a buffer block
+ * @kludge64 *w@ = where to put the word
+ *
+ * Returns: Zero if OK, or nonzero if there wasn't a word there.
+ *
+ * Use: Gets a word of appropriate size and order from a buffer.
+ */
+
+int buf_getk64(buf *b, kludge64 *w)
+{
+ if (BENSURE(b, 8)) return (-1);
+ LOAD64_(*w, b->p); BSTEP(b, 8); return (0);
+}
+
+int buf_getk64l(buf *b, kludge64 *w)
+{
+ if (BENSURE(b, 8)) return (-1);
+ LOAD64_L_(*w, b->p); BSTEP(b, 8); return (0);
+}
+
+int buf_getk64b(buf *b, kludge64 *w)
+{
+ if (BENSURE(b, 8)) return (-1);
+ LOAD64_B_(*w, b->p); BSTEP(b, 8); return (0);
+}
+
/* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- *
*
* Arguments: @buf *b@ = pointer to a buffer block
}
DOUINTCONV(BUF_PUTU_)
+/* --- @buf_putk64{,l,b}@ --- *
+ *
+ * Arguments: @buf *b@ = pointer to a buffer block
+ * @kludge64 w@ = word to write
+ *
+ * Returns: Zero if OK, or nonzero if there wasn't enough space
+ *
+ * Use: Gets a word of appropriate size and order from a buffer.
+ */
+
+int buf_putk64(buf *b, kludge64 w)
+{
+ if (BENSURE(b, 8)) return (-1);
+ STORE64_(b->p, w); BSTEP(b, 8); return (0);
+}
+
+int buf_putk64l(buf *b, kludge64 w)
+{
+ if (BENSURE(b, 8)) return (-1);
+ STORE64_L_(b->p, w); BSTEP(b, 8); return (0);
+}
+
+int buf_putk64b(buf *b, kludge64 w)
+{
+ if (BENSURE(b, 8)) return (-1);
+ STORE64_B_(b->p, w); BSTEP(b, 8); return (0);
+}
+
/* --- @findz@ --- *
*
* Arguments: @buf *b@ = pointer to a buffer block
extern int buf_getu##w(buf */*b*/, uint##n */*w*/);
DOUINTCONV(BUF_DECL_GETU_)
+/* --- @buf_getk64{,l,b}@ --- *
+ *
+ * Arguments: @buf *b@ = pointer to a buffer block
+ * @kludge64 *w@ = where to put the word
+ *
+ * Returns: Zero if OK, or nonzero if there wasn't a word there.
+ *
+ * Use: Gets a word of appropriate size and order from a buffer.
+ */
+
+extern int buf_getk64(buf */*b*/, kludge64 */*w*/);
+extern int buf_getk64l(buf */*b*/, kludge64 */*w*/);
+extern int buf_getk64b(buf */*b*/, kludge64 */*w*/);
+
/* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- *
*
* Arguments: @buf *b@ = pointer to a buffer block
extern int buf_putu##w(buf */*b*/, uint##n /*w*/);
DOUINTCONV(BUF_DECL_PUTU_)
+/* --- @buf_putk64{,l,b}@ --- *
+ *
+ * Arguments: @buf *b@ = pointer to a buffer block
+ * @kludge64 w@ = word to write
+ *
+ * Returns: Zero if OK, or nonzero if there wasn't enough space
+ *
+ * Use: Gets a word of appropriate size and order from a buffer.
+ */
+
+extern int buf_putk64(buf */*b*/, kludge64 /*w*/);
+extern int buf_putk64l(buf */*b*/, kludge64 /*w*/);
+extern int buf_putk64b(buf */*b*/, kludge64 /*w*/);
+
/* --- @buf_getmem{8,{16,24,32,64}{,l,b},z} --- *
*
* Arguments: @buf *b@ = pointer to a buffer block
autotest_TESTS += $(top_srcdir)/hash/tests.at
autotest_TESTS += $(top_srcdir)/struct/tests.at
autotest_TESTS += $(top_srcdir)/sys/tests.at
+autotest_TESTS += $(top_srcdir)/test/tests.at
autotest_TESTS += $(top_srcdir)/utils/tests.at
###----- That's all, folks --------------------------------------------------
###--------------------------------------------------------------------------
### Component files.
-## Testing.
+## Benchmarking.
+pkginclude_HEADERS += bench.h
+libtest_la_SOURCES += bench.c
+
+## Old `testrig' testing framework.
pkginclude_HEADERS += testrig.h
libtest_la_SOURCES += testrig.c
LIBMANS += testrig.3
+## New `tvec' testing framework.
+pkginclude_HEADERS += tvec.h
+libtest_la_SOURCES += tvec-core.c
+libtest_la_SOURCES += tvec-output.c
+libtest_la_SOURCES += tvec-types.c
+libtest_la_SOURCES += tvec-main.c
+
+check_PROGRAMS += t/tvec.t
+t_tvec_t_SOURCES = t/tvec-test.c
+t_tvec_t_CPPFLAGS = $(TEST_CPPFLAGS)
+t_tvec_t_LDFLAGS = -static
+
###----- That's all, folks --------------------------------------------------
--- /dev/null
+/* -*-c-*-
+ *
+ * Benchmarking support
+ *
+ * (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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "alloc.h"
+#include "bench.h"
+#include "bits.h"
+#include "linreg.h"
+#include "macros.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+struct timer {
+ struct bench_timer _t;
+ const struct timer_ops *clkops, *cyops;
+ union { int fd; } u_cy;
+};
+
+struct timer_ops {
+ void (*now)(struct bench_time *t_out, struct timer *t);
+ void (*teardown)(struct timer *t);
+};
+
+/*----- Preliminaries -----------------------------------------------------*/
+
+#define NS_PER_S 1000000000
+
+static void PRINTF_LIKE(1, 2) debug(const char *fmt, ...)
+{
+ const char *p;
+ va_list ap;
+
+ p = getenv("MLIB_BENCH_DEBUG");
+ if (p && *p != 'n' && *p != '0') {
+ va_start(ap, fmt);
+ fputs("mLib BENCH: ", stderr);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+ va_end(ap);
+ }
+}
+
+/*----- The null clock ----------------------------------------------------*/
+
+static void null_now(struct bench_time *t_out, struct timer *t) { ; }
+static void null_teardown(struct timer *t) { ; }
+static const struct timer_ops null_ops = { null_now, null_teardown };
+
+static int null_cyinit(struct timer *t)
+ { t->cyops = &null_ops; return (0); }
+
+#define NULL_CYENT { "null", null_cyinit },
+
+/*----- Linux performance counters ----------------------------------------*/
+
+#if defined(HAVE_LINUX_PERF_EVENT_H) && defined(HAVE_UINT64)
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <linux/perf_event.h>
+#include <asm/unistd.h>
+
+static void perfevent_now(struct bench_time *t_out, struct timer *t)
+{
+ ssize_t n;
+
+ n = read(t->u_cy.fd, &t_out->cy.i, sizeof(t_out->cy.i));
+ if (n != sizeof(t_out->cy.i)) {
+ debug("failed to read perf-event counter: %s", strerror(errno));
+ return;
+ }
+ t_out->f |= BTF_CYOK;
+}
+
+static void perfevent_teardown(struct timer *t)
+ { close(t->u_cy.fd); }
+
+static const struct timer_ops perfevent_ops =
+ { perfevent_now, perfevent_teardown };
+
+static int perfevent_init(struct timer *t)
+{
+ struct perf_event_attr attr = { 0 };
+ struct bench_time tm;
+
+ attr.type = PERF_TYPE_HARDWARE;
+ attr.size = sizeof(attr);
+ attr.config = PERF_COUNT_HW_CPU_CYCLES;
+ attr.disabled = 0;
+ attr.exclude_kernel = 1;
+ attr.exclude_hv = 1;
+
+ t->u_cy.fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
+ if (t->u_cy.fd < 0) {
+ debug("couldn't open perf evvent: %s", strerror(errno));
+ return (-1);
+ }
+
+ tm.f = 0; perfevent_now(&tm, t);
+ if (!(tm.f&BTF_CYOK)) { close(t->u_cy.fd); return (-1); }
+
+ t->cyops = &perfevent_ops; return (0);
+}
+# define PERFEVENT_CYENT { "linux-perf-event", perfevent_init },
+#else
+# define PERFEVENT_CYENT
+#endif
+
+/*----- Intel time-stamp counter ------------------------------------------*/
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+
+#define EFLAGS_ID (1u << 21)
+#define CPUID_1D_TSC (1u << 4)
+
+static uint32 set_flags(unsigned long m, unsigned long x)
+{
+ unsigned long r;
+
+#ifdef __x86_64__
+# define TMP "%%rcx"
+#else
+# define TMP "%%ecx"
+#endif
+
+ __asm__ ("pushf\n\t"
+ "pop %0\n\t"
+ "mov %0, " TMP "\n\t"
+ "and %1, %0\n\t"
+ "xor %2, %0\n\t"
+ "push %0\n\t"
+ "popf\n\t"
+ "pushf\n\t"
+ "pop %0\n\t"
+ "push " TMP "\n\t"
+ "popf"
+ : "=r"(r)
+ : "g"(m), "g"(x)
+ : "ecx");
+ return (r);
+}
+
+struct cpuid { uint32 a, b, c, d; };
+
+static void cpuid(struct cpuid *info_out, uint32 a, uint32 c)
+{
+ __asm__ ("movl %1, %%eax\n\t"
+ "movl %2, %%ecx\n\t"
+ "cpuid\n\t"
+ "movl %%eax, 0(%0)\n\t"
+ "movl %%ebx, 4(%0)\n\t"
+ "movl %%ecx, 8(%0)\n\t"
+ "movl %%edx, 12(%0)\n\t"
+ : /* no outputs */
+ : "r"(info_out), "g"(a), "g"(c)
+ : "eax", "ebx", "ecx", "edx", "cc");
+}
+
+static void x86rdtsc_now(struct bench_time *t_out, struct timer *t)
+{
+ uint32 lo, hi;
+
+ __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
+ SET64(t_out->cy, hi, lo); t_out->f |= BTF_CYOK;
+}
+
+static const struct timer_ops x86rdtsc_ops =
+ { x86rdtsc_now, null_teardown };
+
+static int x86rdtsc_init(struct timer *t)
+{
+ struct cpuid info;
+
+ if ((set_flags(~EFLAGS_ID, 0)&EFLAGS_ID) ||
+ !(set_flags(~EFLAGS_ID, EFLAGS_ID)&EFLAGS_ID))
+ { debug("no `cpuid' instruction"); return (-1); }
+ cpuid(&info, 0, 0);
+ if (info.a < 1) { debug("no `cpuid' leaf 1"); return (-1); }
+ cpuid(&info, 1, 0);
+ if (!(info.d&CPUID_1D_TSC))
+ { debug("no `rdtsc' instrunction"); return (-1); }
+ t->cyops = &x86rdtsc_ops; return (0);
+}
+
+# define X86RDTSC_CYENT { "x86-rdtsc", x86rdtsc_init },
+#else
+# define X86RDTWC_CYENT
+#endif
+
+/*----- POSIX `clock_gettime' ---------------------------------------------*/
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_THREAD_CPUTIME_ID)
+
+static void gettime_now(struct bench_time *t_out, struct timer *t)
+{
+ struct timespec now;
+
+ if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now))
+ { debug("error reading POSIX clock: %s", strerror(errno)); return; }
+ ASSIGN64(t_out->s, now.tv_sec); t_out->ns = now.tv_nsec;
+ t_out->f |= BTF_TIMEOK;
+}
+
+static const struct timer_ops gettime_ops = { gettime_now, null_teardown };
+
+static int gettime_init(struct timer *t)
+{
+ struct bench_time tm;
+
+ tm.f = 0; gettime_now(&tm, t); if (!tm.f&BTF_TIMEOK) return (-1);
+ t->clkops = &gettime_ops; return (0);
+}
+
+# define GETTIME_CLKENT { "posix-clock_gettime", gettime_init },
+#else
+# define GETTIME_CLKENT
+#endif
+
+/*----- Standard C `clock' ------------------------------------------------*/
+
+static void clock_now(struct bench_time *t_out, struct timer *t)
+{
+ clock_t now, x;
+ unsigned long s; uint32 ns;
+
+ now = clock();
+ if (now == (clock_t)-1) {
+ debug("error reading standard clock: %s", strerror(errno));
+ return;
+ }
+ x = now/CLOCKS_PER_SEC;
+ if (x > ULONG_MAX) { debug("standard clock out of range"); return; }
+
+ s = x; x = now - CLOCKS_PER_SEC*s;
+ if (!(NS_PER_S%CLOCKS_PER_SEC))
+ ns = x*(NS_PER_S/CLOCKS_PER_SEC);
+ else if (NS_PER_S <= ULONG_MAX/CLOCKS_PER_SEC)
+ ns = (x*NS_PER_S)/CLOCKS_PER_SEC;
+ else
+ ns = x*((NS_PER_S + 0.0)/CLOCKS_PER_SEC);
+ ASSIGN64(t_out->s, s); t_out->ns = ns; t_out->f |= BTF_TIMEOK;
+}
+
+static const struct timer_ops clock_ops = { clock_now, null_teardown };
+
+static int clock_init(struct timer *t)
+{
+ struct bench_time tm;
+
+ tm.f = 0; clock_now(&tm, t); if (!tm.f&BTF_TIMEOK) return (-1);
+ t->clkops = &clock_ops; return (0);
+}
+
+#define CLOCK_CLKENT { "clock", clock_init },
+
+/*----- Timing setup ------------------------------------------------------*/
+
+static const struct timerent {
+ const char *name;
+ int (*init)(struct timer */*t*/);
+}
+ clktab[] = { GETTIME_CLKENT CLOCK_CLKENT { 0, 0 } },
+ cytab[] = { PERFEVENT_CYENT X86RDTSC_CYENT NULL_CYENT { 0, 0 } };
+
+static const struct timerent *find_timer_n(const char *name, size_t sz,
+ const struct timerent *timers,
+ const char *what)
+{
+ while (timers->name) {
+ if (strlen(timers->name) == sz && MEMCMP(name, ==, timers->name, sz))
+ return (timers);
+ timers++;
+ }
+ debug("%s timer `%.*s' not found", what, (int)sz, name); return (0);
+}
+
+static int try_timer(struct timer *t,
+ const struct timerent *timer, const char *what)
+{
+ if (timer->init(t)) return (-1);
+ debug("selected %s timer `%s'", what, timer->name); return (0);
+}
+
+static int select_timer(struct timer *t, const struct timerent *timers,
+ const char *varname, const char *what)
+{
+ const char *p; size_t n;
+ const struct timerent *timer;
+
+ p = getenv(varname);
+ if (!p) {
+ while (timers->name)
+ if (!try_timer(t, timers++, what)) return (0);
+ } else {
+ for (;;) {
+ n = strcspn(p, ",");
+ timer = find_timer_n(p, n, timers, what);
+ if (timer && !try_timer(t, timer, what)) return (0);
+ if (!p[n]) break;
+ p += n + 1;
+ }
+ }
+ debug("no suitable %s timer found", what); return (-1);
+}
+
+static void timer_now(struct bench_timer *tm, struct bench_time *t_out)
+{
+ struct timer *t = (struct timer *)tm;
+
+ t->clkops->now(t_out, t);
+ t->cyops->now(t_out, t);
+}
+
+static void timer_destroy(struct bench_timer *tm)
+{
+ struct timer *t = (struct timer *)tm;
+
+ if (!t) return;
+ if (t->clkops) t->clkops->teardown(t);
+ if (t->cyops) t->cyops->teardown(t);
+ xfree(t);
+}
+
+static const struct bench_timerops timer_ops = { timer_now, timer_destroy };
+
+struct bench_timer *bench_createtimer(void)
+{
+ struct timer *t = 0;
+ struct bench_timer *ret = 0;
+
+ t = xmalloc(sizeof(*t)); t->cyops = 0; t->clkops = 0;
+ if (select_timer(t, clktab, "MLIB_BENCH_CLKTIMER", "clock")) goto end;
+ if (select_timer(t, cytab, "MLIB_BENCH_CYCLETIMER", "cycle")) goto end;
+ t->_t.ops = &timer_ops; ret = &t->_t; t = 0;
+end:
+ if (t) timer_destroy(&t->_t);
+ return (ret);
+}
+
+#ifdef HAVE_UINT64
+# define FLOATK64(k) ((double)(k).i)
+#else
+# define FLOATK64(k) ((double)(k).lo + 4275123318.0*(double)(k).hi)
+#endif
+
+static void timer_diff(struct bench_timing *delta_out,
+ const struct bench_time *t0,
+ const struct bench_time *t1)
+{
+ delta_out->f = t0->f&t1->f;
+ kludge64 k;
+
+ if (!(delta_out->f&BTF_TIMEOK))
+ delta_out->t = 0.0;
+ else {
+ SUB64(k, t1->s, t0->s);
+ delta_out->t = FLOATK64(k) - 1 +
+ (t1->ns + NS_PER_S - t0->ns)/(double)NS_PER_S;
+ }
+
+ if (!(delta_out->f&BTF_CYOK))
+ delta_out->cy = 0.0;
+ else {
+ SUB64(k, t1->cy, t0->cy);
+ delta_out->cy = FLOATK64(k);
+ }
+}
+
+/*----- Calibration -------------------------------------------------------*/
+
+void bench_init(struct bench_state *b, struct bench_timer *tm)
+ { b->tm = tm; b->target_s = 1.0; b->f = 0; }
+
+void bench_destroy(struct bench_state *b)
+ { b->tm->ops->destroy(b->tm); }
+
+static void do_nothing(unsigned long n, void *p)
+ { while (n--) RELAX; }
+
+int bench_calibrate(struct bench_state *b)
+{
+ struct linreg lr_clk = LINREG_INIT, lr_cy = LINREG_INIT;
+ unsigned long n;
+ unsigned i;
+ struct bench_timer *tm = b->tm;
+ struct bench_time t0, t1;
+ struct bench_timing delta;
+ bench_fn *fn = LAUNDER(&do_nothing);
+ unsigned f = BTF_ANY;
+ int rc;
+
+ if (b->f&BTF_ANY) return (0);
+
+ for (i = 0; i < 10; i++)
+ { tm->ops->now(tm, &t0); fn(1, 0); tm->ops->now(tm, &t1); }
+
+ debug("calibrating...");
+ n = 1;
+ for (;;) {
+ tm->ops->now(tm, &t0); fn(n, 0); tm->ops->now(tm, &t1);
+ timer_diff(&delta, &t0, &t1); f &= delta.f;
+ if (!(f&BTF_TIMEOK)) { rc = -1; goto end; }
+ linreg_update(&lr_clk, n, delta.t);
+ if (!(f&BTF_CYOK))
+ debug(" n = %10lu; t = %12g s", n, delta.t);
+ else {
+ linreg_update(&lr_cy, n, delta.cy);
+ debug(" n = %10lu; t = %12g s, cy = %10.0f", n, delta.t, delta.cy);
+ }
+ if (delta.t >= b->target_s/20.0) break;
+ if (n >= ULONG_MAX - n/3) break;
+ n += n/3 + 1;
+ }
+
+ linreg_fit(&lr_clk, &b->clk.m, &b->clk.c, 0);
+ debug("clock overhead = (%g n + %g) s", b->clk.m, b->clk.c);
+ if (f&BTF_CYOK) {
+ linreg_fit(&lr_clk, &b->clk.m, &b->clk.c, 0);
+ debug("cycle overhead = (%g n + %g) cy", b->cy.m, b->cy.c);
+ }
+ b->f |= f; rc = 0;
+end:
+ return (rc);
+}
+
+int bench_measure(struct bench_timing *t_out, struct bench_state *b,
+ bench_fn *fn, void *p)
+{
+ struct bench_timer *tm = b->tm;
+ struct bench_time t0, t1;
+ unsigned long n;
+
+ if (bench_calibrate(b)) return (-1);
+ debug("measuring..."); n = 1;
+ for (;;) {
+ tm->ops->now(tm, &t0); fn(n, p); tm->ops->now(tm, &t1);
+ timer_diff(t_out, &t0, &t1);
+ if (!(t_out->f&BTF_TIMEOK)) return (-1);
+ if (!(t_out->f&BTF_CYOK)) debug(" n = %10lu; t = %12g", n, t_out->t);
+ else debug(" n = %10lu; t = %12g, cy = %10.0f", n, t_out->t, t_out->cy);
+ if (t_out->t >= 0.72*b->target_s) break;
+ n *= 1.44*b->target_s/t_out->t;
+ }
+ if (!(t_out->f&BTF_CYOK))
+ debug(" %g s per op; %g ops/s", t_out->t/n, n/t_out->t);
+ 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);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Benchmarking support
+ *
+ * (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_BENCH_H
+#define MLIB_BENCH_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_BITS_H
+# include "bits.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+struct bench_time {
+ unsigned f;
+#define BTF_TIMEOK 1u
+#define BTF_CYOK 2u
+#define BTF_ANY (BTF_TIMEOK | BTF_CYOK)
+ kludge64 s; uint32 ns;
+ kludge64 cy;
+};
+
+struct bench_timing {
+ unsigned f;
+ unsigned long n;
+ double t, cy;
+};
+
+struct bench_timer { const struct bench_timerops *ops; };
+
+struct bench_timerops {
+ void (*now)(struct bench_timer */*bt*/, struct bench_time */*t_out*/);
+ void (*destroy)(struct bench_timer */*bt*/);
+};
+
+struct bench_state {
+ struct bench_timer *tm;
+ double target_s;
+ unsigned f;
+ struct { double m, c; } clk, cy;
+};
+
+typedef void bench_fn(unsigned long /*n*/, void */*p*/);
+
+/*----- Functions provided ------------------------------------------------*/
+
+extern struct bench_timer *bench_createtimer(void);
+
+extern void bench_init(struct bench_state *b, struct bench_timer *tm);
+
+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*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
--- /dev/null
+/* -*-c-*-
+ *
+ * Test 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 "tvec.h"
+
+/*----- 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 },
+ { "greater", +1 },
+ { 0 }
+};
+
+static const struct tvec_uassoc uenum_assocs[] = {
+ { "apple", 0 },
+ { "banana", 1 },
+ { "clementine", 2 },
+ { 0 }
+};
+
+static const struct tvec_passoc penum_assocs[] = {
+ { "alice", &uenum_assocs[0] },
+ { "bob", &uenum_assocs[1] },
+ { "carol", &uenum_assocs[2] },
+ { 0 }
+};
+
+#if __STDC_VERSION__ >= 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_flag attr_flags[] = {
+ { "black-fg", 0x07, 0x00 },
+ { "blue-fg", 0x07, 0x01 },
+ { "red-fg", 0x07, 0x02 },
+ { "magenta-fg", 0x07, 0x03 },
+ { "green-fg", 0x07, 0x04 },
+ { "cyan-fg", 0x07, 0x05 },
+ { "yellow-fg", 0x07, 0x06 },
+ { "white-fg", 0x07, 0x07 },
+
+ { "black-bg", 0x38, 0x00 },
+ { "blue-bg", 0x38, 0x08 },
+ { "red-bg", 0x38, 0x10 },
+ { "magenta-bg", 0x38, 0x18 },
+ { "green-bg", 0x38, 0x20 },
+ { "cyan-bg", 0x38, 0x28 },
+ { "yellow-bg", 0x38, 0x30 },
+ { "white-bg", 0x38, 0x38 },
+
+ { "normal", 0xc0, 0x00 },
+ { "bright", 0x40, 0x40 },
+ { "flash", 0x80, 0x80 },
+
+ { 0 }
+};
+
+static const struct tvec_flaginfo attr_info =
+ { "attr", attr_flags, &tvrange_u16 };
+
+static const struct tvec_urange range_32 = { 0, 31 };
+
+#define TYPEREGS(_) \
+ _(int, RI, int, p, &tvrange_i16) \
+ _(uint, RU, uint, p, &tvrange_u16) \
+ _(ienum, RIE, enum, p, &ienum_info) \
+ _(uenum, RUE, enum, p, &uenum_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)
+
+/*----- Serialization test ------------------------------------------------*/
+
+struct test_context {
+ struct tvec_state *tv;
+};
+
+static void capture_state_and_run(struct tvec_state *tv)
+{
+ struct test_context tctx;
+
+ tctx.tv = tv; tv->test->fn(tv->in, tv->out, &tctx);
+ 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;
+ }
+ tvec_check(tv, 0);
+}
+
+static void test_serialization
+ (const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
+{
+ struct test_context *tctx = ctx;
+ struct tvec_state *tv = tctx->tv;
+ const struct tvec_regdef *rd;
+ union tvec_regval *rv;
+ void *p; size_t sz;
+
+ if (tvec_serialize(tv->in, tv->test->regs,
+ NROUT, sizeof(struct tvec_reg), &p, &sz))
+ { out[RRC].v.i = -1; return; }
+ out[RSER].f |= TVRF_LIVE;
+ out[RSER].v.bytes.p = p; out[RSER].v.bytes.sz = sz;
+
+ if (tvec_deserialize(tv->out, tv->test->regs,
+ NROUT, sizeof(struct tvec_reg), p, sz))
+ { out[RRC].v.i = -1; return; }
+
+ if (in[RSAB].f&TVRF_LIVE) {
+ for (rd = tv->test->regs; rd->name; rd++)
+ if (STRCMP(in[RSAB].v.str.p, ==, rd->name)) {
+ rv = &out[rd->i].v;
+ if (rd->ty == &tvty_int ||
+ (rd->ty == &tvty_enum &&
+ ((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))
+ rv->u ^= 1;
+ else if (rd->ty == &tvty_string)
+ { if (rv->str.sz) rv->str.p[0] ^= 1; }
+ else if (rd->ty == &tvty_bytes)
+ { if (rv->bytes.sz) rv->bytes.p[0] ^= 1; }
+ }
+ }
+
+ out[RRC].v.i = 0;
+}
+
+DSGINIT(static) const struct tvec_regdef test_regs[] = {
+#define DEFREG(name, i, ty, argslot, argval) \
+ { #name, i, &tvty_##ty, TVRF_OPT, \
+ DSGINIT({ .argslot = argval }) },
+ TYPEREGS(DEFREG)
+#undef DEFREG
+ { "rc", RRC, &tvty_int, TVRF_OPT, { &tvrange_int } },
+ { "serialized", RSER, &tvty_bytes, TVRF_OPT },
+ { "sabotage", RSAB, &tvty_string, TVRF_OPT, { &tvrange_byte } },
+
+ { 0 }
+};
+
+/*----- Single-type copy tests --------------------------------------------*/
+
+static void test_copy_simple
+ (const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
+ { out->v = in->v; }
+
+static void test_copy_string
+ (const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
+{
+ tvec_allocstring(&out->v, in->v.str.sz);
+ memcpy(out->v.str.p, in->v.str.p, in->v.str.sz);
+}
+
+static void test_copy_bytes
+ (const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
+{
+ tvec_allocstring(&out->v, in->v.str.sz);
+ memcpy(out->v.str.p, in->v.str.p, in->v.str.sz);
+}
+
+#define test_copy_int test_copy_simple
+#define test_copy_uint test_copy_simple
+#define test_copy_ienum test_copy_simple
+#define test_copy_uenum test_copy_simple
+#define test_copy_penum test_copy_simple
+#define test_copy_flags test_copy_simple
+#define test_copy_buffer test_copy_bytes
+
+#define SINGLEREG(name, i, ty, argslot, argval) \
+ DSGINIT(const) struct tvec_regdef name##_regs[] = { \
+ { #name, RV, &tvty_##ty, 0, DSGINIT({ .argslot = argval }) }, \
+ { 0 } \
+ };
+TYPEREGS(SINGLEREG)
+#undef SINGLEREG
+
+/*----- Front end ---------------------------------------------------------*/
+
+static const struct tvec_test tests[] = {
+ { "types", test_regs, 0, capture_state_and_run,
+ test_serialization },
+
+#define DEFCOPY(name, i, ty, argslot, argval) \
+ { #name, name##_regs, 0, tvec_runtest, test_copy_##name },
+ TYPEREGS(DEFCOPY)
+#undef DEFCOPY
+
+ { 0 }
+};
+
+static const struct tvec_info testinfo = {
+ tests,
+ NROUT, NREG, sizeof(struct tvec_reg)
+};
+
+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
+# define POKE(name, i, ty, argslot, argval) \
+ name##_regs->arg.argslot = argval;
+ TYPEREGS(POKE)
+# undef POKE
+#endif
+ return (tvec_main(argc, argv, &testinfo, 0));
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+### -*-autotest-*-
+###
+### Test script for test machinery
+###
+### (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.
+
+###--------------------------------------------------------------------------
+### tvec
+
+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 mismatch_filter
+m4_define([mismatch_filter],
+ [s/\(@%:@<@<:@0-9a-zA-Z_-@:>@*\) @<:@^>@:>@*>/\1 ...>/g])
+
+dnl test_parse(TY, IN, OUT)
+m4_define([test_parse], [
+AT_DATA([tv],
+[;;; -*-conf-*-
+[[$1]]
+$1 = $2
+@status = ?
+])
+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
+])])
+
+dnl test_parserr(TY, IN, LNO, ERR)
+m4_define([test_parserr], [
+AT_DATA([tv],
+[;;; -*-conf-*-
+[[$1]]
+$1 = $2
+@status = ?
+])
+AT_CHECK([BUILDDIR/t/tvec.t -fh tv], [2], [],
+[tvec.t: tv:$3: $4
+])])
+
+AT_SETUP(tvec type-int)
+test_parse([int], [4], [4 ; = 0x04])
+test_parse([int], [ 17; comment], [17 ; = 0x11])
+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>])
+test_parserr([int], [123456], [3],
+ [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_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>])
+test_parserr([uint], [123456], [3],
+ [integer 123456 out of range (must be in [[0 .. 65535]])])
+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([penum], [@%:@nil], [@%:@nil])
+AT_CLEANUP
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+/* -*-c-*-
+ *
+ * Main test vector driver
+ *
+ * (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 <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "bench.h"
+#include "tvec.h"
+
+/*----- Output ------------------------------------------------------------*/
+
+void tvec_error(struct tvec_state *tv, const char *msg, ...)
+ { va_list ap; va_start(ap, msg); tvec_error_v(tv, msg, &ap); }
+void tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap)
+ { tv->output->ops->error(tv->output, msg, ap); exit(2); }
+
+void tvec_notice(struct tvec_state *tv, const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap);
+}
+void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap)
+ { tv->output->ops->notice(tv->output, msg, ap); }
+
+void tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
+{
+ va_list ap;
+ va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
+}
+void tvec_syntax_v(struct tvec_state *tv, int ch,
+ const char *expect, va_list *ap)
+{
+ dstr d = DSTR_INIT;
+ char found[8];
+
+ switch (ch) {
+ case EOF: strcpy(found, "<eof>"); break;
+ case '\n': strcpy(found, "<eol>"); break;
+ default:
+ if (isprint(ch)) sprintf(found, "`%c'", ch);
+ else sprintf(found, "<#x%02x>", ch);
+ break;
+ }
+ dstr_vputf(&d, expect, ap);
+ tvec_error(tv, "syntax error: expected %s but found %s", expect, found);
+}
+
+void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
+{
+ va_list ap;
+ va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
+}
+void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
+{
+ tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++;
+ tv->output->ops->skipgroup(tv->output, excuse, ap);
+}
+
+static void set_outcome(struct tvec_state *tv, unsigned out)
+{
+ tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK);
+ tv->f |= out << TVSF_OUTSHIFT;
+}
+
+void tvec_skip(struct tvec_state *tv, const char *excuse, ...)
+{
+ va_list ap;
+ va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap);
+}
+void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap)
+{
+ assert(tv->f&TVSF_ACTIVE);
+ set_outcome(tv, TVOUT_SKIP);
+ tv->output->ops->skip(tv->output, excuse, ap);
+}
+
+void tvec_fail(struct tvec_state *tv, const char *detail, ...)
+{
+ va_list ap;
+ va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap);
+}
+void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap)
+{
+ assert((tv->f&TVSF_ACTIVE) ||
+ (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT));
+ 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_write(struct tvec_state *tv, const char *p, ...)
+{
+ 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;
+
+ 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;
+ }
+
+ if (BBAD(&b)) { rc = -1; goto end; }
+ rc = 0;
+end:
+ return (rc);
+}
+
+/*----- Benchmarking ------------------------------------------------------*/
+
+struct bench_state *tvec_benchstate;
+
+struct benchrun {
+ unsigned long *n;
+ tvec_testfn *fn;
+ const struct tvec_reg *in; struct tvec_reg *out;
+ void *ctx;
+};
+
+static void benchloop_outer(unsigned long n, void *p)
+ { struct benchrun *r = p; while (n--) r->fn(r->in, r->out, r->ctx); }
+
+static void benchloop_inner(unsigned long n, void *p)
+ { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); }
+
+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;
+
+ if (tvb->b) bb = tvb->b;
+ else bb = &tvec_benchstate;
+
+ 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); }
+
+ *b_out = *bb;
+ return (0);
+}
+
+void tvec_bench(struct tvec_state *tv)
+{
+ const struct tvec_bench *tvb = tv->test->arg.p;
+ struct bench_state *b;
+ struct bench_timing tm;
+ struct benchrun r;
+ bench_fn *loopfn;
+
+ if (tvec_ensurebench(tv, &b)) goto end_0;
+
+ 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; }
+
+ if (tvb->riter < 0)
+ { r.n = 0; loopfn = benchloop_outer; }
+ else
+ { r.n = &TVEC_REG(tv, in, tvb->riter)->v.u; loopfn = benchloop_inner; }
+
+ 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);
+
+end_2:
+ if (tvb->teardown) tvb->teardown(r.ctx);
+end_1:
+ if (r.ctx) xfree(r.ctx);
+end_0:
+ return;
+}
+
+/*----- Main machinery ----------------------------------------------------*/
+
+void tvec_skipspc(struct tvec_state *tv)
+{
+ int ch;
+
+ for (;;) {
+ ch = getc(tv->fp);
+ if (ch == EOF) break;
+ else if (ch == '\n' || !isspace(ch)) { ungetc(ch, tv->fp); break; }
+ }
+}
+
+void tvec_flushtoeol(struct tvec_state *tv, unsigned f)
+{
+ int ch;
+
+ for (;;) {
+ ch = getc(tv->fp);
+ switch (ch) {
+ case '\n': tv->lno++; return;
+ case EOF: return;
+ case ';': f |= TVFF_ALLOWANY; break;
+ default:
+ if (!(f&TVFF_ALLOWANY) && !isspace(ch))
+ tvec_syntax(tv, ch, "end-of-line");
+ break;
+ }
+ }
+}
+
+int tvec_nexttoken(struct tvec_state *tv)
+{
+ enum { TAIL, NEWLINE, INDENT, COMMENT };
+ int ch;
+ unsigned s = TAIL;
+
+ for (;;) {
+ ch = getc(tv->fp);
+ switch (ch) {
+ case EOF:
+ return (-1);
+
+ case ';':
+ s = COMMENT;
+ break;
+
+ case '\n':
+ if (s == NEWLINE || s == INDENT) { ungetc(ch, tv->fp); return (-1); }
+ else { tv->lno++; s = NEWLINE; }
+ break;
+
+ default:
+ if (isspace(ch))
+ { if (s == NEWLINE) s = INDENT; }
+ else if (s != COMMENT) {
+ ungetc(ch, tv->fp);
+ if (s == NEWLINE) return (-1);
+ else return (0);
+ }
+ break;
+ }
+ }
+}
+
+int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
+ const char *expect, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, expect);
+ rc = tvec_readword_v(tv, d, delims, expect, &ap);
+ va_end(ap);
+ return (rc);
+}
+int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
+ const char *expect, va_list *ap)
+{
+ int ch;
+
+ ch = getc(tv->fp);
+ if (ch == '\n' || ch == EOF || ch == ';' ||
+ (delims && strchr(delims, ch))) {
+ if (expect) tvec_syntax(tv, ch, expect, ap);
+ else { ungetc(ch, tv->fp); return (-1); }
+ }
+ if (d->len) DPUTC(d, ' ');
+ do {
+ DPUTC(d, ch);
+ ch = getc(tv->fp);
+ } while (ch != EOF && !isspace(ch) && (!delims || !strchr(delims, ch)));
+ DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
+ return (0);
+}
+
+static void init_registers(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); r = TVEC_REG(tv, in, rd->i);
+ rd->ty->init(&r->v, rd); r->f = 0;
+ 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)
+{
+ const struct tvec_regdef *rd;
+ struct tvec_reg *r;
+
+ for (rd = tv->test->regs; rd->name; rd++) {
+ assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
+ rd->ty->release(&r->v, rd); r->f = 0;
+ if (rd->i < tv->nrout)
+ { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
+ }
+}
+
+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)
+{
+ 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 (!(f&f_mismatch)) return;
+
+ tvec_fail_v(tv, detail, ap);
+ tvec_mismatch(tv);
+
+#undef f_mismatch
+}
+
+void tvec_runtest(struct tvec_state *tv)
+{
+ tv->test->fn(tv->in, tv->out, (/*unconst*/ void *)tv->test->arg.p);
+ tvec_check(tv, 0);
+}
+
+static void begin_test(struct tvec_state *tv)
+{
+ tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->st = '.';
+ tv->output->ops->btest(tv->output);
+}
+
+void tvec_endtest(struct tvec_state *tv)
+{
+ unsigned out;
+
+ if (tv->f&TVSF_ACTIVE) out = TVOUT_WIN;
+ else out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT;
+ assert(out < TVOUT_LIMIT); tv->curr[out]++;
+ tv->output->ops->etest(tv->output, out);
+ tv->f &= ~TVSF_OPEN;
+}
+
+static void check(struct tvec_state *tv)
+{
+ const struct tvec_regdef *rd;
+
+ if (!(tv->f&TVSF_OPEN)) return;
+
+ for (rd = tv->test->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);
+ }
+
+ if (!(tv->f&TVSF_SKIP))
+ { begin_test(tv); tv->test->run(tv); tvec_endtest(tv); }
+
+ tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv);
+}
+
+static void begin_test_group(struct tvec_state *tv)
+{
+ 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);
+}
+
+void tvec_reportgroup(struct tvec_state *tv)
+{
+ unsigned i, out, nrun;
+
+ for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++)
+ { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; }
+
+ if (tv->curr[TVOUT_SKIP] == nrun)
+ { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); }
+ else {
+ if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE;
+ else out = TVOUT_WIN;
+ tv->grps[out]++; tv->output->ops->egroup(tv->output, out);
+ }
+}
+
+static void end_test_group(struct tvec_state *tv)
+{
+ if (!tv->test) return;
+ if (tv->f&TVSF_OPEN) check(tv);
+ if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv);
+ release_registers(tv); tv->test = 0;
+}
+
+void tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
+{
+ dstr d = DSTR_INIT;
+ const struct tvec_test *test;
+ const struct tvec_regdef *rd;
+ struct tvec_reg *r;
+ int ch;
+
+ tv->infile = infile; tv->lno = 1; tv->fp = fp;
+
+ for (;;) {
+ ch = getc(tv->fp);
+ switch (ch) {
+
+ case EOF:
+ goto end;
+
+ case '[':
+ tvec_skipspc(tv);
+ DRESET(&d); tvec_readword(tv, &d, "];", "group name");
+ for (test = tv->tests; test->name; test++)
+ if (STRCMP(d.buf, ==, test->name)) goto found_test;
+ tvec_error(tv, "unknown test group `%s'", d.buf);
+ found_test:
+ tvec_skipspc(tv);
+ ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
+ tvec_flushtoeol(tv, 0);
+ end_test_group(tv);
+ tv->test = test;
+ begin_test_group(tv);
+ break;
+
+ case '\n':
+ tv->lno++;
+ if (tv->f&TVSF_OPEN) check(tv);
+ break;
+
+ default:
+ if (isspace(ch)) {
+ tvec_skipspc(tv);
+ ch = getc(tv->fp);
+ if (ch == EOF)
+ goto end;
+ else if (ch == ';')
+ { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; }
+ else
+ { tvec_flushtoeol(tv, 0); check(tv); }
+ } else if (ch == ';')
+ { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; }
+ else {
+ ungetc(ch, tv->fp);
+ DRESET(&d); tvec_readword(tv, &d, "=:;", "register name");
+ tvec_skipspc(tv); ch = getc(tv->fp);
+ if (ch != '=' && ch != ':') tvec_syntax(tv, ch, "`=' or `:'");
+ tvec_skipspc(tv);
+ 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");
+ if (ch == '\\') {
+ ch = getc(tv->fp);
+ if (ch == EOF || ch == '\n')
+ tvec_syntax(tv, ch, "escaped status character");
+ }
+ tv->expst = ch;
+ tvec_flushtoeol(tv, 0);
+ } else
+ tvec_error(tv, "unknown special register `%s'", d.buf);
+ } else {
+ if (!tv->test) tvec_error(tv, "no current test");
+ for (rd = tv->test->regs; rd->name; rd++)
+ if (STRCMP(rd->name, ==, d.buf)) goto found_reg;
+ tvec_error(tv, "unknown register `%s' for test `%s'",
+ d.buf, tv->test->name);
+ found_reg:
+ if (!(tv->f&TVSF_OPEN))
+ { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
+ tvec_skipspc(tv);
+ r = TVEC_REG(tv, in, rd->i);
+ if (r->f&TVRF_LIVE)
+ tvec_error(tv, "register `%s' already set", rd->name);
+ rd->ty->parse(&r->v, rd, tv); r->f |= TVRF_LIVE;
+ }
+ }
+ break;
+ }
+ }
+end:
+ end_test_group(tv);
+ tv->infile = 0; tv->fp = 0;
+ dstr_destroy(&d);
+}
+
+/*----- Ad-hoc testing ----------------------------------------------------*/
+
+static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
+
+static void 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;
+ tv->tests = t;
+}
+
+void tvec_begingroup(struct tvec_state *tv, const char *name,
+ const char *file, unsigned lno)
+{
+ struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->tests;
+
+ t->name = name; tv->test = t;
+ tv->infile = file; tv->lno = tv->test_lno = lno;
+ begin_test_group(tv);
+}
+
+void tvec_endgroup(struct tvec_state *tv)
+{
+ if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv);
+ tv->test = 0;
+}
+
+void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
+{
+ tv->infile = file; tv->lno = tv->test_lno = lno;
+ begin_test(tv); tv->f |= TVSF_OPEN;
+}
+
+struct adhoc_claim {
+ unsigned f;
+#define ACF_FRESH 1u
+ const char *saved_file; unsigned saved_lno;
+};
+
+static void adhoc_claim_setup(struct tvec_state *tv,
+ struct adhoc_claim *ck,
+ const struct tvec_regdef *regs,
+ const char *file, unsigned lno)
+{
+ struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
+
+ ck->f = 0;
+
+ if (!(tv->f&TVSF_OPEN))
+ { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); }
+
+ 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,
+ struct adhoc_claim *ck)
+{
+ struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
+
+ t->regs = &no_regs;
+ tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno;
+
+ if (ck->f&ACF_FRESH) tvec_endtest(tv);
+}
+
+int tvec_claim(struct tvec_state *tv, int ok,
+ const char *file, unsigned lno, const char *expr, ...)
+{
+ struct adhoc_claim ck;
+ va_list ap;
+
+ adhoc_claim_setup(tv, &ck, 0, file, lno);
+ if (!ok)
+ { va_start(ap, expr); tvec_fail_v(tv, expr, &ap); va_end(ap); }
+ adhoc_claim_teardown(tv, &ck);
+ return (ok);
+}
+
+int tvec_claimeq(struct tvec_state *tv,
+ const struct tvec_regty *ty, const union tvec_misc *arg,
+ const char *file, unsigned lno, const char *expr)
+{
+ struct tvec_regdef regs[2];
+ struct adhoc_claim ck;
+ int ok;
+
+ tv->in[0].f = tv->out[0].f = TVRF_LIVE;
+
+ regs[0].name = "value"; regs[0].i = 0;
+ regs[0].ty = ty; regs[0].f = 0;
+ if (arg) regs[0].arg = *arg;
+ else regs[0].arg.p = 0;
+
+ regs[1].name = 0;
+
+ 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); }
+ 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 -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Main entry point for test-vector processing
+ *
+ * (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 <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+
+#include "bench.h"
+#include "dstr.h"
+#include "macros.h"
+#include "mdwopt.h"
+#include "quis.h"
+#include "report.h"
+#include "tvec.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+static const struct outform {
+ const char *name;
+ struct tvec_output *(*makefn)(FILE *fp);
+} outtab[] = {
+ { "human", tvec_humanoutput },
+ { "tap", tvec_tapoutput },
+ { 0, 0 }
+};
+
+static struct bench_state benchstate;
+
+const struct tvec_info tvec_adhocinfo =
+ { 0, 1, 1, sizeof(struct tvec_reg) };
+
+static const struct outform *find_outform(const char *p)
+{
+ const struct outform *best = 0, *of;
+ size_t n = strlen(p);
+
+ for (of = outtab; of->name; of++)
+ if (!STRNCMP(p, ==, of->name, n))
+ ;
+ else if (!of->name[n])
+ return (of);
+ else if (best)
+ die(2, "ambiguous output format name (`%s' or `%s'?)",
+ best->name, of->name);
+ else
+ best = of;
+ if (best) return (best);
+ else die(2, "unknown output format `%s'", optarg);
+}
+
+static void version(FILE *fp)
+ { pquis(fp, "$, mLib test-vector framework version " VERSION "\n"); }
+
+static void usage(FILE *fp)
+{
+ pquis(fp, "\
+usage: $ [-f FORMAT] [-o OUTPUT] [-t SECS] [TEST-FILE ...]\n\
+");
+}
+
+static void help(FILE *fp)
+{
+ version(fp); fputc('\n', fp);
+ usage(fp); fputs("\
+Options:\n\
+ -h, --help show this help text.\n\
+ -v, --version show version number.\n\
+ -u, --usage show usage synopsis.\n\
+\n\
+ -f, --format=FORMAT produce output in FORMAT.\n\
+ -o, --output=OUTPUT write output to OUTPUT file.\n\
+ -t, --target=SECS aim to run benchmarks for SECS.\n\
+", fp);
+}
+
+void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out,
+ int *argpos_out, const struct tvec_info *info)
+{
+ FILE *ofp = 0;
+ const struct outform *of = 0;
+ struct tvec_output *o;
+ struct bench_timer *tm;
+ const char *p; char *q;
+ int opt;
+ double t;
+ unsigned f = 0;
+#define f_bogus 1u
+
+ static const struct option options[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { "usage", 0, 0, 'u' },
+
+ { "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);
+ if (opt < 0) break;
+ switch (opt) {
+ case 'h': help(stdout); exit(0);
+ case 'v': version(stdout); exit(0);
+ case 'u': usage(stdout); exit(0);
+
+ case 'f': of = find_outform(optarg); break;
+ case 'o':
+ if (ofp) fclose(ofp);
+ ofp = fopen(optarg, "w");
+ if (!ofp)
+ 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;
+ break;
+ }
+ }
+ if (f&f_bogus) { usage(stderr); exit(2); }
+ if (!ofp) ofp = stdout;
+ if (!of) {
+ p = getenv("TVEC_FORMAT");
+ if (p) of = find_outform(p);
+ }
+ if (of) o = of->makefn(ofp);
+ else o = tvec_dfltout(ofp);
+
+ tvec_begin(tv_out, info, o); *argpos_out = optind;
+}
+
+void tvec_readstdin(struct tvec_state *tv)
+ { tvec_read(tv, "<stdin>", stdin); }
+
+void tvec_readfile(struct tvec_state *tv, const char *file)
+{
+ FILE *fp;
+
+ fp = fopen(file, "r");
+ if (!fp)
+ tvec_error(tv, "failed to open `%s' for reading: %s",
+ file, strerror(errno));
+ tvec_read(tv, file, fp);
+ fclose(fp);
+}
+
+void tvec_readdflt(struct tvec_state *tv, const char *file)
+{
+ dstr d = DSTR_INIT;
+ const char *p;
+
+ if (file) {
+ p = getenv("srcdir");
+ if (p)
+ { dstr_putf(&d, "%s/%s", p, file); file = d.buf; }
+ tvec_readfile(tv, file);
+ } else if (isatty(0))
+ tvec_error(tv, "use `-' to force reading from interactive stdin");
+ else
+ tvec_readstdin(tv);
+}
+
+void tvec_readarg(struct tvec_state *tv, const char *arg)
+{
+ if (STRCMP(arg, ==, "-")) tvec_readstdin(tv);
+ else tvec_readfile(tv, arg);
+}
+
+void tvec_readargs(int argc, char *argv[], struct tvec_state *tv,
+ int *argpos_inout, const char *dflt)
+{
+ int i = *argpos_inout;
+
+
+ if (i == argc) tvec_readdflt(tv, dflt);
+ else while (i < argc) tvec_readarg(tv, argv[i++]);
+ *argpos_inout = i;
+}
+
+int tvec_main(int argc, char *argv[],
+ const struct tvec_info *info, const char *dflt)
+{
+ struct tvec_state tv;
+ int argpos;
+
+ tvec_parseargs(argc, argv, &tv, &argpos, info);
+ tvec_readargs(argc, argv, &tv, &argpos, dflt);
+ return (tvec_end(&tv));
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Test vector output management
+ *
+ * (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 <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <unistd.h>
+
+#include "alloc.h"
+#include "bench.h"
+#include "dstr.h"
+#include "quis.h"
+#include "report.h"
+#include "tvec.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";
+ default: abort();
+ }
+}
+
+static int getenv_boolean(const char *var, int dflt)
+{
+ const char *p;
+
+ p = getenv(var);
+ if (!p)
+ return (dflt);
+ else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
+ STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
+ STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
+ STRCMP(p, ==, "1"))
+ return (1);
+ else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
+ STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
+ STRCMP(p, ==, "0"))
+ return (0);
+ else {
+ moan("unexpected value `%s' for boolean environment variable `%s'",
+ var, p);
+ return (dflt);
+ }
+}
+
+static void basic_report_status(unsigned disp, int st, struct tvec_state *tv)
+ { tvec_write(tv, " %8s status = `%c'\n", stdisp(disp), st); }
+
+static void basic_report_register(unsigned disp,
+ const struct tvec_reg *r,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ tvec_write(tv, " %8s %s = ", regdisp(disp), rd->name);
+ if (r->f&TVRF_LIVE) rd->ty->dump(&r->v, rd, tv, 0);
+ else tvec_write(tv, "#<unset>");
+ tvec_write(tv, "\n");
+}
+
+static const struct mismatchfns basic_mismatchfns =
+ { basic_report_status, basic_report_register };
+
+static void dump_inreg(const struct tvec_regdef *rd,
+ const struct mismatchfns *fns, struct tvec_state *tv)
+ { fns->report_register(INPUT, TVEC_REG(tv, in, rd->i), rd, tv); }
+
+static void dump_outreg(const struct tvec_regdef *rd,
+ const struct mismatchfns *fns, struct tvec_state *tv)
+{
+ const struct tvec_reg
+ *rin = TVEC_REG(tv, in, rd->i), *rout = TVEC_REG(tv, out, rd->i);
+
+ if (tv->st == '.') {
+ if (!(rout->f&TVRF_LIVE)) {
+ if (!(rin->f&TVRF_LIVE))
+ fns->report_register(INPUT, rin, rd, tv);
+ else {
+ fns->report_register(FOUND, rout, rd, tv);
+ fns->report_register(EXPECT, rin, rd, tv);
+ }
+ } else {
+ if (!(rin->f&TVRF_LIVE)) fns->report_register(OUTPUT, rout, rd, tv);
+ else if (rd->ty->eq(&rin->v, &rout->v, rd))
+ fns->report_register(MATCH, rin, rd, tv);
+ else {
+ fns->report_register(FOUND, rout, rd, tv);
+ fns->report_register(EXPECT, rin, rd, tv);
+ }
+ }
+ }
+}
+
+static void mismatch(const struct mismatchfns *fns, struct tvec_state *tv)
+{
+ const struct tvec_regdef *rd;
+
+ if (tv->st != tv->expst) {
+ fns->report_status(FOUND, tv->st, tv);
+ fns->report_status(EXPECT, tv->expst, tv);
+ } else if (tv->st != '.')
+ fns->report_status(MATCH, tv->st, tv);
+
+ for (rd = tv->test->regs; rd->name; rd++) {
+ if (rd->i < tv->nrout) dump_outreg(rd, fns, tv);
+ else dump_inreg(rd, fns, tv);
+ }
+}
+
+static void bench_summary(struct tvec_state *tv)
+{
+ const struct tvec_regdef *rd;
+ unsigned f = 0;
+#define f_any 1u
+
+ 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");
+}
+
+/*----- 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 ..._egroup(struct tvec_output *o, unsigned outcome)
+static void ..._skipgroup(struct tvec_output *o,
+ const char *excuse, va_list *ap)
+static void ..._btest(struct tvec_output *o)
+static void ..._skip(struct tvec_output *o, const char *detail, va_list *ap)
+static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
+static void ..._mismatch(struct tvec_output *o)
+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 ..._destroy(struct tvec_output *o)
+
+static const struct tvec_outops ..._ops = {
+ ..._error, ..._notice, ..._write,
+ ..._bsession, ..._esession,
+ ..._bgroup, ..._egroup, ..._skip,
+ ..._btest, ..._skip, ..._fail, ..._mismatch, ..._etest,
+ ..._bbench, ..._ebench,
+ ..._destroy
+};
+*/
+/*----- Human-readable output ---------------------------------------------*/
+
+#define HAF_FGMASK 0x0f
+#define HAF_FGSHIFT 0
+#define HAF_BGMASK 0xf0
+#define HAF_BGSHIFT 4
+#define HAF_FG 256u
+#define HAF_BG 512u
+#define HAF_BOLD 1024u
+#define HCOL_BLACK 0u
+#define HCOL_RED 1u
+#define HCOL_GREEN 2u
+#define HCOL_YELLOW 3u
+#define HCOL_BLUE 4u
+#define HCOL_MAGENTA 5u
+#define HCOL_CYAN 6u
+#define HCOL_WHITE 7u
+#define HCF_BRIGHT 8u
+#define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT)
+#define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT)
+
+#define HA_WIN (HFG(GREEN))
+#define HA_LOSE (HFG(RED) | HAF_BOLD)
+#define HA_SKIP (HFG(YELLOW))
+
+struct human_output {
+ struct tvec_output _o;
+ FILE *fp;
+ dstr scoreboard;
+ unsigned attr;
+ unsigned f;
+#define HOF_TTY 1u
+#define HOF_DUPERR 2u
+#define HOF_COLOUR 4u
+#define HOF_PROGRESS 8u
+};
+
+static void set_colour(FILE *fp, int *sep_inout,
+ const char *norm, const char *bright,
+ unsigned colour)
+{
+ if (*sep_inout) putc(*sep_inout, fp);
+ fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
+ *sep_inout = ';';
+}
+
+static void setattr(struct human_output *h, unsigned attr)
+{
+ unsigned diff = h->attr ^ attr;
+ int sep = 0;
+
+ if (!diff || !(h->f&HOF_COLOUR)) return;
+ fputs("\x1b[", h->fp);
+
+ if (diff&HAF_BOLD) {
+ if (attr&HAF_BOLD) putc('1', h->fp);
+ else { putc('0', h->fp); diff = h->attr; }
+ sep = ';';
+ }
+ if (diff&(HAF_FG | HAF_FGMASK)) {
+ if (attr&HAF_FG)
+ set_colour(h->fp, &sep, "3", "9", (attr&HAF_FGMASK) >> HAF_FGSHIFT);
+ else
+ { if (sep) putc(sep, h->fp); fputs("39", h->fp); sep = ';'; }
+ }
+ if (diff&(HAF_BG | HAF_BGMASK)) {
+ if (attr&HAF_BG)
+ set_colour(h->fp, &sep, "4", "10", (attr&HAF_BGMASK) >> HAF_BGSHIFT);
+ else
+ { if (sep) putc(sep, h->fp); fputs("49", h->fp); sep = ';'; }
+ }
+
+ putc('m', h->fp); h->attr = attr;
+
+#undef f_any
+}
+
+static void clear_progress(struct human_output *h)
+{
+ size_t i, n;
+
+ if (h->f&HOF_PROGRESS) {
+ n = strlen(h->_o.tv->test->name) + 2 + h->scoreboard.len;
+ for (i = 0; i < n; i++) fputs("\b \b", h->fp);
+ h->f &= ~HOF_PROGRESS;
+ }
+}
+
+static void write_scoreboard_char(struct human_output *h, int ch)
+{
+ switch (ch) {
+ case 'x': setattr(h, HA_LOSE); break;
+ case '_': setattr(h, HA_SKIP); break;
+ default: setattr(h, 0); break;
+ }
+ putc(ch, h->fp); setattr(h, 0);
+}
+
+static void show_progress(struct human_output *h)
+{
+ const char *p, *l;
+
+ if ((h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
+ fprintf(h->fp, "%s: ", h->_o.tv->test->name);
+ if (!(h->f&HOF_COLOUR))
+ dstr_write(&h->scoreboard, h->fp);
+ else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
+ write_scoreboard_char(h, *p);
+ fflush(h->fp); h->f |= HOF_PROGRESS;
+ }
+}
+
+static void report_location(struct human_output *h, FILE *fp,
+ const char *file, unsigned lno)
+{
+ unsigned f = 0;
+#define f_flush 1u
+
+#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
+
+ if (fp != h->fp) f |= f_flush;
+
+ if (file) {
+ setattr(h, HFG(CYAN)); FLUSH(h->fp); fputs(file, fp); FLUSH(fp);
+ setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
+ setattr(h, HFG(CYAN)); FLUSH(h->fp); fprintf(fp, "%u", lno); FLUSH(fp);
+ setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
+ setattr(h, 0); FLUSH(h->fp); fputc(' ', fp);
+ }
+
+#undef f_flush
+#undef FLUSH
+}
+
+static void human_report(struct human_output *h,
+ const char *msg, va_list *ap)
+{
+ struct tvec_state *tv = h->_o.tv;
+
+ fprintf(stderr, "%s: ", QUIS);
+ report_location(h, stderr, tv->infile, tv->lno);
+ vfprintf(stderr, msg, *ap);
+ fputc('\n', stderr);
+
+ if (h->f&HOF_DUPERR) {
+ report_location(h, stderr, tv->infile, tv->lno);
+ vfprintf(h->fp, msg, *ap);
+ fputc('\n', h->fp);
+ }
+}
+
+static void human_error(struct tvec_output *o, const char *msg, va_list *ap)
+{
+ struct human_output *h = (struct human_output *)o;
+
+ if (h->f&HOF_PROGRESS) fputc('\n', h->fp);
+ human_report(h, msg, ap);
+}
+
+static void human_notice(struct tvec_output *o, const char *msg, va_list *ap)
+{
+ struct human_output *h = (struct human_output *)o;
+ clear_progress(h); human_report(h, msg, ap); 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)
+{
+ if (n) {
+ fprintf(h->fp, " (%u ", n);
+ setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
+ fputc(')', h->fp);
+ }
+}
+
+static int human_esession(struct tvec_output *o)
+{
+ struct human_output *h = (struct human_output *)o;
+ struct tvec_state *tv = h->_o.tv;
+ unsigned
+ all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
+ all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
+ all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
+ all_run = all_win + all_lose, grps_run = grps_win + grps_lose;
+
+ if (!all_lose) {
+ setattr(h, HA_WIN); fputs("PASSED", h->fp); setattr(h, 0);
+ fprintf(h->fp, " %s%u %s",
+ !(all_skip || grps_skip) ? "all " : "",
+ all_win, all_win == 1 ? "test" : "tests");
+ report_skipped(h, all_skip);
+ fprintf(h->fp, " in %u %s",
+ grps_win, grps_win == 1 ? "group" : "groups");
+ report_skipped(h, grps_skip);
+ } else {
+ setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
+ fprintf(h->fp, " %u out of %u %s",
+ all_lose, all_run, all_run == 1 ? "test" : "tests");
+ report_skipped(h, all_skip);
+ fprintf(h->fp, " in %u out of %u %s",
+ grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
+ report_skipped(h, grps_skip);
+ }
+ fputc('\n', h->fp);
+
+ return (tv->all[TVOUT_LOSE] ? 1 : 0);
+}
+
+static void human_bgroup(struct tvec_output *o)
+{
+ struct human_output *h = (struct human_output *)o;
+ dstr_reset(&h->scoreboard); show_progress(h);
+}
+
+static void human_grpsumm(struct human_output *h, unsigned outcome)
+{
+ struct tvec_state *tv = h->_o.tv;
+ unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE],
+ skip = tv->curr[TVOUT_SKIP], run = win + lose;
+
+ if (lose) {
+ assert(outcome == TVOUT_LOSE);
+ fprintf(h->fp, " %u/%u ", lose, run);
+ setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
+ report_skipped(h, skip);
+ } else {
+ assert(outcome == TVOUT_WIN);
+ fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0);
+ report_skipped(h, skip);
+ }
+ fputc('\n', h->fp);
+}
+
+static void human_egroup(struct tvec_output *o, unsigned outcome)
+{
+ struct human_output *h = (struct human_output *)o;
+
+ if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
+ else fprintf(h->fp, "%s:", h->_o.tv->test->name);
+ human_grpsumm(h, outcome);
+}
+
+static void human_skipgroup(struct tvec_output *o,
+ const char *excuse, va_list *ap)
+{
+ struct human_output *h = (struct human_output *)o;
+
+ if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
+ h->f &= ~HOF_PROGRESS;
+ setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
+ } else {
+ fprintf(h->fp, "%s: ", h->_o.tv->test->name);
+ setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
+ }
+ if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
+ fputc('\n', h->fp);
+}
+
+static void human_btest(struct tvec_output *o)
+ { struct human_output *h = (struct human_output *)o; show_progress(h); }
+
+static void human_skip(struct tvec_output *o,
+ const char *excuse, va_list *ap)
+{
+ struct human_output *h = (struct human_output *)o;
+ struct tvec_state *tv = h->_o.tv;
+
+ clear_progress(h);
+ report_location(h, h->fp, tv->infile, tv->test_lno);
+ fprintf(h->fp, "`%s' ", tv->test->name);
+ setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
+ if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
+ fputc('\n', h->fp);
+}
+
+static void human_fail(struct tvec_output *o,
+ const char *detail, va_list *ap)
+{
+ struct human_output *h = (struct human_output *)o;
+ struct tvec_state *tv = h->_o.tv;
+
+ clear_progress(h);
+ report_location(h, h->fp, tv->infile, tv->test_lno);
+ fprintf(h->fp, "`%s' ", tv->test->name);
+ setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
+ if (detail) { fputs(": ", h->fp); vfprintf(h->fp, detail, *ap); }
+ 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)
+{
+ struct human_output *h = (struct human_output *)o;
+
+ if (h->f&HOF_COLOUR) mismatch(&human_mismatchfns, h->_o.tv);
+ else mismatch(&basic_mismatchfns, h->_o.tv);
+}
+
+static void human_etest(struct tvec_output *o, unsigned outcome)
+{
+ struct human_output *h = (struct human_output *)o;
+ int ch;
+
+ if (h->f&HOF_TTY) {
+ show_progress(h);
+ switch (outcome) {
+ case TVOUT_WIN: ch = '.'; break;
+ case TVOUT_LOSE: ch = 'x'; break;
+ case TVOUT_SKIP: ch = '_'; break;
+ default: abort();
+ }
+ dstr_putc(&h->scoreboard, ch);
+ write_scoreboard_char(h, ch); fflush(h->fp);
+ }
+}
+
+static void human_bbench(struct tvec_output *o)
+{
+ 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);
+}
+
+static void human_ebench(struct tvec_output *o,
+ const struct bench_timing *tm)
+{
+ struct human_output *h = (struct human_output *)o;
+ bench_report(h->_o.tv, tm);
+}
+
+static void human_destroy(struct tvec_output *o)
+{
+ struct human_output *h = (struct human_output *)o;
+
+ if (h->f&HOF_DUPERR) fclose(h->fp);
+ dstr_destroy(&h->scoreboard);
+ xfree(h);
+}
+
+static const struct tvec_outops human_ops = {
+ human_error, human_notice, human_write,
+ human_bsession, human_esession,
+ human_bgroup, human_egroup, human_skipgroup,
+ human_btest, human_skip, human_fail, human_mismatch, human_etest,
+ human_bbench, human_ebench,
+ human_destroy
+};
+
+struct tvec_output *tvec_humanoutput(FILE *fp)
+{
+ struct human_output *h;
+ const char *p;
+
+ h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
+ 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;
+ case 0: break;
+ default:
+ if (isatty(fileno(fp))) h->f |= HOF_TTY;
+ break;
+ }
+ switch (getenv_boolean("TVEC_COLOUR", -1)) {
+ case 1: h->f |= HOF_COLOUR; break;
+ case 0: break;
+ default:
+ if (h->f&HOF_TTY) {
+ p = getenv("TERM");
+ if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
+ }
+ break;
+ }
+
+ dstr_create(&h->scoreboard);
+ return (&h->_o);
+}
+
+/*----- Perl's `Test Anything Protocol' -----------------------------------*/
+
+struct tap_output {
+ struct tvec_output _o;
+ FILE *fp;
+ unsigned f;
+#define TOF_FRESHLINE 1u
+};
+
+static void tap_report(struct tap_output *t, const char *msg, va_list *ap)
+{
+ struct tvec_state *tv = t->_o.tv;
+
+ if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno);
+ vfprintf(t->fp, msg, *ap); fputc('\n', t->fp);
+}
+
+static void tap_error(struct tvec_output *o, const char *msg, va_list *ap)
+{
+ struct tap_output *t = (struct tap_output *)o;
+ fputs("Bail out! ", t->fp); tap_report(t, msg, ap);
+}
+
+static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap)
+{
+ struct tap_output *t = (struct tap_output *)o;
+ fputs("## ", t->fp); tap_report(t, msg, ap);
+}
+
+static void tap_write(struct tvec_output *o, const char *p, size_t sz)
+{
+ struct tap_output *t = (struct tap_output *)o;
+ const char *q, *l = p + sz;
+
+ if (p == l) return;
+ if (t->f&TOF_FRESHLINE) fputs("## ", t->fp);
+ 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);
+ }
+ fwrite(p, 1, l - p, t->fp); t->f &= ~TOF_FRESHLINE;
+}
+
+static void tap_bsession(struct tvec_output *o) { ; }
+
+static unsigned tap_grpix(struct tap_output *t)
+{
+ struct tvec_state *tv = t->_o.tv;
+
+ return (tv->grps[TVOUT_WIN] +
+ tv->grps[TVOUT_LOSE] +
+ tv->grps[TVOUT_SKIP]);
+}
+
+static int tap_esession(struct tvec_output *o)
+{
+ struct tap_output *t = (struct tap_output *)o;
+
+ fprintf(t->fp, "1..%u\n", tap_grpix(t));
+ return (0);
+}
+
+static void tap_bgroup(struct tvec_output *o) { ; }
+
+static void tap_egroup(struct tvec_output *o, unsigned outcome)
+{
+ struct tap_output *t = (struct tap_output *)o;
+ struct tvec_state *tv = t->_o.tv;
+ unsigned
+ grpix = tap_grpix(t),
+ win = tv->curr[TVOUT_WIN],
+ lose = tv->curr[TVOUT_LOSE],
+ skip = tv->curr[TVOUT_SKIP];
+
+ if (lose) {
+ assert(outcome == TVOUT_LOSE);
+ fprintf(t->fp, "not ok %u %s: FAILED %u/%u",
+ grpix, tv->test->name, lose, win + lose);
+ if (skip) fprintf(t->fp, " (skipped %u)", skip);
+ } else {
+ assert(outcome == TVOUT_WIN);
+ fprintf(t->fp, "ok %u %s: passed %u", grpix, tv->test->name, win);
+ if (skip) fprintf(t->fp, " (skipped %u)", skip);
+ }
+ fputc('\n', t->fp);
+}
+
+static void tap_skipgroup(struct tvec_output *o,
+ const char *excuse, va_list *ap)
+{
+ struct tap_output *t = (struct tap_output *)o;
+
+ fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->_o.tv->test->name);
+ if (excuse)
+ { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); }
+ fputc('\n', t->fp);
+}
+
+static void tap_btest(struct tvec_output *o) { ; }
+
+static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
+{
+ struct tap_output *t = (struct tap_output *)o;
+ struct tvec_state *tv = t->_o.tv;
+
+ fprintf(t->fp, "## %s:%u: `%s' skipped",
+ tv->infile, tv->test_lno, tv->test->name);
+ if (excuse) { fputs(": ", t->fp); vfprintf(t->fp, excuse, *ap); }
+ fputc('\n', t->fp);
+}
+
+static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
+{
+ struct tap_output *t = (struct tap_output *)o;
+ struct tvec_state *tv = t->_o.tv;
+
+ fprintf(t->fp, "## %s:%u: `%s' FAILED",
+ tv->infile, tv->test_lno, tv->test->name);
+ if (detail) { fputs(": ", t->fp); vfprintf(t->fp, detail, *ap); }
+ fputc('\n', t->fp);
+}
+
+static void tap_mismatch(struct tvec_output *o)
+ { mismatch(&basic_mismatchfns, o->tv); }
+
+static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
+
+static void tap_bbench(struct tvec_output *o) { ; }
+
+static void tap_ebench(struct tvec_output *o,
+ 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);
+}
+
+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);
+ xfree(t);
+}
+
+static const struct tvec_outops tap_ops = {
+ tap_error, tap_notice, tap_write,
+ tap_bsession, tap_esession,
+ tap_bgroup, tap_egroup, tap_skipgroup,
+ tap_btest, tap_skip, tap_fail, tap_mismatch, tap_etest,
+ tap_bbench, tap_ebench,
+ tap_destroy
+};
+
+struct tvec_output *tvec_tapoutput(FILE *fp)
+{
+ struct tap_output *t;
+
+ t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
+ t->f = TOF_FRESHLINE;
+ t->fp = fp;
+ return (&t->_o);
+}
+
+/*----- Default output ----------------------------------------------------*/
+
+struct tvec_output *tvec_dfltout(FILE *fp)
+{
+ int ttyp = getenv_boolean("TVEC_TTY", -1);
+
+ if (ttyp == -1) ttyp = isatty(fileno(fp));
+ if (ttyp) return (tvec_humanoutput(fp));
+ else return (tvec_tapoutput(fp));
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Types for 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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "buf.h"
+#include "codec.h"
+# include "base32.h"
+# include "base64.h"
+# include "hex.h"
+#include "dstr.h"
+#include "tvec.h"
+
+/*----- Preliminary utilities ---------------------------------------------*/
+
+static int signed_to_buf(buf *b, long i)
+{
+ kludge64 k;
+ unsigned long u;
+
+ u = i;
+ if (i >= 0) ASSIGN64(k, u);
+ else { ASSIGN64(k, ~u); CPL64(k, k); }
+ return (buf_putk64l(b, k));
+}
+
+static int signed_from_buf(buf *b, long *i_out)
+{
+ kludge64 k, lmax, not_lmin;
+
+ ASSIGN64(lmax, LONG_MAX); ASSIGN64(not_lmin, ~(unsigned long)LONG_MIN);
+ if (buf_getk64l(b, &k)) return (-1);
+ if (CMP64(k, <=, lmax)) *i_out = (long)GET64(unsigned long, k);
+ else {
+ CPL64(k, k);
+ if (CMP64(k, <=, not_lmin)) *i_out = -(long)GET64(unsigned long, k) - 1;
+ else return (-1);
+ }
+ return (0);
+}
+
+static int unsigned_to_buf(buf *b, unsigned long u)
+ { kludge64 k; ASSIGN64(k, u); return (buf_putk64l(b, k)); }
+
+static int unsigned_from_buf(buf *b, unsigned long *u_out)
+{
+ kludge64 k, ulmax;
+
+ ASSIGN64(ulmax, ULONG_MAX);
+ if (buf_getk64l(b, &k)) return (-1);
+ if (CMP64(k, >, ulmax)) return (-1);
+ *u_out = GET64(unsigned long, k); return (0);
+}
+
+static int hex_width(unsigned long u)
+{
+ int wd;
+ unsigned long t;
+
+ for (t = u >> 4, wd = 4; t >>= wd, wd *= 2, t; );
+ return (wd/4);
+}
+
+static void check_signed_range(long i,
+ const struct tvec_irange *ir,
+ struct tvec_state *tv)
+{
+ if (ir && (ir->min > i || i > ir->max))
+ tvec_error(tv, "integer %ld out of range (must be in [%ld .. %ld])",
+ i, ir->min, ir->max);
+}
+
+static void check_unsigned_range(unsigned long u,
+ const struct tvec_urange *ur,
+ struct tvec_state *tv)
+{
+ if (ur && (ur->min > u || u > ur->max))
+ tvec_error(tv, "integer %lu out of range (must be in [%lu .. %lu])",
+ u, ur->min, ur->max);
+}
+
+static void parse_signed(long *i_out, const char *p,
+ const struct tvec_irange *ir,
+ struct tvec_state *tv)
+{
+ char *q; const char *pp;
+ int olderr;
+ long i;
+
+ olderr = errno; errno = 0;
+ pp = p; if (*pp == '-' || *pp == '+') pp++;
+ if (!ISDIGIT(*pp)) tvec_syntax(tv, *pp, "signed integer");
+ i = strtol(p, &q, 0);
+ if (*q && !ISSPACE(*q)) tvec_syntax(tv, *q, "end-of-line");
+ if (errno) tvec_error(tv, "invalid integer `%s'", p);
+ check_signed_range(i, ir, tv);
+ errno = olderr; *i_out = i;
+}
+
+static void parse_unsigned(unsigned long *u_out, const char *p,
+ const struct tvec_urange *ur,
+ struct tvec_state *tv)
+{
+ char *q;
+ int olderr;
+ unsigned long u;
+
+ olderr = errno; errno = 0;
+ if (!ISDIGIT(*p)) tvec_syntax(tv, *p, "unsigned integer");
+ u = strtoul(p, &q, 0);
+ if (*q && !ISSPACE(*q)) tvec_syntax(tv, *q, "end-of-line");
+ if (errno) tvec_error(tv, "invalid integer `%s'", p);
+ check_unsigned_range(u, ur, tv);
+ errno = olderr; *u_out = u;
+}
+
+static int convert_hex(char ch, int *v_out)
+{
+ if ('0' <= ch && ch <= '9') { *v_out = ch - '0'; return (0); }
+ else if ('a' <= ch && ch <= 'f') { *v_out = ch - 'a' + 10; return (0); }
+ else if ('A' <= ch && ch <= 'F') { *v_out = ch - 'A' + 10; return (0); }
+ else return (-1);
+}
+
+static void read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
+{
+ char expect[4];
+ int ch, i, esc;
+ unsigned f = 0;
+#define f_brace 1u
+
+ sprintf(expect, "`%c'", quote);
+
+ for (;;) {
+ ch = getc(tv->fp);
+ reinsert:
+ switch (ch) {
+ case EOF: case '\n':
+ tvec_syntax(tv, ch, expect);
+
+ 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)) 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)
+ tvec_error(tv, "character code %d out of range", esc);
+ }
+ DPUTC(d, esc);
+ if (!(f&f_brace)) goto reinsert;
+ else if (ch != '}') 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)
+ tvec_error(tv, "character code %d out of range", esc);
+ DPUTC(d, esc);
+ goto reinsert;
+ }
+ tvec_syntax(tv, ch, "string escape");
+ break;
+ }
+ break;
+
+ default:
+ if (ch == quote) goto end;
+ ordinary:
+ DPUTC(d, ch);
+ break;
+ }
+ }
+
+end:
+ DPUTZ(d);
+
+#undef f_brace
+}
+
+enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 };
+
+static int collect_bare(dstr *d, struct tvec_state *tv)
+{
+ size_t pos = d->len;
+ enum { WORD, SPACE, ESCAPE }; unsigned s = WORD;
+ int ch, rc;
+
+ for (;;) {
+ ch = getc(tv->fp);
+ switch (ch) {
+ case EOF:
+ goto bad;
+ case '\n':
+ if (s == ESCAPE) { tv->lno++; goto addch; }
+ if (s == WORD) pos = d->len;
+ ungetc(ch, tv->fp); if (tvec_nexttoken(tv)) { rc = -1; goto done; }
+ DPUTC(d, ' '); s = SPACE;
+ break;
+ case '"': case '\'': case '!':
+ if (s == SPACE) { ungetc(ch, tv->fp); rc = 0; goto done; }
+ goto addch;
+ case '\\':
+ s = ESCAPE;
+ break;
+ default:
+ if (s != ESCAPE && isspace(ch)) {
+ if (s == WORD) pos = d->len;
+ DPUTC(d, ch); s = SPACE;
+ break;
+ }
+ addch:
+ DPUTC(d, ch); s = WORD;
+ }
+ }
+
+done:
+ if (s == SPACE) d->len = pos;
+ DPUTZ(d); return (rc);
+
+bad:
+ tvec_syntax(tv, ch, "bareword");
+}
+
+static void set_up_encoding(const codec_class **ccl_out, unsigned *f_out,
+ unsigned code)
+{
+ switch (code) {
+ case TVCODE_BARE:
+ *ccl_out = 0; *f_out = 0;
+ break;
+ case TVCODE_HEX:
+ *ccl_out = &hex_class; *f_out = CDCF_IGNCASE;
+ break;
+ case TVCODE_BASE32:
+ *ccl_out = &base32_class; *f_out = CDCF_IGNCASE | CDCF_IGNEQPAD;
+ break;
+ case TVCODE_BASE64:
+ *ccl_out = &base64_class; *f_out = CDCF_IGNEQPAD;
+ break;
+ default:
+ abort();
+ }
+}
+
+static void read_compound_string(void **p_inout, size_t *sz_inout,
+ unsigned code, struct tvec_state *tv)
+{
+ const codec_class *ccl; unsigned f;
+ codec *cdc;
+ dstr d = DSTR_INIT, w = DSTR_INIT;
+ char *p;
+ int ch, err;
+
+ set_up_encoding(&ccl, &f, code);
+ if (tvec_nexttoken(tv)) tvec_syntax(tv, fgetc(tv->fp), "string");
+ do {
+ ch = getc(tv->fp);
+ if (ch == '"' || ch == '\'')
+ read_quoted_string(&d, ch, tv);
+ else if (ch == '!') {
+ ungetc(ch, tv->fp);
+ DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword");
+ if (STRCMP(w.buf, ==, "!bare")) code = TVCODE_BARE;
+ else if (STRCMP(w.buf, ==, "!hex")) code = TVCODE_HEX;
+ else if (STRCMP(w.buf, ==, "!base32")) code = TVCODE_BASE32;
+ else if (STRCMP(w.buf, ==, "!base64")) code = TVCODE_BASE64;
+ else tvec_error(tv, "unknown string keyword `%s'", w.buf);
+ set_up_encoding(&ccl, &f, code);
+ } else if (ccl) {
+ ungetc(ch, tv->fp);
+ DRESET(&w);
+ tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name);
+ cdc = ccl->decoder(f);
+ err = cdc->ops->code(cdc, w.buf, w.len, &d);
+ if (!err) err = cdc->ops->code(cdc, 0, 0, &d);
+ if (err)
+ tvec_error(tv, "invalid %s fragment `%s': %s",
+ ccl->name, w.buf, codec_strerror(err));
+ cdc->ops->destroy(cdc);
+ } else switch (code) {
+ case TVCODE_BARE:
+ ungetc(ch, tv->fp);
+ if (collect_bare(&d, tv)) goto done;
+ break;
+ default:
+ abort();
+ }
+ } while (!tvec_nexttoken(tv));
+
+done:
+ if (*sz_inout <= d.len)
+ { xfree(*p_inout); *p_inout = xmalloc(d.len + 1); }
+ p = *p_inout; memcpy(p, d.buf, d.len); p[d.len] = 0; *sz_inout = d.len;
+ dstr_destroy(&d); dstr_destroy(&w);
+}
+
+/*----- Skeleton ----------------------------------------------------------*/
+/*
+static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd)
+static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd)
+static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+static size_t measure_...(const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+static int tobuf_...(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+static int frombuf_...(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+static void parse_...(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+static 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_...,
+ tobuf_..., frombuf_...,
+ parse_..., dump_...
+};
+*/
+/*----- Signed and unsigned integer types ---------------------------------*/
+
+static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->i = 0; }
+
+static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->u = 0; }
+
+static void release_int(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { ; }
+
+static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+ { return (rv0->i == rv1->i); }
+
+static int eq_uint(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ 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)); }
+
+static int tobuf_uint(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (unsigned_to_buf(b, rv->u)); }
+
+static int frombuf_int(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return signed_from_buf(b, &rv->i); }
+
+static int frombuf_uint(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (unsigned_from_buf(b, &rv->u)); }
+
+static void parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ dstr d = DSTR_INIT;
+
+ tvec_readword(tv, &d, ";", "signed integer");
+ parse_signed(&rv->i, d.buf, rd->arg.p, tv);
+ tvec_flushtoeol(tv, 0);
+ dstr_destroy(&d);
+}
+
+static void parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ dstr d = DSTR_INIT;
+
+ tvec_readword(tv, &d, ";", "unsigned integer");
+ parse_unsigned(&rv->u, d.buf, rd->arg.p, tv);
+ tvec_flushtoeol(tv, 0);
+ dstr_destroy(&d);
+}
+
+static void dump_int(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ unsigned long u;
+
+ tvec_write(tv, "%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);
+ }
+}
+
+static void dump_uint(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ tvec_write(tv, "%lu", rv->u);
+ if (!(style&TVSF_COMPACT))
+ tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
+}
+
+const struct tvec_regty tvty_int = {
+ init_int, release_int, eq_int, measure_int,
+ tobuf_int, frombuf_int,
+ parse_int, dump_int
+};
+
+const struct tvec_irange
+ tvrange_schar = { SCHAR_MIN, SCHAR_MAX },
+ tvrange_short = { SHRT_MIN, SHRT_MAX },
+ tvrange_int = { INT_MIN, INT_MAX },
+ tvrange_long = { LONG_MIN, LONG_MAX },
+ tvrange_sbyte = { -128, 127 },
+ tvrange_i16 = { -32768, +32767 },
+ tvrange_i32 = { -2147483648, 2147483647 };
+
+const struct tvec_regty tvty_uint = {
+ init_uint, release_int, eq_uint, measure_int,
+ tobuf_uint, frombuf_uint,
+ parse_uint, dump_uint
+};
+
+const struct tvec_urange
+ tvrange_uchar = { 0, UCHAR_MAX },
+ tvrange_ushort = { 0, USHRT_MAX },
+ tvrange_uint = { 0, UINT_MAX },
+ tvrange_ulong = { 0, ULONG_MAX },
+ tvrange_size = { 0, (size_t)-1 },
+ tvrange_byte = { 0, 255 },
+ tvrange_u16 = { 0, 65535 },
+ tvrange_u32 = { 0, 4294967296 };
+
+int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.i = i0; tv->out[0].v.i = i1;
+ return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr));
+}
+
+int tvec_claimeq_uint(struct tvec_state *tv,
+ unsigned long u0, unsigned long u1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.u = u0; tv->out[0].v.u = u1;
+ return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr));
+}
+
+/*----- Enumerations ------------------------------------------------------*/
+
+static void init_enum(union tvec_regval *rv, const struct tvec_regdef *rd)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: rv->slot = 0; break;
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+ default: abort();
+ }
+}
+
+static int eq_enum(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: return (rv0->slot == rv1->slot);
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+ default: abort();
+ }
+}
+
+static int tobuf_enum(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+
+ 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
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef HANDLE_INT
+#undef HANDLE_UINT
+#undef HANDLE_PTR
+ default: abort();
+ }
+ return (0);
+}
+
+static int frombuf_enum(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+
+ 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
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef HANDLE_INT
+#undef HANDLE_UINT
+#undef HANDLE_PTR
+ default: abort();
+ }
+}
+
+static void parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+#define DECLS(tag, ty, slot) \
+ const struct tvec_##slot##assoc *slot##a;
+ TVEC_MISCSLOTS(DECLS)
+#undef DECLS
+ dstr d = DSTR_INIT;
+
+ tvec_readword(tv, &d, ";", "enumeration tag or literal integer");
+ switch (ei->mv) {
+#define CASE(tag_, ty, slot) \
+ case TVMISC_##tag_: \
+ for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++) \
+ if (STRCMP(d.buf, ==, slot##a->tag)) \
+ { rv->slot = FETCH_##tag_; goto end; }
+#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
+ }
+
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: HANDLE_##tag goto end;
+#define HANDLE_INT parse_signed(&rv->i, d.buf, ei->u.i.ir, tv);
+#define HANDLE_UINT parse_unsigned(&rv->u, d.buf, ei->u.u.ur, tv);
+#define HANDLE_PTR if (STRCMP(d.buf, ==, "#nil")) rv->p = 0; \
+ else goto tagonly;
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef HANDLE_INT
+#undef HANDLE_UINT
+#undef HANDLE_PTR
+ default: tagonly:
+ tvec_error(tv, "unknown `%s' value `%s'", ei->name, d.buf);
+ }
+
+end:
+ tvec_flushtoeol(tv, 0);
+ dstr_destroy(&d);
+}
+
+static void dump_enum(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+#define DECLS(tag, ty, slot) \
+ const struct tvec_##slot##assoc *slot##a;
+ TVEC_MISCSLOTS(DECLS)
+#undef DECLS
+ const char *tag;
+ unsigned long u;
+ unsigned f = 0;
+#define f_known 1u
+
+ switch (ei->mv) {
+#define CASE(tag_, ty, slot) \
+ case TVMISC_##tag_: \
+ for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++) \
+ if (rv->slot == slot##a->slot) \
+ { tag = slot##a->tag; goto found; } \
+ break;
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+ default: abort();
+ }
+ goto print_int;
+
+found:
+ f |= f_known;
+ tvec_write(tv, "%s", tag);
+ if (style&TVSF_COMPACT) return;
+ tvec_write(tv, " ; = ");
+
+print_int:
+ 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);
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef HANDLE_INT
+#undef HANDLE_UINT
+#undef HANDLE_PTR
+ }
+
+ switch (ei->mv) {
+ case TVMISC_INT:
+ if (!(f&f_known)) tvec_write(tv, " ;");
+ 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);
+ break;
+ case TVMISC_UINT:
+ if (!(f&f_known)) tvec_write(tv, " ;");
+ tvec_write(tv, " = 0x%0*lx", hex_width(rv->u), rv->u);
+ break;
+ }
+}
+
+const struct tvec_regty tvty_enum = {
+ init_enum, release_int, eq_enum, measure_int,
+ tobuf_enum, frombuf_enum,
+ parse_enum, dump_enum
+};
+
+#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) \
+ { \
+ union tvec_misc arg; \
+ \
+ assert(ei->mv == TVMISC_##tag); \
+ arg.p = ei; \
+ tv->in[0].v.slot = GET_##tag(e0); \
+ tv->out[0].v.slot = GET_##tag(e1); \
+ return (tvec_claimeq(tv, &tvty_enum, &arg, file, lno, expr)); \
+ }
+#define GET_INT(e) (e)
+#define GET_UINT(e) (e)
+#define GET_PTR(e) ((/*unconst*/ void *)(e))
+TVEC_MISCSLOTS(DEFCLAIM)
+#undef DEFCLAIM
+#undef GET_INT
+#undef GET_UINT
+#undef GET_PTR
+
+/*----- Flag types --------------------------------------------------------*/
+
+static void parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ const struct tvec_flaginfo *fi = rd->arg.p;
+ const struct tvec_flag *f;
+ unsigned long m = 0, v = 0, t;
+ dstr d = DSTR_INIT;
+ int ch;
+
+ for (;;) {
+ DRESET(&d); tvec_readword(tv, &d, "|;", "flag name or integer");
+
+ for (f = fi->fv; f->tag; f++)
+ if (STRCMP(f->tag, ==, d.buf)) {
+ if (m&f->m) tvec_error(tv, "colliding flag setting");
+ else { m |= f->m; v |= f->v; goto next; }
+ }
+
+ parse_unsigned(&t, d.buf, fi->range, tv); v |= t;
+ next:
+ if (tvec_nexttoken(tv)) break;
+ ch = getc(tv->fp); if (ch != '|') tvec_syntax(tv, ch, "`|'");
+ if (tvec_nexttoken(tv)) tvec_syntax(tv, '\n', "flag name or integer");
+ }
+ rv->u = v;
+}
+
+static void dump_flags(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ const struct tvec_flaginfo *fi = rd->arg.p;
+ const struct tvec_flag *f;
+ unsigned long m = ~(unsigned long)0, v = rv->u;
+ const char *sep;
+
+ 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;
+ sep = style&TVSF_COMPACT ? "|" : " | ";
+ }
+
+ if (v&m) tvec_write(tv, "%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);
+}
+
+const struct tvec_regty tvty_flags = {
+ init_uint, release_int, eq_uint, measure_int,
+ tobuf_uint, frombuf_uint,
+ parse_flags, dump_flags
+};
+
+int tvec_claimeq_flags(struct tvec_state *tv,
+ const struct tvec_flaginfo *fi,
+ unsigned long f0, unsigned long f1,
+ const char *file, unsigned lno, const char *expr)
+{
+ union tvec_misc arg;
+
+ arg.p = fi; tv->in[0].v.u = f0; tv->out[0].v.u = f1;
+ return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr));
+}
+
+/*----- Text and byte strings ---------------------------------------------*/
+
+void tvec_allocstring(union tvec_regval *rv, size_t sz)
+{
+ if (rv->str.sz < sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz); }
+ rv->str.sz = sz;
+}
+
+void tvec_allocbytes(union tvec_regval *rv, size_t sz)
+{
+ if (rv->bytes.sz < sz) { xfree(rv->bytes.p); rv->bytes.p = xmalloc(sz); }
+ rv->bytes.sz = sz;
+}
+
+static void init_string(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->str.p = 0; rv->str.sz = 0; }
+
+static void init_bytes(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->bytes.p = 0; rv->bytes.sz = 0; }
+
+static void release_string(union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { xfree(rv->str.p); }
+
+static void release_bytes(union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { xfree(rv->bytes.p); }
+
+static int eq_string(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+{
+ return (rv0->str.sz == rv1->str.sz &&
+ (!rv0->bytes.sz ||
+ MEMCMP(rv0->str.p, ==, rv1->str.p, rv1->str.sz)));
+}
+
+static int eq_bytes(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+{
+ return (rv0->bytes.sz == rv1->bytes.sz &&
+ (!rv0->bytes.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 int tobuf_bytes(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (buf_putmem32l(b, rv->bytes.p, rv->bytes.sz)); }
+
+static int frombuf_string(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const void *p;
+ size_t sz;
+
+ p = buf_getmem32l(b, &sz); if (!p) return (-1);
+ tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz);
+ return (0);
+}
+
+static int frombuf_bytes(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const void *p;
+ size_t sz;
+
+ p = buf_getmem32l(b, &sz); if (!p) return (-1);
+ tvec_allocbytes(rv, sz); memcpy(rv->bytes.p, p, sz);
+ return (0);
+}
+
+static void check_string_length(size_t sz, const struct tvec_urange *ur,
+ struct tvec_state *tv)
+{
+ if (ur && (ur->min > sz || sz > ur->max))
+ tvec_error(tv, "invalid string length %lu; must be in [%lu..%lu]",
+ (unsigned long)sz, ur->min, ur->max);
+}
+
+static void parse_string(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ void *p = rv->str.p;
+
+ read_compound_string(&p, &rv->str.sz, TVCODE_BARE, tv); rv->str.p = p;
+ check_string_length(rv->str.sz, rd->arg.p, tv);
+}
+
+static void parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ void *p = rv->bytes.p;
+
+ read_compound_string(&p, &rv->bytes.sz, TVCODE_HEX, tv); rv->bytes.p = p;
+ check_string_length(rv->bytes.sz, rd->arg.p, tv);
+}
+
+static void dump_string(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ 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; }
+
+ 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; }
+ else if (f&f_nonword) goto quote;
+ tv->output->ops->write(tv->output, (const char *)p, rv->str.sz); return;
+
+quote:
+ tvec_write(tv, "\"");
+ 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 (p < q) tv->output->ops->write(tv->output, (const char *)p, q - p);
+ tvec_write(tv, "\"");
+
+#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)
+{
+ const unsigned char *p = rv->bytes.p, *l = p + rv->bytes.sz;
+ size_t off, sz = rv->bytes.sz;
+ unsigned i, n;
+ int wd;
+
+ if (!sz) {
+ tvec_write(tv, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
+ return;
+ }
+
+ if (style&TVSF_COMPACT) {
+ while (p < l) tvec_write(tv, "%02x", *p++);
+ return;
+ }
+
+ if (sz > 16) tvec_write(tv, "\n\t");
+
+ off = 0; wd = hex_width(sz);
+ while (p < l) {
+ if (l - p < 16) n = l - p;
+ 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, " ");
+ }
+ tvec_write(tv, " ; ");
+ if (sz > 16) tvec_write(tv, "[%0*lx] ", wd, (unsigned long)off);
+ for (i = 0; i < n; i++)
+ tvec_write(tv, "%c", isprint(p[i]) ? p[i] : '.');
+ p += n; off += n;
+ if (p < l) tvec_write(tv, "\n\t");
+ }
+}
+
+const struct tvec_regty tvty_string = {
+ init_string, release_string, eq_string, measure_string,
+ tobuf_string, frombuf_string,
+ parse_string, dump_string
+};
+
+const struct tvec_regty tvty_bytes = {
+ init_bytes, release_bytes, eq_bytes, measure_bytes,
+ tobuf_bytes, frombuf_bytes,
+ parse_bytes, dump_bytes
+};
+
+int tvec_claimeq_string(struct tvec_state *tv,
+ const char *p0, size_t sz0,
+ const char *p1, size_t sz1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.str.p = (/*unconst*/ char *)p0; tv->in[0].v.str.sz = sz0;
+ tv->out[0].v.str.p =(/*unconst*/ char *) p1; tv->out[0].v.str.sz = sz1;
+ return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
+}
+
+int tvec_claimeq_strz(struct tvec_state *tv,
+ const char *p0, const char *p1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.str.p = (/*unconst*/ char *)p0;
+ tv->in[0].v.str.sz = strlen(p0);
+ tv->out[0].v.str.p = (/*unconst*/ char *)p1;
+ tv->out[0].v.str.sz = strlen(p1);
+ return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
+}
+
+int tvec_claimeq_bytes(struct tvec_state *tv,
+ const void *p0, size_t sz0,
+ const void *p1, size_t sz1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.bytes.p = (/*unconst*/ void *)p0;
+ tv->in[0].v.bytes.sz = sz0;
+ tv->out[0].v.bytes.p = (/*unconst*/ void *)p1;
+ tv->out[0].v.bytes.sz = sz1;
+ return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr));
+}
+
+/*----- Buffer type -------------------------------------------------------*/
+
+static int eq_buffer(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+ { return (rv0->bytes.sz == rv1->bytes.sz); }
+
+static int tobuf_buffer(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (unsigned_to_buf(b, rv->bytes.sz)); }
+
+static int frombuf_buffer(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ unsigned long u;
+
+ if (unsigned_from_buf(b, &u)) return (-1);
+ if (u > (size_t)-1) return (-1);
+ tvec_allocbytes(rv, u); memset(rv->bytes.p, '!', u);
+ return (0);
+}
+
+static const char units[] = "kMGTPEZY";
+
+static void parse_buffer(union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ dstr d = DSTR_INIT;
+ char *q; const char *unit;
+ int olderr;
+ size_t pos;
+ unsigned long u, t;
+ unsigned f = 0;
+#define f_range 1u
+
+ tvec_readword(tv, &d, ";", "buffer length");
+ olderr = errno; errno = 0;
+ u = strtoul(d.buf, &q, 0);
+ if (errno) goto bad;
+ errno = olderr;
+ if (!*q) {
+ tvec_skipspc(tv); pos = d.len;
+ if (!tvec_readword(tv, &d, ";", 0)) pos++;
+ q = d.buf + pos;
+ }
+
+ if (u > (size_t)-1) goto rangerr;
+ for (t = u, unit = units; *unit; unit++) {
+ if (t > (size_t)-1/1024) f |= f_range;
+ else t *= 1024;
+ if (*q == *unit && (!q[1] || q[1] == 'B')) {
+ if (f&f_range) goto rangerr;
+ u = t; q += 2; break;
+ }
+ }
+ if (*q && *q != ';') goto bad;
+ check_string_length(u, rd->arg.p, tv);
+
+ tvec_flushtoeol(tv, 0);
+ tvec_allocbytes(rv, u); memset(rv->bytes.p, 0, u);
+ DDESTROY(&d); return;
+
+bad:
+ tvec_error(tv, "invalid buffer length `%s'", d.buf);
+
+rangerr:
+ tvec_error(tv, "buffer length `%s' out of range", d.buf);
+
+#undef f_range
+}
+
+static void dump_buffer(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ const char *unit;
+ unsigned long u = rv->bytes.sz;
+
+ if (!u || u%1024)
+ tvec_write(tv, "%lu B", u);
+ else {
+ for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++);
+ tvec_write(tv, "%lu %cB", u, *unit);
+ }
+}
+
+const struct tvec_regty tvty_buffer = {
+ init_bytes, release_bytes, eq_buffer, measure_int,
+ tobuf_buffer, frombuf_buffer,
+ parse_buffer, dump_buffer
+};
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Test vector processing 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.
+ */
+
+#ifndef MLIB_TVEC_H
+#define MLIB_TVEC_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifndef MLIB_BUF_H
+# include "buf.h"
+#endif
+
+#ifndef MLIB_CONTROL_H
+# include "control.h"
+#endif
+
+#ifndef MLIB_BUF_H
+# include "dstr.h"
+#endif
+
+#ifndef MLIB_MACROS_H
+# include "macros.h"
+#endif
+
+/*----- Miscellaneous values ----------------------------------------------*/
+
+/* These are attached to structures which represent extension points, as a
+ * way to pass an opaque parameter to whatever things are hooked onto them.
+ */
+
+#define TVEC_MISCSLOTS(_) \
+ _(PTR, const void *, p) /* arbitrary pointer */ \
+ _(INT, long, i) /* signed integer */ \
+ _(UINT, unsigned long, u) /* signed integer */
+
+union tvec_misc {
+#define TVEC_DEFSLOT(tag, ty, slot) ty slot;
+ TVEC_MISCSLOTS(TVEC_DEFSLOT)
+#undef TVEC_DEFSLOT
+};
+enum {
+#define TVEC_DEFCONST(tag, ty, slot) TVMISC_##tag,
+ TVEC_MISCSLOTS(TVEC_DEFCONST)
+ TVMISC_LIMIT
+};
+
+/*----- Register values ---------------------------------------------------*/
+
+/* The framework doesn't have a preconceived idea about what's in a register
+ * value: it just allocates them and accesses them through the register type
+ * functions. It doesn't even have a baked-in idea of how big a register
+ * value is: instead, it gets that via the `regsz' slot in `struct
+ * tvec_testinfo'. So, as far as the framework is concerned, it's safe to
+ * add new slots to this union, even if they make the overall union larger.
+ * This can be done by defining the preprocessor macro `TVEC_REGSLOTS' to be
+ * a `union' fragment defining any additional union members.
+ *
+ * This creates a distinction between code which does and doesn't know the
+ * size of a register value. Code which does, which typically means the test
+ * functions, benchmarking setup and teardown functions, and tightly-bound
+ * runner functions, is free to index the register vectors directly. Code
+ * which doesn't, which means the framework core itself and output formatting
+ * machinery, must use the `TVEC_REG' macro (or its more general `TVEC_GREG'
+ * companion) for indexing register vectors. (In principle, register type
+ * handlers also fit into this category, but they have no business peering
+ * into register values other than the one's they're given.)
+ */
+
+union tvec_regval {
+ /* The actual register value. This is what the type handler sees.
+ * Additional members can be added by setting `TVEC_REGSLOTS' before
+ * including this file.
+ *
+ * A register value can be /initialized/, which simply means that its
+ * contents represent a valid value according to its type -- the
+ * register can be compared, dumped, serialized, parsed into, etc.
+ * You can't do anything safely to an uninitialized register value
+ * other than initialize it.
+ */
+
+ long i; /* signed integer */
+ unsigned long u; /* unsigned integer */
+ void *p; /* pointer */
+ struct { unsigned char *p; size_t sz; } bytes; /* binary string of bytes */
+ struct { char *p; size_t sz; } str; /* text string */
+#ifdef TVEC_REGSLOTS
+ TVEC_REGSLOTS
+#endif
+};
+
+struct tvec_reg {
+ /* A register.
+ *
+ * Note that all of the registers listed as being used by a
+ * particular test group are initialized at all times[1] while that
+ * test group is being processed. (The other register slots don't
+ * even have types associated with them, so there's nothing useful we
+ * could do with them.)
+ *
+ * The `TVRF_LIVE' flag indicates that the register was assigned a
+ * value by the test vector file: it's the right thing to use to
+ * check whether an optional register is actually present. Even
+ * `dead' registers are still initialized, though.
+ *
+ * [1] This isn't quite true. Between individual tests, the
+ * registers are released and reinitialized in order to reset
+ * them to known values ready for the next test. But you won't
+ * see them at this point.
+ */
+
+ unsigned f; /* flags */
+#define TVRF_LIVE 1u /* used in current test */
+ union tvec_regval v; /* register value */
+};
+
+struct tvec_regdef {
+ /* A register definition. Register definitions list the registers
+ * which are used by a particular test group (see `struct tvec_test'
+ * below).
+ *
+ * A vector of register definitions is terminated by a definition
+ * whose `name' slot is null.
+ */
+
+ const char *name; /* register name (for input files) */
+ unsigned i; /* register index */
+ const struct tvec_regty *ty; /* register type descriptor */
+ unsigned f; /* flags */
+#define TVRF_OPT 1u /* optional register */
+#define TVRF_ID 2u /* part of test identity */
+ union tvec_misc arg; /* extra detail for the type */
+};
+
+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*/);
+
+/*----- Test state --------------------------------------------------------*/
+
+enum { TVOUT_LOSE, TVOUT_SKIP, TVOUT_WIN, TVOUT_LIMIT };
+
+struct tvec_state {
+ unsigned f; /* flags */
+#define TVSF_SKIP 1u /* skip this test group */
+#define TVSF_OPEN 2u /* test is open */
+#define TVSF_ACTIVE 4u /* test is active */
+#define TVSF_OUTMASK 0xf0 /* test outcome */
+#define TVSF_OUTSHIFT 4
+ 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 */
+ const struct tvec_test *tests, *test; /* all tests and current test */
+ unsigned curr[TVOUT_LIMIT], all[TVOUT_LIMIT], grps[TVOUT_LIMIT];
+ struct tvec_output *output; /* output formatter */
+ 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)))
+#define TVEC_REG(tv, vec, i) TVEC_GREG((tv)->vec, (i), (tv)->regsz)
+
+/*----- Test descriptions -------------------------------------------------*/
+
+typedef void tvec_hookfn(struct tvec_state */*tv*/);
+typedef void tvec_testfn(const struct tvec_reg */*in*/,
+ struct tvec_reg */*out*/,
+ void */*ctx*/);
+
+struct tvec_test {
+ 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 */
+ tvec_testfn *fn; /* test function */
+ union tvec_misc arg; /* additional parameter to `run' */
+};
+
+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*/);
+
+extern void tvec_runtest(struct tvec_state */*tv*/);
+
+/*----- Input utilities ---------------------------------------------------*/
+
+extern void tvec_skipspc(struct tvec_state */*tv*/);
+
+#define TVFF_ALLOWANY 1u
+extern void tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/);
+
+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 int tvec_nexttoken(struct tvec_state */*tv*/);
+
+/*----- Session lifecycle -------------------------------------------------*/
+
+struct tvec_info {
+ const struct tvec_test *tests;
+ unsigned nrout, nreg;
+ size_t regsz;
+};
+
+extern void tvec_begin(struct tvec_state */*tv_out*/,
+ const struct tvec_info */*info*/,
+ struct tvec_output */*o*/);
+extern int tvec_end(struct tvec_state */*tv*/);
+
+extern void tvec_read(struct tvec_state */*tv*/,
+ const char */*infile*/, FILE */*fp*/);
+
+
+/*----- Benchmarking ------------------------------------------------------*/
+
+struct tvec_bench {
+ 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 */
+};
+
+extern struct bench_state *tvec_benchstate;
+
+extern int tvec_ensurebench(struct tvec_state */*tv*/,
+ struct bench_state **/*b_out*/);
+extern void tvec_bench(struct tvec_state */*tv*/);
+
+/*----- Ad-hoc testing ----------------------------------------------------*/
+
+extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/);
+
+extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/,
+ const char */*file*/, unsigned /*lno*/);
+extern void tvec_reportgroup(struct tvec_state */*tv*/);
+extern void tvec_endgroup(struct tvec_state */*tv*/);
+
+#define TVEC_BEGINGROUP(tv, name) \
+ do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0)
+
+#define TVEC_TESTGROUP(tag, tv, name) \
+ MC_WRAP(tag##__around, \
+ { TVEC_BEGINGROUP(tv, name); }, \
+ { tvec_endgroup(tv); }, \
+ { if (!((tv)->f&TVSF_SKIP)) tvec_skipgroup(tv, 0); \
+ tvec_endgroup(tv); })
+
+extern void tvec_begintest(struct tvec_state */*tv*/,
+ const char */*file*/, unsigned /*lno*/);
+extern void tvec_endtest(struct tvec_state */*tv*/);
+
+#define TVEC_BEGINTEST(tv) \
+ do tvec_begintest(tv, __FILE__, __LINE__); while (0)
+
+#define TVEC_TEST(tag, tv) \
+ MC_WRAP(tag##__around, \
+ { TVEC_BEGINTEST(tv); }, \
+ { tvec_endtest(tv); }, \
+ { if ((tv)->f&TVSF_ACTIVE) tvec_skipgroup((tv), 0); \
+ tvec_endtest(tv); })
+
+extern int PRINTF_LIKE(5, 6)
+ tvec_claim(struct tvec_state */*tv*/, int /*ok*/,
+ const char */*file*/, unsigned /*lno*/,
+ const char */*expr*/, ...);
+
+#define TVEC_CLAIM(tv, cond) \
+ (tvec_claim(tv, !!(cond), __FILE__, __LINE__, #cond " untrue"))
+
+extern int tvec_claimeq(struct tvec_state */*tv*/,
+ const struct tvec_regty */*ty*/,
+ const union tvec_misc */*arg*/,
+ const char */*file*/, unsigned /*lno*/,
+ const char */*expr*/);
+
+/*----- Command-line interface --------------------------------------------*/
+
+extern const struct tvec_info tvec_adhocinfo;
+
+extern void tvec_parseargs(int /*argc*/, char */*argv*/[],
+ struct tvec_state */*tv_out*/,
+ int */*argpos_out*/,
+ const struct tvec_info */*info*/);
+
+extern void tvec_readstdin(struct tvec_state */*tv*/);
+extern void tvec_readfile(struct tvec_state */*tv*/, const char */*file*/);
+extern void tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/);
+extern void tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/);
+
+extern void tvec_readargs(int /*argc*/, char */*argv*/[],
+ struct tvec_state */*tv*/,
+ int */*argpos_inout*/, const char */*dflt*/);
+
+extern int tvec_main(int /*argc*/, char */*argv*/[],
+ const struct tvec_info */*info*/,
+ const char */*dflt*/);
+
+/*----- Output formatting -------------------------------------------------*/
+
+struct tvec_output {
+ const struct tvec_outops *ops;
+ struct tvec_state *tv;
+};
+
+struct bench_timing;
+
+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*/);
+
+ void (*bgroup)(struct tvec_output */*o*/);
+ void (*egroup)(struct tvec_output */*o*/, unsigned /*outcome*/);
+ void (*skipgroup)(struct tvec_output */*o*/,
+ const char */*excuse*/, va_list */*ap*/);
+
+ void (*btest)(struct tvec_output */*o*/);
+ void (*skip)(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 (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/);
+
+ void (*bbench)(struct tvec_output */*o*/);
+ void (*ebench)(struct tvec_output */*o*/,
+ const struct bench_timing */*tm*/);
+
+ void (*destroy)(struct tvec_output */*o*/);
+};
+
+extern void PRINTF_LIKE(2, 3) NORETURN
+ tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...);
+extern void NORETURN tvec_error_v(struct tvec_state */*tv*/,
+ const char */*msg*/, va_list */*ap*/);
+
+extern void PRINTF_LIKE(2, 3)
+ tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...);
+extern void tvec_notice_v(struct tvec_state */*tv*/,
+ const char */*msg*/, va_list */*ap*/);
+
+extern void PRINTF_LIKE(3, 4) NORETURN
+ tvec_syntax(struct tvec_state */*tv*/, int /*ch*/,
+ const char */*expect*/, ...);
+extern void NORETURN 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 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 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_mismatch(struct tvec_state */*tv*/);
+
+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 struct tvec_output *tvec_humanoutput(FILE */*fp*/);
+extern struct tvec_output *tvec_tapoutput(FILE */*fp*/);
+extern struct tvec_output *tvec_dfltout(FILE */*fp*/);
+
+/*----- Register types ----------------------------------------------------*/
+
+struct tvec_regty {
+ void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/);
+ void (*release)(union tvec_regval */*rv*/,
+ const struct tvec_regdef */*rd*/);
+ 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*/,
+ const struct tvec_regdef */*rd*/);
+ void (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/,
+ struct tvec_state */*tv*/);
+ void (*dump)(const union tvec_regval */*rv*/,
+ const struct tvec_regdef */*rd*/,
+ struct tvec_state */*tv*/, unsigned /*style*/);
+#define TVSF_COMPACT 1u
+};
+
+extern const struct tvec_regty tvty_int, tvty_uint;
+struct tvec_irange { long min, max; };
+struct tvec_urange { unsigned long min, max; };
+
+extern const struct tvec_irange
+ tvrange_schar, tvrange_short, tvrange_int, tvrange_long,
+ tvrange_sbyte, tvrange_i16, tvrange_i32;
+extern const struct tvec_urange
+ tvrange_uchar, tvrange_ushort, tvrange_uint, tvrange_ulong, tvrange_size,
+ tvrange_byte, tvrange_u16, tvrange_u32;
+
+extern int tvec_claimeq_int(struct tvec_state */*tv*/,
+ long /*i0*/, long /*i1*/,
+ const char */*file*/, unsigned /*lno*/,
+ const char */*expr*/);
+extern int tvec_claimeq_uint(struct tvec_state */*tv*/,
+ unsigned long /*u0*/, unsigned long /*u1*/,
+ const char */*file*/, unsigned /*lno*/,
+ const char */*expr*/);
+#define TVEC_CLAIMEQ_INT(tv, i0, i1) \
+ (tvec_claimeq_int(tv, i0, i1, __FILE__, __LINE__, #i0 " /= " #i1))
+#define TVEC_CLAIMEQ_UINT(tv, u0, u1) \
+ (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1))
+
+extern const struct tvec_regty tvty_enum;
+
+#define DEFASSOC(tag_, ty, slot) \
+ struct tvec_##slot##assoc { const char *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;
+};
+
+#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 char */*file*/, unsigned /*lno*/, const char */*expr*/);
+TVEC_MISCSLOTS(DECLCLAIM)
+#undef DECLCLAIM
+#define TVEC_CLAIMEQ_IENUM(tv, ei, e0, e1) \
+ (tvec_claimeq_ienum(tv, ei, e0, e1, \
+ __FILE__, __LINE__, #e0 " /= " #e1))
+#define TVEC_CLAIMEQ_UENUM(tv, ei, e0, e1) \
+ (tvec_claimeq_uenum(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))
+
+extern const struct tvec_regty tvty_flags;
+struct tvec_flag { const char *tag; unsigned long m, v; };
+struct tvec_flaginfo {
+ const char *name;
+ const struct tvec_flag *fv;
+ const struct tvec_urange *range;
+};
+
+extern int tvec_claimeq_flags(struct tvec_state */*tv*/,
+ const struct tvec_flaginfo */*fi*/,
+ unsigned long /*f0*/, unsigned long /*f1*/,
+ const char */*file*/, unsigned /*lno*/,
+ const char */*expr*/);
+#define TVEC_CLAIMEQ_FLAGS(tv, fi, f0, f1) \
+ (tvec_claimeq_flags(tv, fi, f0, f1, \
+ __FILE__, __LINE__, #f0 " /= " #f1))
+
+extern const struct tvec_regty tvty_string, tvty_bytes;
+
+extern int tvec_claimeq_string(struct tvec_state */*tv*/,
+ const char */*p0*/, size_t /*sz0*/,
+ const char */*p1*/, size_t /*sz1*/,
+ const char */*file*/, unsigned /*lno*/,
+ const char */*expr*/);
+extern int tvec_claimeq_strz(struct tvec_state */*tv*/,
+ const char */*p0*/, const char */*p1*/,
+ const char */*file*/, unsigned /*lno*/,
+ const char */*expr*/);
+extern int tvec_claimeq_bytes(struct tvec_state */*tv*/,
+ const void */*p0*/, size_t /*sz0*/,
+ 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 "]"))
+#define TVEC_CLAIMEQ_STRZ(tv, p0, p1) \
+ (tvec_claimeq_strz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1))
+#define TVEC_CLAIMEQ_BYTES(tv, p0, sz0, p1, sz1) \
+ (tvec_claimeq(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \
+ #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
+
+extern const struct tvec_regty tvty_buffer;
+
+extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/);
+extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
t_exc_t_SOURCES = t/exc-test.c
t_exc_t_LDFLAGS = -static
+## Linear regression.
+pkginclude_HEADERS += linreg.h
+libutils_la_SOURCES += linreg.c
+##LIBMANS += linreg.3
+
## String handling.
pkginclude_HEADERS += str.h
libutils_la_SOURCES += str.c
--- /dev/null
+.\" -*-nroff-*-
+.TH control 3 "23 April 2023" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+control \- control structure metaprogramming
+.\" @MC_BEFORE
+.\" @MC_AFTER
+.\" @MC_WRAP
+.\" @MC_FINALLY
+.\" @MC_DOWHILE
+.\" @MC_DECL
+.\" @MC_LOOPELSE
+.\" @MC_LOOPBETWEEN
+.\" @MC_ALLOWELSE
+.\" @MC_GOELSE
+.\" @MC_TARGET
+.\" @MC_GOTARGET
+.\" @MC_ACT
+.\" @MC_LABEL
+.\" @MC_GOTO
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/control.h>"
+
+.BI MC_BEFORE( tag ", " stmts ") " body
+.BI MC_AFTER( tag ", " stmts ") " body
+.BI MC_WRAP( tag ", " before_stmt ", " onend_stmt ", " onbreak_stmt ") " body
+.BI MC_DOWHILE( tag ", " cond ") " body
+.BI MC_DECL( tag ", " decl ") " body
+.BI MC_LOOPELSE( tag ", " head ") " loop_body " \fR[\fBelse " else_body \fR]
+
+.BI MC_TARGET( tag ", " stmt ") " body
+.BI MC_GOTARGET( tag );
+.BI MC_ALLOWELSE( tag ") " main_body " \fR[\fBelse " else_body \fR]
+.BI MC_GOELSE( tag );
+
+.BI MC_ACT( stmt )
+.BI MC_LABEL( tag )
+.BI MC_GOTO( tag )
+.fi
+.SH DESCRIPTION
+The header file
+.B <mLib/control.h>
+defines a number of macros which are useful when defining new
+control structures for C. They are inspired by Simon Tatham's article
+.IR "Metaprogramming custom control structures in C",
+though these macros differ from Tatham's in a few respects.
+.SS "Common features"
+Each of these macros takes a
+.I tag
+argument. A
+.I tag
+is lexically like an identifier, except that it may begin with a digit,
+so, for example, plain integers are acceptable tags. Each use of an
+action macro by a user-level macro must have a distinct
+.IR tag .
+If you're writing a new prefix action macro written in terms of these
+existing actions, your macro should receive a
+.I tag
+from its caller, and pass this tag, along with a distinctive component
+of its own, down to any prefix actions that it calls; the
+.IR tag s
+from each layer should be separated by a pair of underscores.
+.PP
+Some of these macros work by wrapping a loop around the
+.I body
+statement. This interferes with the way that `free'
+.B break
+and
+.B continue
+statements within the
+.I body
+behave: we say that these statements are
+.I captured
+by the macro.
+A
+.B break
+or
+.B continue
+statement is `free' if it doesn't appear lexically within a loop or
+(for
+.B break)
+.B switch
+statement that is part of the
+.IR body .
+So
+.IP
+.B "if (!x) break;"
+.PP
+contains a free
+.B break
+statement, while
+.IP
+.nf
+.ft B
+for (i = 0; i < n; i++)
+\h'4n'if (interestingp(i)) break;
+.ft
+.fi
+.PP
+does not.
+.PP
+Some of these macros take special care to give you control over what
+happens when a captured
+.B break
+is executed. Alas, proper handling of
+.B continue
+doesn't seem possible. Free
+.B break
+and
+.B continue
+statements
+.I within
+arguments to these macros are never captured.
+.SS "Prefix action macros"
+.B MC_BEFORE
+macro is the simplest to understand. Executing
+.IP
+.BI MC_BEFORE( tag ", " stmt ") " body
+.PP
+has the same effect as executing
+.I stmt
+followed by
+.IR body ,
+except that the whole thing is syntactically a single statement, so, for
+example, it doesn't need to be enclosed in braces to be the body of a
+.B for
+loop.
+.B MC_BEFORE
+does not capture free
+.B break
+or
+.B continue
+statements.
+.PP
+Executing
+.IP
+.BI MC_AFTER( tag ", " stmt ") " body
+.PP
+has
+.I nearly
+the same effect as executing
+.I stmt
+followed by
+.IR body .
+Again, the whole thing is syntactically a single statement. However,
+.B MC_AFTER
+captures free
+.B break
+and
+.B continue
+statements within the
+.IR body .
+A free
+.B break
+or
+.B continue
+abruptly ends execution of the
+.IR body ,
+immediately transferring control to the
+.IR stmt .
+.PP
+Executing
+.IP
+.BI MC_WRAP( tag ", " before ", " onend ", " onbreak ") " body
+.PP
+has
+.I nearly
+the same effect as executing
+.IR before ,
+.IR body ,
+and then
+.IR onend .
+If the
+.I body
+executes a free
+.B break
+statement, then control abruptly continues with the
+.I onbreak
+statement, and
+.I onend
+is not executed. Currently, if the
+.I body
+executes a free
+.B continue
+statement, then control abruptly continues with the
+.I onend
+statement, but this behaviour is a bug and may be fixed in the future.
+.PP
+.\" @@@ mc_finally
+.PP
+Executing
+.IP
+.BI MC_DOWHILE( tag ", " cond ") " body
+.PP
+has exactly the same effect as
+.BI "do " body " while (" cond ); \fR,
+the only difference being that the
+.I body
+appears in tail position rather than sandwiched in the middle.
+.PP
+Executing
+.BI MC_DECL( tag ", " decl ") " body
+has the same effect as
+.BI "{ " decl "; " body " }" \fR,
+except that free
+.B break
+and
+.B continue
+statements are captured. Currently, a free
+.B continue
+statement will simply abruptly terminate execution of the
+.IR body ,
+while a free
+.B break
+statement abruptly
+.I restarts
+executing the
+.I body
+without leaving the scope of the
+.IR decl ;
+but these behaviours are bugs and may be fixed in the future.
+.PP
+The
+.B MC_DECL
+macro makes use of the fact that a
+.B for
+statement can introduce a declaration into its body's scope in C99 and
+C++; the macro is not available in C89.
+.PP
+Executing
+.IP
+.nf
+.BI MC_LOOPELSE( head ", " tag ") "
+.RI \h'4n' loop_body
+.RB [ else
+.RI \h'4n' else_body ]
+.fi
+.PP
+results in Python-like loop behaviour. The
+.I head
+must be a valid loop head with one of the forms
+.IP
+.BI "while (" cond ")"
+.br
+.BI "for (" decl "; " cond "; " next_expr ")"
+.br
+.BI "MC_DOWHILE(" tag ", " cond ")"
+.PP
+The resulting loop executes the same as
+.IP
+.nf
+.I head
+.RI \h'4n' loop_body
+.fi
+.PP
+If the loop ends abruptly, as a result of
+.BR break ,
+then control is passed to the statement following the loop in the usual
+way. However, if the loop completes naturally, and the optional
+.B else
+clause is present, then the
+.I else_body
+is executed. A free
+.B continue
+statement within the
+.I loop_body
+behaves normally. Free
+.B break
+and
+.B continue
+statements within the
+.I else_body
+are not captured.
+.PP
+.\" @@@ loopbetween
+.SS "Lower-level machinery"
+Executing
+.IP
+.BI MC_TARGET( tag ", " stmt ") " body
+.PP
+has exactly the same effect as simply executing
+.IR body .
+Executing
+.B MC_GOTARGET
+immediately transfers control to
+.IR stmt ,
+with control continuing with the following statement, skipping the
+.IR body .
+Free
+.B break
+or
+.B continue
+statements in
+.I body
+are not captured.
+.PP
+This is most commonly useful in loops in order to arrange the correct
+behaviour of a free
+.B break
+within the loop body. See the example below, which shows the definition
+of
+.BR MC_LOOPELSE .
+.PP
+Executing
+.IP
+.BI MC_ALLOWELSE( tag ") " main_body " \fR[\fBelse " else_body \fR]
+.PP
+has exactly the same effect as just
+.IR main_body .
+Executing
+.IP
+.BI MC_GOELSE( tag );
+.PP
+transfers control immediately to
+.I else_body
+(if present); control then naturally transfers to the following
+statement as usual. Free
+.B break
+or
+.B continue
+statements in either of
+.I main_body
+or
+.I else_body
+are not captured.
+.PP
+Note that
+.B MC_ALLOWELSE
+works by secretly inserting an
+.B if
+statement head before the
+.IR main_body ,
+so things will likely to wrong if
+.I main_body
+is itself an
+.B if
+statement: if
+.I main_body
+lacks an
+.B else
+clause, then an
+.B else
+intended to match
+.B MC_ALLOWELSE
+will be mis-associated; and even if
+.I main_body
+.I does
+have an
+.B else
+clause, the resulting program text is likely to provoke a compiler
+warning about `dangling
+.BR else '.
+.PP
+Using these tools, it's relatively straightforward to define a macro
+like
+.BR MC_LOOPELSE ,
+described above:
+.IP
+.nf
+.ft B
+#define MC_LOOPELSE(tag, head) \e
+\h'4n'MC_TARGET(tag##__exit, { ; }) \e
+\h'4n'MC_ALLOWELSE(tag##__else) \e
+\h'4n'MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); }) \e
+\h'4n'head \e
+\h'8n'MC_WRAP(tag##__body, { ; }, { ; }, \e
+\h'8n+\w'MC_WRAP(tag##__body, ''{ MC_GOTARGET(tag##__exit); })
+.ft R
+.fi
+.PP
+The main `trick' for these control-flow macros is
+.BR MC_ACT ,
+which wraps up a statement as an
+.IR action .
+An action is a valid
+.IR "statement head" ,
+like
+.B if
+or
+.BR while :
+i.e., it must be completed by following it with a
+.I body
+statement. Executing
+.IP
+.BI MC_ACT( stmt ") " body
+.PP
+has the same effect as simply executing
+.IR stmt ;
+the
+.I body
+is usually ignored. Note that
+.B ;
+is a valid statement which does nothing, so
+.BI MC_ACT( stmt );
+is also a valid statement with the same effect as
+.IR stmt .
+The only way to cause
+.I body
+to be executed is to attach a label to it and transfer control using
+.BR goto .
+.PP
+Executing
+.IP
+.BI MC_LABEL( tag ") " body
+.PP
+has the same effect as
+.IR body .
+Executing
+.IP
+.BI MC_GOTO( tag )
+.PP
+immediately transfers control to the
+.IR body .
+Note that
+.B MC_GOTO
+is syntactically an action
+(i.e., it's wrapped in
+.BR MC_ACT ).
+The
+.IR tag s
+here are scoped to the top-level source line, like all
+.IR tag s
+in this macro package.
+.PP
+All of the control-flow macros in this package are mainly constructed from
+.BR MC_ACT ,
+.BR MC_LABEL ,
+and
+.BR MC_GOTO ,
+sometimes with one or two other statement heads thrown into the mix.
+For example,
+.B MC_AFTER
+is defined as
+.IP
+.nf
+.ft B
+#define MC_AFTER(tag, stmt) \e
+\h'28n'MC_GOTO(tag##__body) \e
+\h'4n'MC_LABEL(tag##__end) \e
+\h'28n'MC_ACT(stmt) \e
+\h'28n'for (;;) \e
+\h'32n'MC_GOTO(tag##__end) \e
+\h'4n'MC_LABEL(tag##__body)
+.ft R
+.fi
+.PP
+(The unusual layout is conventional, to make the overall structure of
+the code clear despite visual interference from the labels.)
+The
+.I body
+appears at the end, labelled as
+.IB tag __body \fR.
+Control enters at the start, and is immediately transferred to the
+.I body ;
+but the
+.I body
+is enclosed in a
+.B for
+loop, so when the
+.I body
+completes, the loop restarts, transferring control to
+.IB tag __end
+and the
+.IR stmt .
+Since it is enclosed in
+.BR MC_ACT ,
+once
+.I stmt
+completes, control transfers to the following statement.
+.SH BUGS
+Some macros cause free
+.B break
+and/or
+.B continue
+statements to behave in unexpected ways.
+.PP
+The need for tagging is ugly, and the restriction on having two
+user-facing control-flow macros on the same line is objectionable. The
+latter could be avoided by using nonstandard features such as GCC's
+.B __COUNTER__
+macro, but adopting that would do programmers a disservice by
+introducing a hazard for those trying to port code to other compilers
+which lack any such feature.
+.SH "SEE ALSO"
+.BR mLib (3),
+.BR macros (3).
+.PP
+Simon Tatham,
+.IR "Metaprogramming custom control structures in C",
+.BR "https://www.chiark.greenend.org.uk/~sgtatham/mp/" .
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
extern "C" {
#endif
+/*----- Notes on the control operator machinery ---------------------------*
+ *
+ * These macros owe an obvious and immense debt to Simon Tatham's article
+ * `Metaprogramming custom control structures in C', available from
+ * https://www.chiark.greenend.org.uk/~sgtatham/mp/. The basic tricks are
+ * all Tatham's, as are most of the provided operators. The focus on
+ * @MC_ACT@ as a significant primitive is probably my main original
+ * contribution.
+ */
+
/*----- Header files ------------------------------------------------------*/
#ifndef MLIB_MACROS_H
#define MCTRL__LABEL(tag) GLUE(_mctrl__##tag##__, __LINE__)
/* @MC_ACT(stmt)@
- * @MC_PASS@
*
* @MC_ACT@ is the main `trick' for constructing these flow-control
* operators. It wraps up a statement as what we call an `action'. Actions
* its body.
*/
#define MC_ACT(stmt) if (1) { stmt; } else
-#define MC_PASS MC_ACT(;)
-
/* @MC_LABEL(tag)@
- * @MC_GOTO(tag)@
+ * @MC_GOTO(tag);@
*
* @MC_LABEL@ just establishes a label which can be invoked (only) from the
* same top-level macro; and @MC_GOTO@ transfers control to it.
#define MC_LABEL(tag) MCTRL__LABEL(tag):
#define MC_GOTO(tag) MC_ACT(goto MCTRL__LABEL(tag))
-/* @BEFORE(tag, stmt_0) stmt_1@
+/* @MC_TARGET(tag, stmt) body@
+ * @MC_GOTARGET(tag);@
+ *
+ * Executing @TARGET@ statement executes the @body@, as if the @TARGET@
+ * weren't there. Executing a @GOTARGET@ transfers control to the @stmt@
+ * (but not normally the @body@). In either case, the normal flow of control
+ * is then to the following statement.
+ */
+#define MC_TARGET(tag, stmt) \
+ MC_GOTO(tag##__body) \
+ MC_LABEL(tag##__tgt) MC_ACT(stmt) \
+ MC_LABEL(tag##__body)
+#define MC_GOTARGET(tag) do MC_GOTO(tag##__tgt); while (0)
+
+/* @MC_BEFORE(tag, stmt) body@
*
- * Execute @stmt_0@ and then @stmt_1@.
+ * Execute @stmt@ and then @body@.
*/
-#define BEFORE(tag, stmt) \
+#define MC_BEFORE(tag, stmt) \
MC_ACT(stmt; MC_GOTO(tag##__body)) \
MC_LABEL(tag##__body)
-/* @AFTER(tag, stmt_0) stmt_1@
+/* @MC_AFTER(tag, stmt) body@
*
- * Execute @stmt_1@ and then @stmt_0@. If either statement invokes @break@
- * then control immediately transfers to the statement following @AFTER@. If
- * either invokes @continue@, then control returns to @stmt_0@.
+ * Execute @body@ and then @stmt@. If @body@ invokes @break@ then control
+ * immediately transfers to the statement following @MC_AFTER@. If @body
+ * invokes @continue@, then control returns to @stmt@.
*/
-#define AFTER(tag, stmt) \
+#define MC_AFTER(tag, stmt) \
MC_GOTO(tag##__body) \
MC_LABEL(tag##__end) MC_ACT(stmt) \
for (;;) \
MC_GOTO(tag##__end) \
MC_LABEL(tag##__body)
-/* @WRAP(tag, before, onend, onbreak) stmt@
+/* @MC_DOWHILE(tag, cond) body@
+ *
+ * Repeatedly execute @body@ until @cond@ evaluates to zero. The @body@ is
+ * executed once before @cond@ is checked the first time. The @break@ and
+ * @continue@ statements work within @body@ as one would expect.
+ */
+#define MC_DOWHILE(tag, cond) \
+ MC_GOTO(tag##__body) \
+ while (cond) \
+ MC_LABEL(tag##__body)
+
+/* @MC_ALLOWELSE(tag) apodosis_body [else haeresis_body]@
+ * @MC_GOELSE(tag);@
*
- * Execute the @before@ statement, followed by @stmt@. If @stmt@ invokes
- * @break@, then @onbreak@ is immediately executed; if @stmt@ completes
+ * Executing @MC_ALLOWELSE@ executes @apodosis_body@, but not
+ * @haeresis_body@. If @MC_GOELSE(tag)@ is executed, then control continues
+ * from @haeresis_body@.
+ */
+#define MC_ALLOWELSE(tag) \
+ MC_GOTO(tag##__body) \
+ MC_LABEL(tag##__else) if (0) \
+ MC_LABEL(tag##__body)
+#define MC_GOELSE(tag) do MC_GOTO(tag##__else); while (0)
+
+/* @MC_WRAP(tag, before, onend, onbreak) body@
+ *
+ * Execute the @before@ statement, followed by @body@. If @body@ invokes
+ * @break@, then @onbreak@ is immediately executed; if @body@ completes
* normally, or invokes @continue@ then @onend@ is immediately executed.
* Any @break@ and @continue@ in the @before@, @onend@, and @onbreak@
* statements behave as one would expect from their context.
*/
-#define WRAP(tag, before, onend, onbreak) \
+#define MC_WRAP(tag, before, onend, onbreak) \
MC_ACT(before; MC_GOTO(tag##__body)) \
MC_LABEL(tag##__end) MC_ACT(onend) \
MC_LABEL(tag##__brk) MC_ACT(onbreak) \
MC_GOTO(tag##__end) \
MC_LABEL(tag##__body)
-/* @ALLOWELSE(tag) stmt_0 [else stmt_1]@
- * @GOELSE(tag);@
+/* @MC_FINALLY(tag, cleanup) body@
*
- * Executing @ALLOWELSE@ executes @stmt_0@, but not @stmt_1@. If
- * @GOELSE(tag)@ is executed, then control continues from @stmt_1@.
- */
-#define ALLOWELSE(tag) \
- MC_GOTO(tag##__body) \
- MC_LABEL(tag##__else) if (0) \
- MC_LABEL(tag##__body)
-#define GOELSE(tag) do MC_GOTO(tag##__else); while (0)
-
-/* @DOWHILE(tag, cond) stmt@
+ * Execute @cleanup@ when @body@ completes or ends with @break@. In the
+ * latter case, propagate the @break@ to the enclosing context -- for which
+ * it must be syntactically appropriate.
*
- * Repeatedly execute @stmt@ until @cond@ evaluates to zero. Execute @stmt@
- * at least once. The @break@ and @continue@ statements work within @stmt@
- * as one would expect.
+ * The @cleanup@ code is duplicated. If it arrange to have private long-term
+ * state, e.g, by declaring @static@ variables, then the two copies will not
+ * share the same state, so probably don't do this.
*/
-#define DOWHILE(tag, cond) \
- MC_GOTO(tag##__body) \
- while (cond) \
- MC_LABEL(tag##__body)
+#define MC_FINALLY(tag, cleanup) \
+ MP_WRAP(tag##__final, { ; } cleanup, { cleanup break; })
-/* @DECL(tag, decl) stmt@
+/* @MC_DECL(tag, decl) body@
*
- * Execute @stmt@ with @decl@ in scope. If @stmt@ completes or invokes
- * @break@ or @continue@ then control continues with the statement following
- * @DECL@. Internally, this uses @for@, so it only works in C99 or later, or
- * C++.
+ * Execute @body@ with @decl@ in scope. If @body@ completes or invokes
+ * @continue@ then control continues with the statement following @MC_DECL@;
+ * if it invokes @break@ then it will be restarted without leaving the scope
+ * of @decl@. Internally, this uses @for@, so it only works in C99 or later,
+ * or C++.
*/
#if __STDC_VERSION__ >= 199901 || defined(__cplusplus)
-# define DECL(tag, decl) \
+# define MC_DECL(tag, decl) \
for (decl;;) \
MC_GOTO(tag##__body) \
- MC_LABEL(tag##__end) MC_ACT(break) \
+ MC_LABEL(tag##__exit) MC_ACT(break) \
for (;;) \
- MC_GOTO(tag##__end) \
+ MC_GOTO(tag##__exit) \
MC_LABEL(tag##__body)
#endif
+/* @MC_LOOPELSE(tag, head) loop_body [else else_body]@
+ *
+ * Python-like looping with optional @else@ clause. @head loop_body@ must be
+ * a syntactically valid @for@, @while@, or @MC_DOWHILE@ loop; if the loop
+ * exits because of @break@ then control continues in the usual way;
+ * otherwise, the @else_body@ (if any) is executed.
+ */
+#define MC_LOOPELSE(tag, head) \
+ MC_TARGET(tag##__exit, { ; }) \
+ MC_ALLOWELSE(tag##__else) \
+ MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); }) \
+ head \
+ MC_WRAP(tag##__body, { ; }, { ; }, { MC_GOTARGET(tag##__exit); })
+
+/* @MC_LOOPBETWEEN(tag, setup, cond, step) loop_body [else else_body]@
+ *
+ * This is essentially a @for@ loop with a twist. The @setup@, @cond@, and
+ * @step@ arguments are the parts of the @for@ head clause; because of the
+ * limitations of the C macro syntax, they're separated by commas rather than
+ * semicolons.
+ *
+ * The twist is that, once the @loop_body@ has finished, the @step@
+ * expression evaluated, and the @cond@ evaluated and determined to be
+ * nonzero, the @else_body@ (if any) is executed before re-entering the
+ * @loop_body@. This makes it a useful place to insert any kind of
+ * interstitial material, e.g., printing commas between list items.
+ *
+ * The @cond@ is textually duplicated. You'll get some code bloat if the
+ * condition is very complex. If it somehow arranges to have private
+ * long-term state (e.g., as a result of declaring static variables inside
+ * GCC statement expressions), then the two copies will not share this state,
+ * so probably don't do this.
+ *
+ * Note that by the time that the @else_body@ is executed, the decision has
+ * already been made that another iteration will be performed, and, in
+ * particular, the @step@ has occurred. The @else_body@ is therefore looking
+ * at the next item to be processed, not the item that has just finished
+ * being processed.
+ */
+#define MC_LOOPBETWEEN(tag, setup, cond, step) \
+ for (setup;;) \
+ if (!(cond)) break; else \
+ MC_TARGET(tag##__exit, { break; }) \
+ for (;;) \
+ MC_WRAP(tag##__tailwrap, { ; }, \
+ { ; }, \
+ { MC_GOTARGET(tag##__exit); }) \
+ MC_ALLOWELSE(tag##__tail) \
+ MC_WRAP(tag##__bodywrap, { ; }, \
+ { if ((step), !(cond)) \
+ MC_GOTARGET(tag##__exit); \
+ else \
+ MC_GOELSE(tag##__tail); }, \
+ { MC_GOTARGET(tag##__exit); })
+
/*----- That's all, folks -------------------------------------------------*/
#ifdef __cplusplus
--- /dev/null
+/* -*-c-*-
+ *
+ * Simple linear regression
+ *
+ * (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 <assert.h>
+#include <math.h>
+
+#include "linreg.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @linreg_init@ --- *
+ *
+ * Arguments: @struct linreg *lr@ = linear regression state
+ *
+ * Returns: ---
+ *
+ * Use: Initializes a linear-regression state ready for use.
+ */
+
+void linreg_init(struct linreg *lr)
+{
+ lr->sum_x = lr->sum_x2 = lr->sum_y = lr->sum_y2 = lr->sum_x_y = 0.0;
+ lr->n = 0;
+}
+
+/* --- @linreg_update@ --- *
+ *
+ * Arguments: @struct linreg *lr@ = linear regression state
+ * @double x, y@ = point coordinates
+ *
+ * Returns: ---
+ *
+ * Use: Informs the linear regression machinery of a point.
+ *
+ * Note that the state size is constant, and independent of the
+ * number of points.
+ */
+
+void linreg_update(struct linreg *lr, double x, double y)
+{
+ lr->sum_x += x; lr->sum_x2 += x*x; lr->sum_x_y += x*y;
+ lr->sum_y += y; lr->sum_y2 += y*y;
+ lr->n++;
+}
+
+/* --- @linreg_fit@ --- *
+ *
+ * Arguments: @const struct linreg *lr@ = linear regression state
+ * @double *m_out, *c_out, *r_out@ = where to write outputs
+ *
+ * Returns: ---
+ *
+ * Use: Compute the best-fit line through the previously-specified
+ * points. The line has the equation %$y = m x + c$%; %$m$% and
+ * %$c$% are written to @*m_out@ and @*c_out@ respectively, and
+ * the correlation coefficient %$r$% is written to @*r_out@.
+ * Any (or all, but that would be useless) of the output
+ * pointers may be null to discard that result.
+ *
+ * At least one point must have been given.
+ */
+
+void linreg_fit(const struct linreg *lr,
+ double *m_out, double *c_out, double *r_out)
+{
+ double E_X, E_X2, E2_X, E_X_Y, E_Y, E_Y2, E2_Y, n;
+ double cov_X_Y, var_X, var_Y, m;
+ assert(lr->n);
+
+ n = lr->n; E_X_Y = lr->sum_x_y/n;
+ E_X = lr->sum_x/n; E_X2 = lr->sum_x2/n; E2_X = E_X*E_X;
+ E_Y = lr->sum_y/n; E_Y2 = lr->sum_y2/n; E2_Y = E_Y*E_Y;
+
+ cov_X_Y = E_X_Y - E_X*E_Y; var_X = E_X2 - E2_X; var_Y = E_Y2 - E2_Y;
+
+ m = cov_X_Y/var_X;
+ if (m_out) *m_out = m;
+ if (c_out) *c_out = E_Y - m*E_X;
+ if (r_out) *r_out = cov_X_Y/sqrt(var_X*var_Y);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Simple linear regression
+ *
+ * (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_LINREG_H
+#define MLIB_LINREG_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* Theory. (This should be well-known.)
+ *
+ * We have a number of data points (x_i, y_i) for 0 <= i < n. We believe
+ * they lie close to a straight line y = m x + c, for unknown constants m and
+ * c. Let e_i = m x_i + c - y_i. We seek the parameters which minimize
+ * E = ∑_{0\le i<n} e_i^2.
+ *
+ * We begin by simplifying
+ *
+ * E = ∑ e_i^2
+ * = ∑ (m x_i + c - y_i)^2
+ * = ∑ (m^2 x_i^2 + c^2 + y_i^2 + 2 c m x_i - 2 m x_i y_i - 2 c y_i) .
+ *
+ * (where all sums are over 0 <= i < n). Taking partial derivatives,
+ *
+ * ∂E/∂m = ∑ (2 m x_i^2 + 2 c x_i - 2 x_i y_i)
+ * = 2 (m ∑ x_i^2 + c ∑ x_i - ∑ x_i y_i)
+ *
+ * and
+ *
+ * ∂E/∂c = ∑ (2 c + 2 m x_i - 2 y_i)
+ * = 2 (n c + m ∑ x_i - ∑ y_i) .
+ *
+ * Now we solve for m and c such that the partial derivatives vanish. Note
+ * that the second partial derivatives are nonnegative, being 2 ∑ x_i^2 and 2
+ * n respectively, so we shall indeed find a minimum.
+ *
+ * Restating the problem,
+ *
+ * m ∑ x_i^2 + c ∑ x_i - ∑ x_i y_i = 0
+ * m ∑ x_i + c n - ∑ y_i = 0 .
+ *
+ * Writing E[X] = (∑ x_i)/n, E[X Y] = (∑ x_i y_i)/n, etc., this gives
+ *
+ * m n E[X^2] + c n E[X] - n E[X Y] = 0
+ * m n E[X] + c n - n E[Y] = 0 .
+ *
+ * We see a common factor of n, so we can take that out. Now multiply the
+ * latter equation by E[X] and subtract, giving
+ *
+ * m (E[X^2] - E[X]^2) - (E[X Y] - E[X] E[Y])
+ *
+ * whence
+ *
+ * m = (E[X Y] - E[X] E[Y])/(E[X^2] - E[X]^2) .
+ *
+ * The numerator is known as the covariance of X and Y, written
+ *
+ * cov(X, Y) = σ_{XY} = E[X Y] - E[X] E[Y] ;
+ *
+ * the denominator is the variance of X, written
+ *
+ * var(X) = σ_X = E[X^2] - E[X]^2 .
+ *
+ * The second equation gives
+ *
+ * c = E[Y] - m E[X] .
+ *
+ * Also of interest is the correlation coefficient
+ *
+ * r = σ_{XY}/√(σ_X σ_Y) ,
+ *
+ * which I'm not going to derive here.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+/*----- Data structures ---------------------------------------------------*/
+
+struct linreg {
+ double sum_x, sum_x2, sum_y, sum_y2, sum_x_y;
+ unsigned long n;
+};
+#define LINREG_INIT { 0.0, 0.0, 0.0, 0.0, 0.0, 0 }
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @linreg_init@ --- *
+ *
+ * Arguments: @struct linreg *lr@ = linear regression state
+ *
+ * Returns: ---
+ *
+ * Use: Initializes a linear-regression state ready for use.
+ */
+
+extern void linreg_init(struct linreg */*lr*/);
+
+/* --- @linreg_update@ --- *
+ *
+ * Arguments: @struct linreg *lr@ = linear regression state
+ * @double x, y@ = point coordinates
+ *
+ * Returns: ---
+ *
+ * Use: Informs the linear regression machinery of a point.
+ *
+ * Note that the state size is constant, and independent of the
+ * number of points.
+ */
+
+extern void linreg_update(struct linreg */*lr*/, double /*x*/, double /*y*/);
+
+/* --- @linreg_fit@ --- *
+ *
+ * Arguments: @const struct linreg *lr@ = linear regression state
+ * @double *m_out, *c_out, *r_out@ = where to write outputs
+ *
+ * Returns: ---
+ *
+ * Use: Compute the best-fit line through the previously-specified
+ * points. The line has the equation %$y = m x + c$%; %$m$% and
+ * %$c$% are written to @*m_out@ and @*c_out@ respectively, and
+ * the correlation coefficient %$r$% is written to @*r_out@.
+ * Any (or all, but that would be useless) of the output
+ * pointers may be null to discard that result.
+ *
+ * At least one point must have been given.
+ */
+
+extern void linreg_fit(const struct linreg */*lr*/,
+ double */*m_out*/, double */*c_out*/,
+ double */*r_out*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
# define EXECL_LIKE(ntrail) __attribute__((__sentinel__(ntrail)))
#endif
+#if GCC_VERSION_P(2, 7)
+# define LAUNDER(x) \
+ ({ __typeof__(x) _y; __asm__("" : "=g"(_y) : "0"(x)); _y; })
+# define RELAX do __asm__ __volatile__("" ::: "memory"); while (0)
+#endif
+
#if CLANG_VERSION_P(3, 3)
# define MLIB__PRAGMA_HACK(x) _Pragma(#x)
# define MUST_CHECK
#endif
+#ifndef LAUNDER
+# define LAUNDER
+#endif
+
+#ifndef RELAX
+# define RELAX
+#endif
+
#ifndef GCC_WARNING
# define GCC_WARNING(warn)
#endif
/*----- Header files ------------------------------------------------------*/
#include "bits.h"
-#include "testrig.h"
+#include "tvec.h"
/*----- Main code ---------------------------------------------------------*/
+enum {
+ RZ, NROUT,
+ RX = NROUT, RY, NREG,
+ RN = RY
+};
+
#define TSHIFT(OP) \
+ static void test_##OP(const struct tvec_reg *in, struct tvec_reg *out, \
+ void *ctx) \
+ { \
+ kludge64 x; \
\
-static int t##OP(dstr *v) \
-{ \
- kludge64 x, xx, y; \
- unsigned s = *(unsigned *)v[1].buf; \
- int ok = 1; \
- \
- LOAD64_(x, v[0].buf); \
- LOAD64_(xx, v[2].buf); \
- y = x; \
- OP##64_(y, y, s); \
- if (CMP64(y, !=, xx)) { \
- ok = 0; \
- fprintf(stderr, \
- "\nbad: %08x:%08x " #OP " %u != %08x:%08x [%08x:%08x]\n", \
- HI64(x), LO64(x), s, HI64(y), LO64(y), HI64(xx), LO64(xx)); \
- } \
- return (ok); \
-}
+ LOAD64_(x, in[RX].v.bytes.p); \
+ OP##64_(x, x, in[RN].v.u); \
+ tvec_allocbytes(&out[RZ].v, 8); \
+ STORE64_(out[RZ].v.bytes.p, x); \
+ }
#define TARITH(OP) \
+ static void test_##OP(const struct tvec_reg *in, struct tvec_reg *out, \
+ void *ctx) \
+ { \
+ kludge64 x, y; \
\
-static int t##OP(dstr *v) \
-{ \
- kludge64 x, y, xx, yy; \
- int ok = 1; \
- \
- LOAD64_(x, v[0].buf); \
- LOAD64_(y, v[1].buf); \
- LOAD64_(xx, v[2].buf); \
- yy = x; \
- OP##64(yy, yy, y); \
- if (CMP64(yy, !=, xx)) { \
- ok = 0; \
- fprintf(stderr, \
- "\nbad: %08x:%08x " #OP " %08x:%08x != %08x:%08x " \
- "[%08x:%08x]\n", \
- HI64(x), LO64(x), HI64(y), LO64(y), \
- HI64(yy), LO64(yy), HI64(xx), LO64(xx)); \
- } \
- return (ok); \
-}
+ LOAD64_(x, in[RX].v.bytes.p); LOAD64_(y, in[RY].v.bytes.p); \
+ OP##64(x, x, y); \
+ tvec_allocbytes(&out[RZ].v, 8); \
+ STORE64_(out[RZ].v.bytes.p, x); \
+ }
TSHIFT(LSL)
TSHIFT(LSR)
TARITH(ADD)
TARITH(SUB)
-static test_chunk tests[] = {
- { "lsl64", tLSL, { &type_hex, &type_int, &type_hex, 0 } },
- { "lsr64", tLSR, { &type_hex, &type_int, &type_hex, 0 } },
- { "rol64", tROL, { &type_hex, &type_int, &type_hex, 0 } },
- { "ror64", tROR, { &type_hex, &type_int, &type_hex, 0 } },
- { "add64", tADD, { &type_hex, &type_hex, &type_hex, 0 } },
- { "sub64", tSUB, { &type_hex, &type_hex, &type_hex, 0 } },
- { 0, 0, { 0 } }
+static const struct tvec_urange ur_eight = { 8, 8 };
+static const struct tvec_urange ur_shift = { 0, 63 };
+static const struct tvec_regdef shift_regs[] = {
+ { "x", RX, &tvty_bytes, 0, { &ur_eight } },
+ { "n", RN, &tvty_uint, 0, { &ur_shift } },
+ { "z", RZ, &tvty_bytes, 0, { &ur_eight } },
+ { 0, 0, 0, 0 }
};
+static const struct tvec_regdef arith_regs[] = {
+ { "x", RX, &tvty_bytes, 0, { &ur_eight } },
+ { "y", RY, &tvty_bytes, 0, { &ur_eight } },
+ { "z", RZ, &tvty_bytes, 0, { &ur_eight } },
+ { 0, 0, 0, 0 }
+};
+
+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 }
+};
+
+static const struct tvec_info testinfo =
+ { tests, NROUT, NREG, sizeof(struct tvec_reg) };
int main(int argc, char *argv[])
-{
- test_run(argc, argv, tests, SRCDIR "/t/bits.tests");
- return (0);
-}
+ { return (tvec_main(argc, argv, &testinfo, 0)); }
/*----- That's all, folks -------------------------------------------------*/
WD = 64
LIMIT = 1 << WD
MASK = LIMIT - 1
+FMT = "%%0%dx" % (WD/4)
-ARGS = SYS.argv[1:]; ARGS.reverse()
-def arg(default = None):
- if len(ARGS): return ARGS.pop()
- else: return default
+def rol(x, n): return ((x << n) | (x >> (WD - n))) & MASK
+def ror(x, n): return ((x >> n) | (x << (WD - n))) & MASK
-R.seed(None)
-seed = arg()
-if seed is None: SEED = R.randrange(0, 1 << 32)
-else: SEED = int(seed, 0)
-R.seed(SEED)
+class BaseVector (object):
+ def __init__(me):
+ me._all = []
+ def newseed(me, seed):
+ me._curr = []
+ me._all.append((seed, me._curr))
+ def _append(me, *args):
+ if len(args) != len(me._REGS):
+ raise TypeError("expected %d arguments" % len(me._REGS))
+ me._curr.append(args)
+ def write(me):
+ print("")
+ print("[%s]" % me._NAME)
+ for seed, vv in me._all:
+ print("")
+ print(";;; seed = 0x%08x" % seed)
+ for v in vv:
+ print("")
+ for r, x in zip(me._REGS, v): print("%s = %s" % (r, me._RFMT[r] % x))
-print('### Test vectors for 64-bit arithmetic macros')
-print('### [generated; seed = 0x%08x]' % SEED)
+class ShiftVector (BaseVector):
+ _REGS = ["x", "n", "z"]
+ _RFMT = { "x": FMT, "n": "%d", "z": FMT }
+ def add(me, r):
+ for i in xrange(NVEC):
+ x = r.randrange(LIMIT)
+ n = r.randrange(70)%WD
+ z = me._op(x, n)
+ me._append(x, n, z)
+ for i in xrange(4):
+ x = r.randrange(LIMIT)
+ z = me._op(x, WD/2)
+ me._append(x, WD/2, z)
+class LSLVector (ShiftVector):
+ _NAME = "lsl64"
+ def _op(me, x, n): return (x << n)&MASK
+class LSRVector (ShiftVector):
+ _NAME = "lsr64"
+ def _op(me, x, n): return (x >> n)&MASK
+class ROLVector (ShiftVector):
+ _NAME = "rol64"
+ def _op(me, x, n): return rol(x, n)
+class RORVector (ShiftVector):
+ _NAME = "ror64"
+ def _op(me, x, n): return ror(x, n)
-def rol(x, n): return ((x << n) | (x >> (WD - n))) & MASK
-def ror(x, n): return ((x >> n) | (x << (WD - n))) & MASK
-def put(x): return '%0*x' % (WD//4, x)
-
-for name, func in [('lsl', lambda x, n: x << n),
- ('lsr', lambda x, n: x >> n),
- ('rol', rol),
- ('ror', ror)]:
- print('\n%s64 {' % name)
- for i in xrange(NVEC):
- x = R.randrange(LIMIT)
- sh = R.randrange(0, 70) & 63
- print(' %s %2d %s;' % (put(x), sh, put(func(x, sh) & MASK)))
- for i in xrange(4):
- x = R.randrange(LIMIT)
- sh = 32
- print(' %s %2d %s;' % (put(x), sh, put(func(x, sh) & MASK)))
- print('}')
-
-for name, func in [('add', lambda x, y: x + y),
- ('sub', lambda x, y: x - y)]:
- print('\n%s64 {' % name)
- for i in xrange(NVEC):
- x = R.randrange(LIMIT)
- y = R.randrange(LIMIT)
- print(' %s %s %s;' % (put(x), put(y), put(func(x, y) & MASK)))
- print('}')
+class ArithVector (BaseVector):
+ _REGS = ["x", "y", "z"]
+ _RFMT = { "x": FMT, "y": FMT, "z": FMT }
+ def add(me, r):
+ for i in xrange(NVEC):
+ x = r.randrange(LIMIT)
+ y = r.randrange(LIMIT)
+ z = me._op(x, y)
+ me._append(x, y, z)
+class AddVector (ArithVector):
+ _NAME = "add64"
+ def _op(me, x, y): return (x + y)&MASK
+class SubVector (ArithVector):
+ _NAME = "sub64"
+ def _op(me, x, y): return (x - y)&MASK
+
+VECS = [LSLVector(), LSRVector(), ROLVector(), RORVector(),
+ AddVector(), SubVector()]
+
+for arg in SYS.argv[1:]:
+ if arg == "-": seed = R.SystemRandom().randrange(1 << 32)
+ else: seed = int(arg, 0)
+ r = R.Random(seed)
+ for v in VECS: v.newseed(seed); v.add(r)
+
+print(";;; -*-conf-*- Test vectors for 64-bit arithmetic macros")
+print(";;; [generated]")
+
+for v in VECS: v.write()
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include "control.h"
+#include "report.h"
+#include "tvec.h"
/*----- Main code ---------------------------------------------------------*/
-static int step = 0;
-static int rc = 0;
-
-#define STEP(s) check_step(s, __FILE__ ": " STR(__LINE__))
-#define MISSTEP STEP(-1)
-static void check_step(int s, const char *where)
+static struct tvec_state tvstate;
+static int step;
+
+#define TESTGROUP(name) \
+ TVEC_TESTGROUP(tg, &tvstate, name) \
+ MC_BEFORE(init, { step = 0; })
+#define TEST TVEC_TEST(test, &tvstate)
+#define STEP(s) do { \
+ tvec_claim(&tvstate, s == step, __FILE__, __LINE__, \
+ "found %d /= expected %d", s, step); \
+ step++; \
+ } while (0)
+#define MISSTEP do { \
+ tvec_claim(&tvstate, 0, __FILE__, __LINE__, \
+ "shouldn't have reached here"); \
+ step++; \
+ } while (0)
+
+int main(int argc, char *argv[])
{
- if (step != s) {
- fprintf(stderr, "misstep at %s: expected %d but found %d\n",
- where, step, s);
- rc = 2;
+ struct tvec_test test;
+ int argpos;
+ int i;
+
+ tvec_parseargs(argc, argv, &tvstate, &argpos, &tvec_adhocinfo);
+ if (argpos < argc) die(2, "no input files expected");
+ tvec_adhoc(&tvstate, &test);
+
+ TESTGROUP("before-after") {
+ MC_BEFORE(before0, STEP(0)) STEP(1);
+ MC_AFTER(after0, STEP(3)) STEP(2);
+ STEP(4);
}
- step++;
-}
-#define LASTSTEP(s) laststep(s, __FILE__ ": " STR(__LINE__))
-static void laststep(int s, const char *where)
- { check_step(s, where); step = 0; }
+ TESTGROUP("wrap") {
+ MC_WRAP(wrap0, STEP(0), STEP(2), MISSTEP)
+ STEP(1);
+ MC_WRAP(wrap1, STEP(3), MISSTEP, STEP(5))
+ { STEP(4); break; }
+ STEP(6);
+ }
-#define FORELSE(head) \
- MC_GOTO(top) \
- MC_LABEL(out) MC_ACT(;) \
- MC_LABEL(top) ALLOWELSE(els) \
- AFTER(outer, GOELSE(els)) \
- for (head) \
- WRAP(inner, { ; }, \
- { ; }, \
- { MC_GOTO(out); })
+ TESTGROUP("loop") {
+ for (;;) {
+ MC_AFTER(after1, STEP(1); break) STEP(0);
+ MISSTEP; break;
+ }
+ STEP(2);
+ }
-#define FOR_FIZZBUZZ(var, base, limit) \
- MC_GOTO(top) \
- MC_LABEL(out) MC_ACT({ ; }) \
- MC_LABEL(top) DECL(bounds, \
- int _i = base COMMA _limit = limit) \
- for (; _i < _limit; _i++) \
- DECL(buf, char _buf[24]) \
- DECL(var, const char *var) \
- WRAP(wrap, { \
- switch (_i%15) { \
- case 0: \
- var = "fizzbuzz"; \
- break; \
- case 3: case 6: case 9: case 12: \
- var = "fizz"; \
- break; \
- case 5: case 10: \
- var = "buzz"; \
- break; \
- default: \
- sprintf(_buf, "%d", _i); var = _buf; \
- break; \
- } \
- }, \
- { ; }, \
- { MC_GOTO(out); })
-
-int main(void)
-{
- int i;
+#define FORELSE(head) MC_LOOPELSE(forelse, for (head))
+
+ TESTGROUP("for-else") {
+ FORELSE (i = 0; i < 10; i++) {
+ STEP(i);
+ if (i == 7) break;
+ } else
+ MISSTEP;
+ STEP(8);
+ }
+
+ TESTGROUP("for-else-break") {
+ FORELSE (i = 0; i < 10; i++) {
+ STEP(i);
+ if (i == 12) break;
+ } else
+ STEP(10);
+ STEP(11);
+ }
- BEFORE(before0, STEP(0)) STEP(1);
- AFTER(after0, STEP(3)) STEP(2);
- LASTSTEP(4);
+#undef FORELSE
- WRAP(wrap0, STEP(0), STEP(2), MISSTEP) STEP(1);
- WRAP(wrap1, STEP(3), MISSTEP, STEP(5)) { STEP(4); break; }
- LASTSTEP(6);
+ TESTGROUP("loop-between") {
+ MC_LOOPBETWEEN(x, i = 0, i < 5, i++) STEP(2*i);
+ else STEP(2*i - 1);
+ STEP(9);
+ }
+
+ TESTGROUP("loop-between-continue-break") {
+ MC_LOOPBETWEEN(x, i = 0, i < 5, i++) {
+ if (i == 1) { STEP(2); continue; }
+ STEP(2*i);
+ if (i == 3) break;
+ } else
+ STEP(2*i - 1);
+ STEP(7);
+ }
- STEP(0);
- for (;;) {
- AFTER(after1, STEP(2); break) STEP(1);
- MISSTEP; break;
+#define WRAPELSE_TEST \
+ MC_TARGET(done_plain, { STEP(4); MC_GOELSE(elsie); }) \
+ MC_WRAP(outer_wrap, { STEP(0); }, \
+ { STEP(6); }, \
+ { MISSTEP; }) \
+ MC_ALLOWELSE(elsie) \
+ MC_WRAP(inner_wrap, { STEP(1); }, \
+ { STEP(3); MC_GOTARGET(done_plain); }, \
+ { MISSTEP; })
+
+ TESTGROUP("wrap-else") {
+ WRAPELSE_TEST STEP(2);
+ else STEP(5);
+ STEP(7);
}
- LASTSTEP(3);
-
- FORELSE (i = 0; i < 10; i++) {
- STEP(i);
- if (i == 7) break;
- } else
- MISSTEP;
- LASTSTEP(8);
-
- FORELSE (i = 0; i < 10; i++) {
- STEP(i);
- if (i == 12) break;
- } else
- STEP(10);
- LASTSTEP(11);
-
-#define TEST \
- MC_ACT(STEP(0); MC_GOTO(in_plain)) \
- MC_LABEL(done_plain) MC_ACT(STEP(5); GOELSE(elsie)) \
- MC_LABEL(in_plain) WRAP(outer_wrap, { STEP(1); }, \
- { STEP(7); }, \
- { MISSTEP; }) \
- ALLOWELSE(elsie) \
- WRAP(inner_wrap, { STEP(2); }, \
- { STEP(4); \
- MC_GOTO(done_plain); }, \
- { MISSTEP; }) \
- STEP(3); \
- else \
- STEP(6); \
- LASTSTEP(8);
- TEST
-#undef TEST
+#undef WRAPELSE_TEST
+
+ TESTGROUP("decl") {
#if __STDC_VERSION__ >= 199901 || defined(__cplusplus)
- STEP(0);
- DECL(decl0, int j = 1) STEP(j);
- LASTSTEP(2);
+ STEP(0);
+ MC_DECL(decl0, int j = 1) STEP(j);
+ STEP(2);
+#else
+ tvec_skipgroup(&tvstate, "`MC_DECL' not supported on C89");
#endif
+ }
+
+#define FIZZBUZZ_DECLS(var) \
+ int _i, _limit; \
+ char _buf[24]; \
+ const char *var
+#define FOR_FIZZBUZZ(var, base, limit) \
+ MC_TARGET(out, { ; }) \
+ MC_BEFORE(bounds, { _i = base; _limit = limit; }) \
+ for (; _i < _limit; _i++) \
+ MC_WRAP(wrap, \
+ { switch (_i%15) { \
+ case 0: var = "fizzbuzz"; break; \
+ case 3: case 6: case 9: case 12: var = "fizz"; break; \
+ case 5: case 10: var = "buzz"; break; \
+ default: sprintf(_buf, "%d", _i); var = _buf; break; \
+ } }, \
+ { ; }, \
+ { MC_GOTARGET(out); })
+
+ TESTGROUP("fizzbuzz") {
+ FIZZBUZZ_DECLS(fb);
+ unsigned i;
+ static const char *const ref[] = {
+ "19", "buzz", "fizz", "22", "23", "fizz", "buzz",
+ "26", "fizz", "28", "29", "fizzbuzz", "31", 0
+ };
+
+ i = 0;
+ FOR_FIZZBUZZ(fb, 19, 32)
+ TEST
+ if (TVEC_CLAIM(&tvstate, ref[i]))
+ { TVEC_CLAIMEQ_STRZ(&tvstate, fb, ref[i]); i++; }
+ TVEC_CLAIM(&tvstate, !ref[i]);
+ }
- FOR_FIZZBUZZ(fb, 19, 32) printf("%s\n", fb);
+#undef FIZZBUZZ_DECLS
+#undef FOR_FIZZBUZZ
- return (rc);
+ return (tvec_end(&tvstate));
}
/*----- That's all, folks -------------------------------------------------*/
* MA 02111-1307, USA.
*/
-#include "testrig.h"
+#include "tvec.h"
#include "versioncmp.h"
-static int t_vcmp(dstr *v)
+enum {
+ RRC, NROUT,
+ RV0 = NROUT, RV1, NREG
+};
+
+static void test_versioncmp(const struct tvec_reg *in, struct tvec_reg *out,
+ void *ctx)
+ { out[RRC].v.i = versioncmp(in[RV0].v.str.p, in[RV1].v.str.p); }
+
+static void swap_test(struct tvec_state *tv)
{
- int ok = 1;
- int rc = versioncmp(v[0].buf, v[1].buf);
- if (rc != *(int *)v[2].buf) {
- fprintf(stderr, "\nversioncmp(%s, %s) returns %d != %d\n",
- v[0].buf, v[1].buf, rc, *(int *)v[2].buf);
- ok = 0;
- }
- return (ok);
+ struct tvec_reg rt;
+
+ tv->st = '.';
+ tv->test->fn(tv->in, tv->out, 0); tvec_check(tv, "vanilla");
+ 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");
}
-static const test_chunk tests[] = {
- { "versioncmp", t_vcmp, { &type_string, &type_string, &type_int } },
- { 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 }
};
+static const struct tvec_test tests[] = {
+ { "versioncmp", versioncmp_regs, 0, swap_test, test_versioncmp },
+ { 0, 0, 0, 0 }
+};
+
+static const struct tvec_info testinfo =
+ { tests, NROUT, NREG, sizeof(struct tvec_reg) };
+
int main(int argc, char *argv[])
- { test_run(argc, argv, tests, SRCDIR "/versioncmp.in"); return (1); }
+ { return (tvec_main(argc, argv, &testinfo, 0)); }
/*----- That's all, folks -------------------------------------------------*/
-## test for versioncmp
-
-versioncmp {
- 1.2 1.2 0;
- 1.1 1.2 -1;
- 1.1 1.0 +1;
- 1.0 1.a -1;
- 1:2.0 2:0.4 -1;
- 1.2 1.2~pre0 +1;
- 1~~ 1~~a -1;
- 1~~a 1~ -1;
- 1~ 1 -1;
- 1 1a -1;
-}
+;;; -*-conf-*-
+;;; test for versioncmp
+
+[versioncmp]
+
+;; some softball tests
+
+v0 = 1.2
+v1 = 1.2
+rc = 0
+
+v0 = 1.1
+v1 = 1.2
+rc = -1
+
+v0 = 1.1
+v1 = 1.0
+rc = +1
+
+;; compare numeric to alphabetic
+
+v0 = 1.0
+v1 = 1.a
+rc = -1
+
+;; compare epochs
+
+v0 = 1:2.0
+v1 = 2:0.4
+rc = -1
+
+;; exercise `~'
+
+v0 = 1.2
+v1 = 1.2~pre0
+rc = +1
+
+v0 = 1~~
+v1 = 1~~a
+rc = -1
+
+v0 = 1~~a
+v1 = 1~
+rc = -1
+
+v0 = 1~
+v1 = 1
+rc = -1
+
+;; juxtaposed alphabetics
+
+v0 = 1
+v1 = 1a
+rc = -1
## bits
AT_SETUP([utilities: bits])
AT_KEYWORDS([utils bits])
-for seed in 0xaca98e08 0x0b6e95fb ""; do
- $PYTHON SRCDIR/t/bits-testgen.py $seed >bits.tests
- AT_CHECK([BUILDDIR/t/bits.t -f bits.tests], [0], [ignore], [ignore])
-done
+$PYTHON SRCDIR/t/bits-testgen.py 0xaca98e08 0x0b6e95fb - >bits.tests
+AT_CHECK([BUILDDIR/t/bits.t bits.tests], [0], [ignore], [ignore])
AT_CLEANUP
## control
AT_SETUP([utilities: control])
AT_KEYWORDS([utils control])
-AT_DATA([expout],
-[19
-buzz
-fizz
-22
-23
-fizz
-buzz
-26
-fizz
-28
-29
-fizzbuzz
-31
-])
-AT_CHECK([BUILDDIR/t/control.t], [0], [expout])
+AT_CHECK([BUILDDIR/t/control.t], [0], [ignore], [ignore])
AT_CLEANUP
## exc
## versioncmp
AT_SETUP([utilities: versioncmp])
AT_KEYWORDS([utils versioncmp])
-AT_CHECK([BUILDDIR/t/versioncmp.t -f SRCDIR/t/versioncmp.tests],
+AT_CHECK([BUILDDIR/t/versioncmp.t SRCDIR/t/versioncmp.tests],
[0], [ignore], [ignore])
AT_CLEANUP