chiark / gitweb /
@@@ fltfmt mess
[mLib] / test / tvec-types.h
diff --git a/test/tvec-types.h b/test/tvec-types.h
new file mode 100644 (file)
index 0000000..60f81d4
--- /dev/null
@@ -0,0 +1,876 @@
+/* -*-c-*-
+ *
+ * Test-vector framework basic register types
+ *
+ * (c) 2024 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib.  If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef MLIB_TVEC_TYPES_H
+#define MLIB_TVEC_TYPES_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_TVEC_H
+#  include "tvec.h"
+#endif
+
+/*----- Integer types: signed and unsigned --------------------------------*/
+
+/* Integers may be input in decimal, hex, binary, or octal, following
+ * approximately usual conventions.
+ *
+ *   * Signed integers may be preceded with a `+' or `-' sign.
+ *
+ *   * Decimal integers are just a sequence of decimal digits `0' ... `9'.
+ *
+ *   * Octal integers are a sequence of digits `0' ... `7', preceded by `0o'
+ *     or `0O'.
+ *
+ *   * Hexadecimal integers are a sequence of digits `0' ... `9', `a'
+ *     ... `f', or `A' ... `F', preceded by `0x' or `0X'.
+ *
+ *   * Radix-B integers are a sequence of digits `0' ... `9', `a' ... `f', or
+ *     `A' ... `F', each with value less than B, preceded by `Br' or `BR',
+ *     where 0 < B < 36 is expressed in decimal without any leading `0' or
+ *     internal underscores `_'.
+ *
+ *   * A digit sequence may contain internal underscore `_' separators, but
+ *     not before or after all of the digits; and two consecutive `_'
+ *     characters are not permitted.
+ */
+
+extern const struct tvec_regty tvty_int, tvty_uint;
+
+/* The @arg.p@ slot may be null or a pointer to @struct tvec_irange@ or
+ * @struct tvec_urange@ as appropriate.  The bounds are inclusive; use, e.g.,
+ * @LONG_MAX@ explicitly if one or the other bound is logically inapplicable.
+ * If %$m$% is nonzero, then the value must additionally be congruent to
+ * %$a$% modulo %$m$%.
+ */
+struct tvec_irange { long min, max, m, a; };
+struct tvec_urange { unsigned long min, max, m, a; };
+
+/* Bounds corresponding to common integer types. */
+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;
+
+/* --- @tvec_claimeq_int@, @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.
+ *
+ *             The @TVEC_CLAIM_INT@ macro is similar, only it (a) identifies
+ *             the file and line number of the call site automatically, and
+ *             (b) implicitly quotes the source text of the @i0@ and @i1@
+ *             arguments in the failure message.
+ */
+
+extern int tvec_claimeq_int(struct tvec_state */*tv*/,
+                           long /*i0*/, long /*i1*/,
+                           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))
+
+/* --- @tvec_claimeq_uint@, @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.
+ *
+ *             The @TVEC_CLAIM_UINT@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @u0@ and @u1@ arguments in the failure message.
+ */
+
+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_UINT(tv, u0, u1)                                  \
+       (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1))
+
+/*----- Size type ---------------------------------------------------------*/
+
+/* A size is an unsigned integer followed by an optional unit specifier
+ * consisting of an SI unit prefix and (optionally) the letter `B'.
+ */
+
+extern const struct tvec_regty tvty_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.
+ *
+ *             The @TVEC_CLAIM_SIZE@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @u0@ and @u1@ arguments in the failure message.
+ */
+
+int tvec_claimeq_size(struct tvec_state *tv,
+                     unsigned long sz0, unsigned long sz1,
+                     const char *file, unsigned lno, const char *expr);
+#define TVEC_CLAIMEQ_UINT(tv, u0, u1)                                  \
+       (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1))
+
+/*----- Floating-point type -----------------------------------------------*/
+
+/* 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.
+ *
+ * The comparison rules for floating-point numbers are complex: see
+ * @tvec_claimeqish_float@ for details.
+ */
+
+extern const struct tvec_regty tvty_float;
+
+struct tvec_floatinfo {
+  /* Details about acceptable floating-point values. */
+
+  unsigned f;                          /* flags (@TVFF_...@ bits) */
+#define TVFF_NOMIN 0x0001              /*   ignore @min@ (allow -∞) */
+#define TVFF_NOMAX 0x0002              /*   ignore @max@ (allow +∞) */
+#define TVFF_NANOK 0x0004              /*   permit NaN */
+#define TVFF_NEGINFOK 0x0008           /*   permit -∞, check finite */
+#define TVFF_POSINFOK 0x0010           /*   permit +∞, check finite */
+#define TVFF_INFOK (TVFF_NEGINFOK | TVFF_POSINFOK) /* permit ±∞ */
+#define TVFF_EQMASK 0x0f00             /*   how to compare */
+#define TVFF_EXACT 0x0000              /*     must equal exactly */
+#define TVFF_ABSDELTA 0x0100           /*     must be within @delta@ */
+#define TVFF_RELDELTA 0x0200           /*     diff < @delta@ fraction */
+  double min, max;                     /* smallest/largest value allowed */
+  double delta;                                /* maximum tolerable difference */
+};
+
+extern const struct tvec_floatinfo
+  tvflt_float, tvflt_double, tvflt_finite, tvflt_nonneg;
+
+/* --- @tvec_claimeqish_float@, @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$%.)
+ *
+ *             The @TVEC_CLAIM_FLOAT@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @f0@ and @f1@ arguments (and @delta@) in the failure
+ *             message.
+ */
+
+extern int tvec_claimeqish_float(struct tvec_state */*tv*/,
+                                double /*f0*/, double /*f1*/,
+                                unsigned /*f*/, double /*delta*/,
+                                const char */*file*/, unsigned /*lno*/,
+                                const char */*expr*/);
+#define TVEC_CLAIMEQISH_FLOAT(tv, f0, f1, f, delta)                    \
+       (tvec_claimeqish_float(tv, f0, f1, f, delta, __FILE__, __LINE__, \
+                              #f0 " /= " #f1 " (+/- " #delta ")"))
+
+/* --- @tvec_claimeq_float@, @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 @u1@ 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@; the macro is similarly like
+ *             @TVEC_CLAIMEQISH_FLOAT@ with @f == TVFF_EXACT@, except that
+ *             it doesn't bother to quote a delta.
+ */
+
+extern int tvec_claimeq_float(struct tvec_state */*tv*/,
+                             double /*f0*/, double /*f1*/,
+                             const char */*file*/, unsigned /*lno*/,
+                             const char */*expr*/);
+#define TVEC_CLAIMEQ_FLOAT(tv, f0, f1)                                 \
+       (tvec_claimeq_float(tv, f0, f1, __FILE__, __LINE__, #f0 " /= " #f1))
+
+/*----- Durations ---------------------------------------------------------*/
+
+/* A duration measures a time interval in seconds.  The input format consists
+ * of a nonnegative decimal floating-point number in @strtod@ format followed
+ * by an optional unit specification.
+ */
+
+extern const struct tvec_regty tvty_duration;
+
+/* --- @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$%.
+ */
+
+extern int tvec_parsedurunit(double */*scale_out*/,
+                            const char **/*p_inout*/);
+
+/* --- @tvec_claimeqish_duration@, @TVEC_CLAIMEQISH_DURATION@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @double t0, t1@ = two durations
+ *             @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 @t0@ and @t1@ are sufficiently close, otherwise
+ *             zero.
+ *
+ * Use:                Check that values of @t0@ and @t1@ are sufficiently close.
+ *             This is essentially the same as @tvec_claimeqish_float@, only
+ *             it dumps the values as durations on a mismatch.
+ *
+ *             The @TVEC_CLAIM_FLOAT@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @t0@ and @t1@ arguments (and @delta@) in the failure
+ *             message.
+ */
+
+extern int tvec_claimeqish_duration(struct tvec_state */*tv*/,
+                                   double /*t0*/, double /*t1*/,
+                                   unsigned /*f*/, double /*delta*/,
+                                   const char */*file*/, unsigned /*lno*/,
+                                   const char */*expr*/);
+#define TVEC_CLAIMEQISH_DURATION(tv, t0, t1, f, delta)                 \
+       (tvec_claimeqish_duration(tv, t0, t1, f, delta, __FILE__, __LINE__, \
+                              #t0 " /= " #t1 " (+/- " #delta ")"))
+
+/* --- @tvec_claimeq_duration@, @TVEC_CLAIMEQ_DURATION@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @double t0, t1@ = two durations
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *             @const char *expr@ = the expression to quote on failure
+ *
+ * Returns:    Nonzero if @t0@ and @t1@ are identical, otherwise zero.
+ *
+ * Use:                Check that values of @t0@ and @t1@ are identical.  The
+ *             function is exactly equivalent to @tvec_claimeqish_duration@
+ *             with @f == TVFF_EXACT@; the macro is similarly like
+ *             @TVEC_CLAIMEQISH_DURATION@ with @f == TVFF_EXACT@, except
+ *             that it doesn't bother to quote a delta.
+ */
+
+int tvec_claimeq_duration(struct tvec_state */*tv*/,
+                         double /*t0*/, double /*t1*/,
+                         const char */*file*/, unsigned /*lno*/,
+                         const char */*expr*/);
+#define TVEC_CLAIMEQ_DURATION(tv, t0, t1)                                      \
+       (tvec_claimeq_float(tv, t0, t1, __FILE__, __LINE__, #t0 " /= " #t1))
+
+/*----- Enumerated types --------------------------------------------------*/
+
+/* An enumeration describes a set of values of some underlying type, each of
+ * which has a symbolic name.  Values outside of the defined set can occur --
+ * on output, because of bugs in the tested code, or on input to test
+ * handling of unexpected values.
+ *
+ * There is a distinct enumerated type for each of the branches of
+ * @tvec_misc@.  In the following, we write @t@ for the type code, which is
+ * @i@ for signed integer, @u@ for unsigned integer, @f@ for floating-point,
+ * and @p@ for pointer.
+ *
+ * On input, an enumerated value may be given by name or as a literal value.
+ * For enumerations based on numeric types, the literal values can be written
+ * in the same syntax as the underlying values.  For enumerations based on
+ * pointers, the only permitted literal is %|#nil|%, which denotes a null
+ * pointer.  On output, names are preferred (with the underlying value given
+ * in a comment).
+ */
+
+#define DEFENUMTY(tag, ty, slot)                                       \
+       extern const struct tvec_regty tvty_##slot##enum;
+TVEC_MISCSLOTS(DEFENUMTY)
+#undef DEFENUMTY
+
+/* A @struct tvec_tassoc@ associates a string tag with a value. */
+#define DEFASSOC(tag_, ty, slot)                                       \
+       struct tvec_##slot##assoc { const char *tag; ty slot; };
+TVEC_MISCSLOTS(DEFASSOC)
+#undef DEFASSOC
+
+#define TVEC_ENDENUM { 0, 0 }
+
+/* Information about an enumerated type. */
+#define DEFINFO(tag, ty, slot)                                         \
+       struct tvec_##slot##enuminfo {                                  \
+         const char *name;             /* type name for diagnostics */ \
+         const struct tvec_##slot##assoc *av; /* name/value mappings */ \
+         EXTRA_##tag##_INFOSLOTS       /* type-specific extra info */  \
+       };
+
+#define EXTRA_INT_INFOSLOTS                                            \
+       const struct tvec_irange *ir;   /* allowed range of raw values */
+
+#define EXTRA_UINT_INFOSLOTS                                           \
+       const struct tvec_urange *ur;   /* allowed range of raw values */
+
+#define EXTRA_FLT_INFOSLOTS                                            \
+       const struct tvec_floatinfo *fi; /* range and matching policy */
+
+#define EXTRA_PTR_INFOSLOTS            /* (nothing) */
+
+TVEC_MISCSLOTS(DEFINFO)
+
+#undef EXTRA_INT_INFOSLOTS
+#undef EXTRA_UINT_INFOSLOTS
+#undef EXTRA_FLT_INFOSLOTS
+#undef EXTRA_PTR_INFOSLOTS
+
+#undef DEFINFO
+
+/* Standard enumerations. */
+extern const struct tvec_ienuminfo tvenum_bool;
+extern const struct tvec_ienuminfo tvenum_cmp;
+
+/* --- @tvec_claimeq_tenum@, @TVEC_CLAIMEQ_TENUM@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const struct tvec_typeenuminfo *ei@ = enumeration type info
+ *             @ty t0, t1@ = two values
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *             @const char *expr@ = the expression to quote on failure
+ *
+ * Returns:    Nonzero if @t0@ and @t1@ are equal, otherwise zero.
+ *
+ * Use:                Check that values of @t0@ and @t1@ 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: @t0@ is printed as the output
+ *             value and @t1@ is printed as the input reference.
+ *
+ *             The @TVEC_CLAIM_TENUM@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @t0@ and @t1@ arguments in the failure message.
+ */
+
+#define DECLCLAIM(tag, ty, slot)                                       \
+       extern int tvec_claimeq_##slot##enum                            \
+         (struct tvec_state */*tv*/,                                   \
+          const struct tvec_##slot##enuminfo */*ei*/,                  \
+          ty /*t0*/, ty /*t1*/,                                        \
+          const char */*file*/, unsigned /*lno*/, const char */*expr*/);
+TVEC_MISCSLOTS(DECLCLAIM)
+#undef DECLCLAIM
+#define TVEC_CLAIMEQ_IENUM(tv, ei, i0, i1)                             \
+       (tvec_claimeq_ienum(tv, ei, i0, i1,                             \
+                           __FILE__, __LINE__, #i0 " /= " #i1))
+#define TVEC_CLAIMEQ_UENUM(tv, ei, u0, u1)                             \
+       (tvec_claimeq_uenum(tv, ei, u0, u1,                             \
+                           __FILE__, __LINE__, #u0 " /= " #u1))
+#define TVEC_CLAIMEQ_FENUM(tv, ei, f0, f1)                             \
+       (tvec_claimeq_fenum(tv, ei, f0, f1,                             \
+                           __FILE__, __LINE__, #f0 " /= " #f1))
+#define TVEC_CLAIMEQ_PENUM(tv, ei, p0, p1)                             \
+       (tvec_claimeq_penum(tv, ei, p0, p1,                             \
+                           __FILE__, __LINE__, #p0 " /= " #p1))
+
+/*----- Flags type --------------------------------------------------------*/
+
+/* A flags value packs a number of fields into a single nonnegative integer.
+ * Symbolic names are associated with the possible values of the various
+ * fields; more precisely, each name is associated with a value and a
+ * covering bitmask.
+ *
+ * The input syntax is a sequence of items separated by `%|||%' signs.  Each
+ * item may be the symbolic name of a field value, or a literal unsigned
+ * integer.  The masks associated with the given symbolic names must be
+ * disjoint.  The resulting numerical value is simply the bitwise OR of the
+ * given values.
+ *
+ * On output, the table of symbolic names and their associated values and
+ * masks is repeatedly scanned, in order, to find disjoint matches -- i.e.,
+ * entries whose value matches the target value in the bit positions
+ * indicated by the mask, and whose mask doesn't overlap with any previously
+ * found matches; the names are then output, separated by `%|||%'.  Any
+ * remaining nonzero bits not covered by any of the matching masks are output
+ * as a single literal integer, in hex.
+ */
+
+extern const struct tvec_regty tvty_flags;
+
+struct tvec_flag {
+  /* Definition of a single flag or bitfield value.
+   *
+   * Each named setting comes with a value @v@ and a mask @m@; the mask
+   * should cover all of the value bits, i.e., @(v&~m) == 0@.
+   */
+
+  const char *tag;                     /* name */
+  unsigned long m, v;                  /* mask and value */
+};
+
+#define TVEC_ENDFLAGS { 0, 0, 0 }
+
+struct tvec_flaginfo {
+  /* Information about a flags type. */
+
+  const char *name;                    /* type name for diagnostics  */
+  const struct tvec_flag *fv;          /* name/mask/value mappings */
+  const struct tvec_urange *range;     /* permitted range for literals */
+};
+
+/* --- @tvec_claimeq_flags@, @TVEC_CLAIMEQ_FLAGS@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const struct tvec_flaginfo *fi@ = flags type info
+ *             @unsigned long f0, f1@ = two values
+ *             @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 equal, otherwise zero.
+ *
+ * Use:                Check that values of @f0@ and @f1@ 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: @f0@ is printed as the output
+ *             value and @f1@ is printed as the input reference.
+ *
+ *             The @TVEC_CLAIM_FLAGS@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @f0@ and @f1@ arguments in the failure message.
+ */
+
+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))
+
+/*----- Character type ----------------------------------------------------*/
+
+/* A character value holds a character, as read by @fgetc@.  The special
+ * @EOF@ value can also be represented.
+ *
+ * On input, a character value can be given by symbolic name, with a leading
+ * `%|#|%'; or a character or `%|\|%'-escape sequence, optionally in single
+ * quotes.
+ *
+ * The following escape sequences and character names are recognized.
+ *
+ *   * `%|#eof|%' is the special end-of-file marker.
+ *
+ *   * `%|#nul|%' is the NUL character, sometimes used to terminate strings.
+ *
+ *   * `%|bell|%', `%|bel|%', `%|ding|%', or `%|\a|%' is the BEL character
+ *     used to ring the terminal bell (or do some other thing to attract the
+ *     user's attention).
+ *
+ *   * %|#backspace|%, %|#bs|%, or %|\b|% is the backspace character, used to
+ *     move the cursor backwords by one cell.
+ *
+ *   * %|#escape|% %|#esc|%, or%|\e|% is the escape character, used to
+ *     introduce special terminal commands.
+ *
+ *   * %|#formfeed|%, %|#ff|%, or %|\f|% is the formfeed character, used to
+ *     separate pages of text.
+ *
+ *   * %|#newline|%, %|#linefeed|%, %|#lf|%, %|#nl|%, or %|\n|% is the
+ *     newline character, used to terminate lines of text or advance the
+ *     cursor to the next line (perhaps without returning it to the start of
+ *     the line).
+ *
+ *   * %|#return|%, %|#carriage-return|%, %|#cr|%, or %|\r|% is the
+ *     carriage-return character, used to return the cursor to the start of
+ *     the line.
+ *
+ *   * %|#tab|%, %|#horizontal-tab|%, %|#ht|%, or %|\t|% is the tab
+ *     character, used to advance the cursor to the next tab stop on the
+ *     current line.
+ *
+ *   * %|#vertical-tab|%, %|#vt|%, %|\v|% is the vertical tab character.
+ *
+ *   * %|#space|%, %|#spc|% is the space character.
+ *
+ *   * %|#delete|%, %|#del|% is the delete character, used to erase the most
+ *     recent character.
+ *
+ *   * %|\'|% is the single-quote character.
+ *
+ *   * %|\\|% is the backslash character.
+ *
+ *   * %|\"|% is the double-quote character.
+ *
+ *   * %|\NNN|% or %|\{NNN}|% is the character with code NNN in octal.  The
+ *     NNN may be up to three digits long.
+ *
+ *   * %|\xNN|% or %|\x{NN}|% is the character with code NNN in hexadecimal.
+ */
+
+extern const struct tvec_regty tvty_char;
+
+/* --- @tvec_claimeq_char@, @TVEC_CLAIMEQ_CHAR@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @int ch0, ch1@ = two character codes
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *             @const char *expr@ = the expression to quote on failure
+ *
+ * Returns:    Nonzero if @ch0@ and @ch1@ are equal, otherwise zero.
+ *
+ * Use:                Check that values of @ch0@ and @ch1@ 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: @ch0@ is printed as the output
+ *             value and @ch1@ is printed as the input reference.
+ *
+ *             The @TVEC_CLAIM_CHAR@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @ch0@ and @ch1@ arguments in the failure message.
+ */
+
+extern int tvec_claimeq_char(struct tvec_state */*tv*/,
+                            int /*ch0*/, int /*ch1*/,
+                            const char */*file*/, unsigned /*lno*/,
+                            const char */*expr*/);
+#define TVEC_CLAIMEQ_CHAR(tv, c0, c1)                                  \
+       (tvec_claimeq_char(tv, c0, c1, __FILE__, __LINE__, #c0 " /= " #c1))
+
+/*----- Text and binary string types --------------------------------------*/
+
+/* A string is a sequence of octets.  Text and binary strings differ
+ * primarily in presentation: text strings are shown as raw characters where
+ * possible; binary strings are shown as hex dumps with an auxiliary text
+ * display.  Storage for strings always uses the standard C library
+ * allocator, though applications will probably need to call @malloc@ or
+ * @free@ only rarely.
+ *
+ * The input format for both kinds of strings is basically the same: a
+ * `compound string', consisting of
+ *
+ *   * single-quoted strings, which are interpreted entirely literally, but
+ *     can't contain single quotes or newlines;
+ *
+ *   * double-quoted strings, in which `%|\|%'-escapes are interpreted as for
+ *     characters;
+ *
+ *   * character names, marked by an initial `%|#|%' sign;
+ *
+ *   * special tokens marked by an initial `%|!|%' sign; or
+ *
+ *   * barewords interpreted according to the current coding scheme.
+ *
+ * The special tokens are
+ *
+ *   * `%|!bare|%', which causes subsequent sequences of barewords to be
+ *     treated as plain text;
+ *
+ *   * `%|!hex|%', `%|!base32|%', `%|!base64|%', which cause subsequent
+ *     barewords to be decoded in the requested manner.
+ *
+ *   * `%|!repeat|% %$n$% %|{|% %%\textit{string}%% %|}|%', which includes
+ *     %$n$% copies of the (compound) string.
+ *
+ * The only difference between text and binary strings is that the initial
+ * coding scheme is %|bare|% for text strings and %|hex|% for binary strings.
+ *
+ * Either kind of string can contain internal nul characters.  A trailing nul
+ * is appended -- beyond the stated input length -- to input strings as a
+ * convenience to test functions.  Test functions may include such a nul
+ * character on output but this is not checked by the equality test.
+ *
+ * A @struct tvec_urange@ may be supplied as an argument: the length of the
+ * string (in bytes) will be checked against the permitted range.
+ */
+
+extern const struct tvec_regty tvty_text, tvty_bytes;
+
+/* --- @tvec_alloctext@, @tvec_allocbytes@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @size_t sz@ = required size
+ *
+ * Returns:    ---
+ *
+ * Use:                Allocated space in a text or binary string register.  If the
+ *             current register size is sufficient, its buffer is left
+ *             alone; otherwise, the old buffer, if any, is freed and a
+ *             fresh buffer allocated.  These functions are not intended to
+ *             be used to adjust a buffer repeatedly, e.g., while building
+ *             output incrementally: (a) they will perform badly, and (b)
+ *             the old buffer contents are simply discarded if reallocation
+ *             is necessary.  Instead, use a @dbuf@ or @dstr@.
+ *
+ *             The @tvec_alloctext@ function sneakily allocates an extra
+ *             byte for a terminating zero.  The @tvec_allocbytes@ function
+ *             doesn't do this.
+ */
+
+extern void tvec_alloctext(union tvec_regval */*rv*/, size_t /*sz*/);
+extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/);
+
+/* --- @tvec_claimeq_text@, @TVEC_CLAIMEQ_TEXT@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *p0@, @size_t sz0@ = first string with length
+ *             @const char *p1@, @size_t sz1@ = second string with length
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *             @const char *expr@ = the expression to quote on failure
+ *
+ * Returns:    Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ *             zero.
+ *
+ * Use:                Check that strings at @p0@ and @p1@ 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: @p0@ is printed as the output
+ *             value and @p1@ is printed as the input reference.
+ *
+ *             The @TVEC_CLAIM_TEXT@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @ch0@ and @ch1@ arguments in the failure message.
+ */
+
+extern int tvec_claimeq_text(struct tvec_state */*tv*/,
+                            const char */*p0*/, size_t /*sz0*/,
+                            const char */*p1*/, size_t /*sz1*/,
+                            const char */*file*/, unsigned /*lno*/,
+                            const char */*expr*/);
+#define TVEC_CLAIMEQ_TEXT(tv, p0, sz0, p1, sz1)                                \
+       (tvec_claimeq_text(tv, p0, sz0, p1, sz1, __FILE__, __LINE__,    \
+                          #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
+
+/* --- @tvec_claimeq_textz@, @TVEC_CLAIMEQ_TEXTZ@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *p0, *p1@ = two strings to compare
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *             @const char *expr@ = the expression to quote on failure
+ *
+ * Returns:    Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ *             zero.
+ *
+ * Use:                Check that strings at @p0@ and @p1@ are equal, as for
+ *             @tvec_claimeq_string@, except that the strings are assumed
+ *             null-terminated, so their lengths don't need to be supplied
+ *             explicitly.  The macro is similarly like @TVEC_CLAIMEQ_TEXT@.
+ */
+
+extern int tvec_claimeq_textz(struct tvec_state */*tv*/,
+                             const char */*p0*/, const char */*p1*/,
+                             const char */*file*/, unsigned /*lno*/,
+                             const char */*expr*/);
+#define TVEC_CLAIMEQ_TEXTZ(tv, p0, p1)                                 \
+       (tvec_claimeq_textz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1))
+
+/* --- @tvec_claimeq_bytes@, @TVEC_CLAIMEQ_BYTES@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const void *p0@, @size_t sz0@ = first string with length
+ *             @const void *p1@, @size_t sz1@ = second string with length
+ *             @const char *file@, @unsigned @lno@ = calling file and line
+ *             @const char *expr@ = the expression to quote on failure
+ *
+ * Returns:    Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ *             zero.
+ *
+ * Use:                Check that binary strings at @p0@ and @p1@ 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: @p0@ is printed as the output
+ *             value and @p1@ is printed as the input reference.
+ *
+ *             The @TVEC_CLAIM_STRING@ macro is similar, only it (a)
+ *             identifies the file and line number of the call site
+ *             automatically, and (b) implicitly quotes the source text of
+ *             the @ch0@ and @ch1@ arguments in the failure message.
+ */
+
+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_BYTES(tv, p0, sz0, p1, sz1)                       \
+       (tvec_claimeq(tv, p0, sz0, p1, sz1, __FILE__, __LINE__,         \
+                     #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
+
+/*----- Buffer type -------------------------------------------------------*/
+
+/* Buffer registers are primarily used for benchmarking.  Only a buffer's
+ * allocation parameters are significant: its contents are ignored on
+ * comparison and output, and unspecified on input.
+ *
+ * The input format gives the buffer's size, and an optional alignment
+ * specification, in the form %|SZ [`@' M [`+' A]]|%.  Each of %|SZ|%, %|M|%
+ * and %|A|% are sizes, as an integer, optionally suffixed with a unit `kB',
+ * `MB', `GB', `TB', `PB', `EB', `ZB', `YB' (with or without the `B')
+ * denoting a power of 1024.  The %|SZ|% gives the (effective) buffer size.
+ * %|M|% is the `alignment quantum' and %|A|% is the `alignment offset'; both
+ * default to zero, but if %|M|% is nonzero then the start of the buffer is
+ * aligned such that it is %|A|% more than a multiple of %|M|% bytes.  Note
+ * that %|M|% need not be a power of two, though this is common.
+ *
+ * Units other than `B' are used on output only when the size would be
+ * expressed exactly.
+ *
+ * Buffers are %%\emph{not}%% allocated by default.  In benchmarks, this is
+ * best done in a @before@ function.
+ *
+ * No @claimeq@ functions or macros are provided for buffers because they
+ * don't seem very useful.
+ */
+
+extern const struct tvec_regty tvty_buffer;
+
+/* --- @tvec_initbuffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const union tvec_regval *ref@ = reference buffer
+ *             @size_t sz@ = size to allocate
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize the alignment parameters in @rv@ to match @ref@,
+ *             and the size to @sz@.
+ */
+
+extern void tvec_initbuffer(union tvec_regval */*rv*/,
+                           const union tvec_regval */*ref*/, size_t /*sz*/);
+
+/* --- @tvec_allocbuffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *
+ * Returns:    ---
+ *
+ * Use:                Allocate @sz@ bytes to the buffer and fill the space with a
+ *             distinctive pattern.
+ */
+
+extern void tvec_allocbuffer(union tvec_regval */*rv*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif