+ tvrange_u32 = { 0, 4294967295 };
+
+/* --- @tvec_claimeq_int@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @long i0, i1@ = two signed integers
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @i0@ and @i1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @i0@ and @i1@ are equal. As for
+ * @tvec_claim@ above, a test case is automatically begun and
+ * ended if none is already underway. If the values are
+ * unequal, then @tvec_fail@ is called, quoting @expr@, and the
+ * mismatched values are dumped: @i0@ is printed as the output
+ * value and @i1@ is printed as the input reference.
+ */
+
+int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->out[0].v.i = i0; tv->in[0].v.i = i1;
+ return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr));
+}
+
+/* --- @tvec_claimeq_uint@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned long u0, u1@ = two unsigned integers
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @u0@ and @u1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @u0@ and @u1@ are equal. As for
+ * @tvec_claim@ above, a test case is automatically begun and
+ * ended if none is already underway. If the values are
+ * unequal, then @tvec_fail@ is called, quoting @expr@, and the
+ * mismatched values are dumped: @u0@ is printed as the output
+ * value and @u1@ is printed as the input reference.
+ */
+
+int tvec_claimeq_uint(struct tvec_state *tv,
+ unsigned long u0, unsigned long u1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->out[0].v.u = u0; tv->in[0].v.u = u1;
+ return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr));
+}
+
+/*----- Size type ---------------------------------------------------------*/
+
+/* --- @parse_size@ --- *
+ *
+ * Arguments: @union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ * @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Parse a register value from an input file.
+ *
+ * The input format for a size value consists of an unsigned
+ * integer followed by an optional unit specifier consisting of
+ * an SI unit prefix and (optionally) the letter `B'. */
+
+static int parse_size(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ unsigned long sz;
+ int rc;
+
+ if (parse_szint(tv, &sz, ";", "size")) { rc = -1; goto end; }
+ if (check_unsigned_range(sz, rd->arg.p, tv, "size")) { rc = -1; goto end; }
+ if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+ rv->u = sz; rc = 0;
+end:
+ return (rc);
+}
+
+/* --- @dump_size@ --- *
+ *
+ * Arguments: @const union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ * @unsigned style@ = output style (@TVSF_...@)
+ * @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns: ---
+ *
+ * Use: Dump a register value to the format output.
+ *
+ * Size values are dumped with a unit specifier, with a unit
+ * prefox only if the size is an exact multiple of the relevant
+ * power of two. Unless compact style is requested, the plain
+ * decimal and hex representations of the value are also
+ * printed.
+ */
+
+static void dump_size(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ unsigned style,
+ const struct gprintf_ops *gops, void *go)
+{
+ if (style&TVSF_RAW) gprintf(gops, go, "size:");
+ format_size(gops, go, rv->u, style);
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) {
+ gprintf(gops, go, " ; = %lu", (unsigned long)rv->u);
+ gprintf(gops, go, " = "); format_unsigned_hex(gops, go, rv->u);
+ maybe_format_unsigned_char(gops, go, rv->u);
+ }
+}
+
+/* Size type definitions. */
+const struct tvec_regty tvty_size = {
+ init_uint, trivial_release, eq_uint,
+ tobuf_uint, frombuf_uint,
+ parse_size, dump_size
+};
+
+/* --- @tvec_claimeq_size@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned long sz0, sz1@ = two sizes
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @sz0@ and @sz1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @u0@ and @u1@ are equal. As for
+ * @tvec_claim@ above, a test case is automatically begun and
+ * ended if none is already underway. If the values are
+ * unequal, then @tvec_fail@ is called, quoting @expr@, and the
+ * mismatched values are dumped: @u0@ is printed as the output
+ * value and @u1@ is printed as the input reference.
+ */
+
+int tvec_claimeq_size(struct tvec_state *tv,
+ unsigned long sz0, unsigned long sz1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->out[0].v.u = sz0; tv->in[0].v.u = sz1;
+ return (tvec_claimeq(tv, &tvty_size, 0, file, lno, expr));
+}
+
+/*----- Floating-point type -----------------------------------------------*/
+
+/* --- @int_float@ --- *
+ *
+ * Arguments: @union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a register value.
+ *
+ * Floating-point values are initialized to zero.
+ */
+
+static void init_float(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->f = 0.0; }
+
+/* --- @eq_float@ --- *
+ *
+ * Arguments: @const union tvec_regval *rv0, *rv1@ = register values
+ * @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns: Nonzero if the values are equal, zero if unequal
+ *
+ * Use: Compare register values for equality.
+ *
+ * Floating-point values may be considered equal if their
+ * absolute or relative difference is sufficiently small, as
+ * described in the register definition.
+ */
+
+static int eq_float(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+ { return (eqish_floating_p(rv0->f, rv1->f, rd->arg.p)); }
+
+/* --- @tobuf_float@ --- *
+ *
+ * Arguments: @buf *b@ = buffer
+ * @const union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns: Zero on success, %$-1$% on failure.
+ *
+ * Use: Serialize a register value to a buffer.
+ *
+ * Floating-point values are serialized as little-endian
+ * IEEE 754 Binary64.
+ */
+
+static int tobuf_float(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (buf_putf64l(b, rv->f)); }
+
+/* --- @frombuf_float@ --- *
+ *
+ * Arguments: @buf *b@ = buffer
+ * @union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns: Zero on success, %$-1$% on failure.
+ *
+ * Use: Deserialize a register value from a buffer.
+ *
+ * Floating-point values are serialized as little-endian
+ * IEEE 754 Binary64.
+ */
+
+static int frombuf_float(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (buf_getf64l(b, &rv->f)); }
+
+/* --- @parse_float@ --- *
+ *
+ * Arguments: @union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ * @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Parse a register value from an input file.
+ *
+ * Floating-point values are either NaN (%|#nan|%, if supported
+ * by the platform); positive or negative infinity (%|#inf|%,
+ * %|+#inf|%, or %|#+inf|% (preferring the last), and %|-#inf|%
+ * or %|#-inf|% (preferring the latter), if supported by the
+ * platform); or a number in strtod(3) syntax.
+ */
+
+static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ dstr d = DSTR_INIT;
+ int rc;
+
+ if (tvec_readword(tv, &d, 0, ";", "floating-point number"))
+ { rc = -1; goto end; }
+ if (parse_floating(&rv->f, 0, d.buf, rd->arg.p, tv))
+ { rc = -1; goto end; }
+ if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+ rc = 0;
+end:
+ dstr_destroy(&d);
+ return (rc);
+}
+
+/* --- @dump_float@ --- *
+ *
+ * Arguments: @const union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ * @unsigned style@ = output style (@TVSF_...@)
+ * @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns: ---
+ *
+ * Use: Dump a register value to the format output.
+ *
+ * Floating-point values are dumped in decimal or as a special
+ * token beginning with `%|#|%'. Some effort is taken to ensure
+ * that the output is sufficient to uniquely identify the
+ * original value, but, honestly, C makes this really hard.
+ */
+
+static void dump_float(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ unsigned style,
+ const struct gprintf_ops *gops, void *go)
+{
+ if (style&TVSF_RAW) gprintf(gops, go, "float:");
+ format_floating(gops, go, rv->f);
+}
+
+/* Floating-point type definition. */
+const struct tvec_regty tvty_float = {
+ init_float, trivial_release, eq_float,
+ tobuf_float, frombuf_float,
+ parse_float, dump_float
+};
+
+/* Predefined floating-point ranges. */
+const struct tvec_floatinfo
+ tvflt_finite = { TVFF_EXACT, -DBL_MAX, DBL_MAX, 0.0 },
+ tvflt_nonneg = { TVFF_EXACT, 0, DBL_MAX, 0.0 };
+
+/* --- @tvec_claimeqish_float@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @double f0, f1@ = two floating-point numbers
+ * @unsigned f@ = flags (@TVFF_...@)
+ * @double delta@ = maximum tolerable difference
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @f0@ and @f1@ are sufficiently close, otherwise
+ * zero.
+ *
+ * Use: Check that values of @f0@ and @f1@ are sufficiently close.
+ * As for @tvec_claim@ above, a test case is automatically begun
+ * and ended if none is already underway. If the values are
+ * too far apart, then @tvec_fail@ is called, quoting @expr@,
+ * and the mismatched values are dumped: @f0@ is printed as the
+ * output value and @f1@ is printed as the input reference.
+ *
+ * The details for the comparison are as follows.
+ *
+ * * A NaN value matches any other NaN, and nothing else.
+ *
+ * * An infinity matches another infinity of the same sign,
+ * and nothing else.
+ *
+ * * If @f&TVFF_EQMASK@ is @TVFF_EXACT@, then any
+ * representable number matches only itself: in particular,
+ * positive and negative zero are considered distinct.
+ * (This allows tests to check that they land on the correct
+ * side of branch cuts, for example.)
+ *
+ * * If @f&TVFF_EQMASK@ is @TVFF_ABSDELTA@, then %$x$% matches
+ * %$y$% when %$|x - y| < \delta$%.
+ *
+ * * If @f&TVFF_EQMASK@ is @TVFF_RELDELTA@, then %$x$% matches
+ * %$y$% when %$|1 - x/y| < \delta$%. (Note that this
+ * criterion is asymmetric. Write %$x \approx_\delta y$%
+ * if and only if %$|1 - x/y < \delta$%. Then, for example,
+ * if %$y/(1 + \delta) < x < y (1 - \delta)$%, then
+ * %$x \approx_\delta y$%, but %$y \not\approx_\delta x$%.)
+ */
+
+int tvec_claimeqish_float(struct tvec_state *tv,
+ double f0, double f1, unsigned f, double delta,
+ const char *file, unsigned lno,
+ const char *expr)
+{
+ struct tvec_floatinfo fi;
+ union tvec_misc arg;
+
+ fi.f = f; fi.min = fi.max = 0.0; fi.delta = delta; arg.p = &fi;
+ tv->out[0].v.f = f0; tv->in[0].v.f = f1;
+ return (tvec_claimeq(tv, &tvty_float, &arg, file, lno, expr));
+}
+
+/* --- @tvec_claimeq_float@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @double f0, f1@ = two floating-point numbers
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @f0@ and @f1@ are identical, otherwise zero.
+ *
+ * Use: Check that values of @f0@ and @f1@ are identical. The
+ * function is exactly equivalent to @tvec_claimeqish_float@
+ * with @f == TVFF_EXACT@.
+ */
+
+int tvec_claimeq_float(struct tvec_state *tv,
+ double f0, double f1,
+ const char *file, unsigned lno,
+ const char *expr)
+{
+ return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0,
+ file, lno, expr));
+}
+
+/*----- Durations ---------------------------------------------------------*/
+
+/* A duration is a floating-point number of seconds. Initialization and
+ * teardown, equality comparison, and serialization are as for floating-point
+ * values.
+ */
+
+static const struct duration_unit {
+ const char *unit;
+ double scale;
+ unsigned f;
+#define DUF_PREFER 1u
+} duration_units[] = {
+ { "Ys", 1e+24, 0 },
+ { "Zs", 1e+21, 0 },
+ { "Es", 1e+18, 0 },
+ { "Ps", 1e+15, 0 },
+ { "Ts", 1e+12, 0 },
+ { "Gs", 1e+9, 0 },
+ { "Ms", 1e+6, 0 },
+ { "ks", 1e+3, 0 },
+ { "hs", 1e+2, 0 },
+ { "das", 1e+1, 0 },
+
+ { "yr", 31557600.0, DUF_PREFER },
+ { "y", 31557600.0, 0 },
+ { "day", 86400.0, DUF_PREFER },
+ { "dy", 86400.0, 0 },
+ { "d", 86400.0, 0 },
+ { "hr", 3600.0, DUF_PREFER },
+ { "hour", 3600.0, 0 },
+ { "h", 3600.0, 0 },
+ { "min", 60.0, DUF_PREFER },
+ { "m", 60.0, 0 },
+
+ { "s", 1.0, DUF_PREFER },
+ { "sec", 1.0, 0 },
+
+ { "ds", 1e-1, 0 },
+ { "cs", 1e-2, 0 },
+ { "ms", 1e-3, DUF_PREFER },
+ { "µs", 1e-6, DUF_PREFER },
+ { "ns", 1e-9, DUF_PREFER },
+ { "ps", 1e-12, DUF_PREFER },
+ { "fs", 1e-15, DUF_PREFER },
+ { "as", 1e-18, DUF_PREFER },
+ { "zs", 1e-21, DUF_PREFER },
+ { "ys", 1e-24, DUF_PREFER },
+
+ { 0 }
+};
+
+/* --- @tvec_parsedurunit@ --- *
+ *
+ * Arguments: @double *scale_out@ = where to leave the scale
+ * @const char **p_inout@ = input unit string, updated
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: If @*p_inout@ begins with a unit string followed by the end
+ * of the string or some non-alphanumeric character, then store
+ * the corresponding scale factor in @*scale_out@, advance
+ * @*p_inout@ past the unit string, and return zero. Otherwise,
+ * return %$-1$%.
+ */
+
+int tvec_parsedurunit(double *scale_out, const char **p_inout)
+{
+ const char *p = *p_inout, *q;
+ const struct duration_unit *u;
+ size_t n;
+
+ while (ISSPACE(*p)) p++;
+ for (q = p; *q && ISALNUM(*q); q++);
+ n = q - p; if (!n) { *scale_out = 1.0; return (0); }
+
+ for (u = duration_units; u->unit; u++)
+ if (STRNCMP(p, ==, u->unit, n) && !u->unit[n])
+ { *scale_out = u->scale; *p_inout = q; return (0); }
+ return (-1);
+}
+
+/* --- @parse_duration@ --- *
+ *
+ * Arguments: @union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ * @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Parse a register value from an input file.
+ *
+ * Duration values are finite nonnegative floating-point
+ * numbers in @strtod@ syntax, optionally followed by a unit .
+ */
+
+static int parse_duration(union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ const struct duration_unit *u;
+ const char *q;
+ dstr d = DSTR_INIT;
+ double t;
+ int rc;
+
+ if (tvec_readword(tv, &d, 0, ";", "duration")) { rc = -1; goto end; }
+ if (parse_floating(&t, &q, d.buf,
+ rd->arg.p ? rd->arg.p : &tvflt_nonneg, tv))
+ { rc = -1; goto end; }
+
+ if (!*q) tvec_readword(tv, &d, &q, ";", 0);
+ if (*q) {
+ for (u = duration_units; u->unit; u++)
+ if (STRCMP(q, ==, u->unit)) { t *= u->scale; goto found_unit; }
+ rc = tvec_syntax(tv, *q, "end-of-line"); goto end;
+ found_unit:;
+ }
+
+ if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+ rv->f = t; rc = 0;
+end:
+ dstr_destroy(&d);
+ return (rc);
+}
+
+/* --- @dump_duration@ --- *
+ *
+ * Arguments: @const union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ * @unsigned style@ = output style (@TVSF_...@)
+ * @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns: ---
+ *
+ * Use: Dump a register value to the format output.
+ *
+ * Durations are dumped as a human-palatable scaled value with
+ * unit, and, if compact style is not requested, as a raw number
+ * of seconds at full precision as a comment.
+ */
+
+static void dump_duration(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ unsigned style,
+ const struct gprintf_ops *gops, void *go)
+{
+ const struct duration_unit *u;
+ double t = rv->f;
+
+ if (style&TVSF_RAW) {
+ gprintf(gops, go, "duration:");
+ format_floating(gops, go, rv->f);
+ gprintf(gops, go, "s");
+ } else {
+ if (!t) u = 0;
+ else {
+ for (u = duration_units; u->scale > t && u[1].unit; u++);
+ t /= u->scale;
+ }
+ gprintf(gops, go, "%.4g %s", t, u ? u->unit : "s");