chiark / gitweb /
@@@ so much mess
[mLib] / test / tvec-types.c
index 249371b6e00d95dd5ed285502651fbe0d278a0f1..8be8ae2004bcc13482e154194c99013322ebd360 100644 (file)
@@ -30,7 +30,9 @@
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
+#include <float.h>
 #include <limits.h>
+#include <math.h>
 #include <stdio.h>
 #include <string.h>
 
 
 /*----- Preliminary utilities ---------------------------------------------*/
 
+#ifdef isnan
+#  define NANP(x) isnan(x)
+#else
+#  define NANP(x) (!((x) == (x)))
+#endif
+
+#ifdef isinf
+#  define INFP(x) isinf(x)
+#else
+#  define INFP(x) ((x) > DBL_MAX || (x) < DBL_MIN)
+#endif
+
 static int signed_to_buf(buf *b, long i)
 {
   kludge64 k;
@@ -126,11 +140,13 @@ static int parse_signed(long *i_out, const char *p,
 
   olderr = errno; errno = 0;
   pp = p; if (*pp == '-' || *pp == '+') pp++;
-  if (!ISDIGIT(*pp)) return (tvec_syntax(tv, *pp, "signed integer"));
+  if (!ISDIGIT(*pp))
+    return (tvec_syntax(tv, *pp ? *pp : fgetc(tv->fp), "signed integer"));
   i = strtol(p, &q, 0);
   if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line"));
-  if (errno) return (tvec_error(tv, "invalid integer `%s'", p));
-  check_signed_range(i, ir, tv);
+  if (errno)
+    return (tvec_error(tv, "invalid integer `%s': %s", p, strerror(errno)));
+  if (check_signed_range(i, ir, tv)) return (-1);
   errno = olderr; *i_out = i;
   return (0);
 }
@@ -147,12 +163,210 @@ static int parse_unsigned(unsigned long *u_out, const char *p,
   if (!ISDIGIT(*p)) return (tvec_syntax(tv, *p, "unsigned integer"));
   u = strtoul(p, &q, 0);
   if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line"));
-  if (errno) return (tvec_error(tv, "invalid integer `%s'", p));
-  check_unsigned_range(u, ur, tv);
+  if (errno)
+    return (tvec_error(tv, "invalid integer `%s': %s", p, strerror(errno)));
+  if (check_unsigned_range(u, ur, tv)) return (-1);
   errno = olderr; *u_out = u;
   return (0);
 }
 
+static void format_floating(const struct gprintf_ops *gops, void *go,
+                           double x)
+{
+  int prec;
+
+  if (NANP(x))
+    gprintf(gops, go, "#nan");
+  else if (INFP(x))
+    gprintf(gops, go, x > 0 ? "#+inf" : "#-inf");
+  else {
+    /* Ugh.  C doesn't provide any function for just printing a
+     * floating-point number /correctly/, i.e., so that you can read the
+     * result back and recover the number you first thought of.  There are
+     * complicated algorithms published for doing this, but I really don't
+     * want to get into that here.  So we have this.
+     *
+     * The sign doesn't cause significant difficulty so we're going to ignore
+     * it for now.  So suppose we're given a number %$x = f b^e$%, in
+     * base-%$b$% format, so %$f b^n$% and %$e$% are integers, with
+     * %$0 \le f < 1$%.  We're going to convert it into the nearest integer
+     * of the form %$X = F B^E$%, with similar conditions, only with the
+     * additional requirement that %$X$% is normalized, i.e., that %$X = 0$%
+     * or %$F \ge B^{-N}$%.
+     *
+     * We're rounding to the nearest such %$X$%.  If there is to be ambiguity
+     * in the conversion, then some %$x = f b^e$% and the next smallest
+     * representable number %$x' = x + b^{e-n}$% must both map to the same
+     * %$X$%, which means both %$x$% and %$x'$% must be nearer to %$X$% than
+     * any other number representable in the target system.  The nest larger
+     * number is %$X' = X + B^{E-N}$%; the next smaller number will normally
+     * be %$W = X - B^{E-N}$%, but if %$F = 1/B$ then the next smaller number
+     * is actually %$X - B^{E-N-1}$%.  We ignore this latter possibility in
+     * the pursuit of a conservative estimate (though actually it doesn't
+     * matter).
+     *
+     * If both %$x$% and %$x'$% map to %$X$% then we must have
+     * %$L = X - B^{E-N}/2 \le x$% and %$x + b^{e-n} \le R = X + B^{E-N}/2$%;
+     * so firstly %$f b^e = x \ge L = W + B^{E-N}/2 > W = (F - B^{-N}) B^E$%,
+     * and secondly %$b^{e-n} \le B^{E-N}$%.  Since these inequalities are in
+     * opposite senses, we can divide, giving
+     *
+     *        %$f b^e/b^{e-n} > (F - B^{-N}) B^E/B^{E-N}$% ,
+     *
+     * whence
+     *
+     *        %$f b^n > (F - B^{-N}) B^N = F B^N - 1$% .
+     *
+     * Now %$f \le 1 - b^{-n}$%, and %$F \ge B^{-1}$%, so, for this to be
+     * possible, it must be the case that
+     *
+     *        %$(1 - b^{-n}) b^n = b^n - 1 > B^{N-1} - 1$% .
+     *
+     * Then rearrange and take logarithms, obtaining
+     *
+     *        %$(N - 1) \log B < n \log b$% ,
+     *
+     * and so
+     *
+     *        %$N < n \log b/\log B + 1$% .
+     *
+     * Recall that this is a necessary condition for a collision to occur; we
+     * are therefore safe whenever
+     *
+     *        %$N \ge n \log b/\log B + 1$% ;
+     *
+     * so, taking ceilings,
+     *
+     *        %$N \ge \lceil n \log b/\log B \rceil + 1$% .
+     *
+     * So that's why we have this.
+     *
+     * I'm going to assume that @n = DBL_MANT_DIG@ is sufficiently small that
+     * we can calculate this without ending up on the wrong side of an
+     * integer boundary.
+     *
+     * In C11, we have @DBL_DECIMAL_DIG@, which should be the same value only
+     * as a constant.  Except that modern compilers are more than clever
+     * enough to work out that this is a constant anyway.
+     *
+     * This is sometimes an overestimate: we'll print out meaningless digits
+     * that don't represent anything we actually know about the number in
+     * question.  To fix that, we'd need a complicated algorithm like Steele
+     * and White's Dragon4, Gay's @dtoa@, or Burger and Dybvig's algorithm
+     * (note that Loitsch's Grisu2 is conservative, and Grisu3 hands off to
+     * something else in difficult situations).
+     */
+
+    prec = ceil(DBL_MANT_DIG*log(FLT_RADIX)/log(10)) + 1;
+    gprintf(gops, go, "%.*g", prec, x);
+  }
+}
+
+static int eqish_floating_p(double x, double y,
+                           const struct tvec_floatinfo *fi)
+{
+  double xx, yy, t;
+
+  if (NANP(x)) return (NANP(y)); else if (NANP(y)) return (0);
+  if (INFP(x)) return (x == y); else if (INFP(y)) return (0);
+
+  switch (fi ? fi->f&TVFF_EQMASK : TVFF_EXACT) {
+    case TVFF_EXACT:
+      return (x == y);
+    case TVFF_ABSDELTA:
+      t = x - y; if (t < 0) t = -t; return (t < fi->delta);
+    case TVFF_RELDELTA:
+      xx = x >= 0 ? x : -x; yy = y >= 0 ? y : -y;
+      if (xx < yy) { t = x; x = y; y = t; }
+      return (1.0 - y/x < fi->delta);
+    default:
+      abort();
+  }
+}
+
+static int parse_floating(double *x_out, const char *p,
+                         const struct tvec_floatinfo *fi,
+                         struct tvec_state *tv)
+{
+  const char *pp; char *q;
+  dstr d = DSTR_INIT;
+  double x;
+  int olderr, rc;
+
+  if (STRCMP(p, ==, "#nan")) {
+#ifdef NAN
+    x = NAN; rc = 0;
+#else
+    tvec_error(tv, "NaN not supported on this system");
+    rc = -1; goto end;
+#endif
+  } else if (STRCMP(p, ==, "#inf") ||
+            STRCMP(p, ==, "#+inf") ||
+            STRCMP(p, ==, "+#inf")) {
+#ifdef NAN
+    x = INFINITY; rc = 0;
+#else
+    tvec_error(tv, "infinity not supported on this system");
+    rc = -1; goto end;
+#endif
+  } else if (STRCMP(p, ==, "#-inf") ||
+            STRCMP(p, ==, "-#inf")) {
+#ifdef NAN
+    x = -INFINITY; rc = 0;
+#else
+    tvec_error(tv, "infinity not supported on this system");
+    rc = -1; goto end;
+#endif
+  } else {
+    pp = p;
+    if (*pp == '+' || *pp == '-') pp++;
+    if (*pp == '.') pp++;
+    if (!ISDIGIT(*pp)) {
+      tvec_syntax(tv, *pp ? *pp : fgetc(tv->fp), "floating-point number");
+      rc = -1; goto end;
+    }
+    olderr = errno; errno = 0;
+    x = strtod(p, &q);
+    if (*q) {
+      tvec_syntax(tv, *q, "end-of-line");
+      rc = -1; goto end;
+    }
+    if (errno && (errno != ERANGE || (x > 0 ? -x : x) == HUGE_VAL)) {
+      tvec_error(tv, "invalid floating-point number `%s': %s",
+                p, strerror(errno));
+      rc = -1; goto end;
+    }
+    errno = olderr;
+  }
+
+  if (NANP(x) && fi && !(fi->f&TVFF_NANOK)) {
+    tvec_error(tv, "#nan not allowed here");
+    rc = -1; goto end;
+  }
+  if (fi && ((!(fi->f&TVFF_NOMIN) && x < fi->min) ||
+            (!(fi->f&TVFF_NOMAX) && x > fi->max))) {
+    dstr_puts(&d, "floating-point number ");
+    format_floating(&dstr_printops, &d, x);
+    dstr_puts(&d, " out of range (must be in ");
+    if (fi->f&TVFF_NOMIN)
+      dstr_puts(&d, "(#-inf");
+    else
+      { dstr_putc(&d, '['); format_floating(&dstr_printops, &d, fi->min); }
+    dstr_puts(&d, " .. ");
+    if (fi->f&TVFF_NOMAX)
+      dstr_puts(&d, "#+inf)");
+    else
+      { format_floating(&dstr_printops, &d, fi->max); dstr_putc(&d, ']'); }
+    dstr_putc(&d, ')'); dstr_putz(&d);
+    tvec_error(tv, "%s", d.buf); rc = -1; goto end;
+  }
+
+  *x_out = x; rc = 0;
+end:
+  dstr_destroy(&d);
+  return (rc);
+}
+
 static int convert_hex(char ch, int *v_out)
 {
   if ('0' <= ch && ch <= '9') { *v_out = ch - '0'; return (0); }
@@ -161,76 +375,81 @@ static int convert_hex(char ch, int *v_out)
   else return (-1);
 }
 
-static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
+static int read_escape(int *ch_out, struct tvec_state *tv)
 {
-  char expect[4];
   int ch, i, esc;
   unsigned f = 0;
 #define f_brace 1u
 
-  sprintf(expect, "`%c'", quote);
+  ch = getc(tv->fp);
+  switch (ch) {
+    case EOF: case '\n': tvec_syntax(tv, ch, "string escape");
+    case '\'': *ch_out = '\''; break;
+    case '\\': *ch_out = '\\'; break;
+    case '"': *ch_out = '"'; break;
+    case 'a': *ch_out = '\a'; break;
+    case 'b': *ch_out = '\b'; break;
+    case 'e': *ch_out = '\x1b'; break;
+    case 'f': *ch_out = '\f'; break;
+    case 'n': *ch_out = '\n'; break;
+    case 'r': *ch_out = '\r'; break;
+    case 't': *ch_out = '\t'; break;
+    case 'v': *ch_out = '\v'; break;
+
+    case 'x':
+      ch = getc(tv->fp);
+      if (ch == '{') { f |= f_brace; ch = getc(tv->fp); }
+      else f &= ~f_brace;
+      if (convert_hex(ch, &esc))
+       return (tvec_syntax(tv, ch, "hex digit"));
+      for (;;) {
+       ch = getc(tv->fp); if (convert_hex(ch, &i)) break;
+       esc = 8*esc + i;
+       if (esc > UCHAR_MAX)
+         return (tvec_error(tv,
+                            "character code %d out of range", esc));
+      }
+      if (!(f&f_brace)) ungetc(ch, tv->fp);
+      else if (ch != '}') return (tvec_syntax(tv, ch, "`}'"));
+      *ch_out = esc;
+      break;
+
+    default:
+      if ('0' <= ch && ch < '8') {
+       i = 1; esc = ch - '0';
+       for (;;) {
+         ch = getc(tv->fp);
+         if ('0' > ch || ch >= '8') { ungetc(ch, tv->fp); break; }
+         esc = 8*esc + ch - '0';
+         i++; if (i >= 3) break;
+       }
+       if (esc > UCHAR_MAX)
+         return (tvec_error(tv,
+                            "character code %d out of range", esc));
+       *ch_out = esc;
+      } else
+       return (tvec_syntax(tv, ch, "string escape"));
+  }
+
+  return (0);
+
+#undef f_brace
+}
+
+static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
+{
+  int ch;
 
   for (;;) {
     ch = getc(tv->fp);
-  reinsert:
     switch (ch) {
       case EOF: case '\n':
-       return (tvec_syntax(tv, ch, expect));
-
+       return (tvec_syntax(tv, ch, "`%c'", quote));
       case '\\':
        if (quote == '\'') goto ordinary;
-       ch = getc(tv->fp);
-       switch (ch) {
-         case EOF: tvec_syntax(tv, ch, expect);
-         case '\n': tv->lno++; break;
-         case '\'': DPUTC(d, '\''); break;
-         case '\\': DPUTC(d, '\\'); break;
-         case '"': DPUTC(d, '"'); break;
-         case 'a': DPUTC(d, '\a'); break;
-         case 'b': DPUTC(d, '\b'); break;
-         case 'e': DPUTC(d, '\x1b'); break;
-         case 'f': DPUTC(d, '\f'); break;
-         case 'n': DPUTC(d, '\n'); break;
-         case 'r': DPUTC(d, '\r'); break;
-         case 't': DPUTC(d, '\t'); break;
-         case 'v': DPUTC(d, '\v'); break;
-
-         case 'x':
-           ch = getc(tv->fp);
-           if (ch == '{') { f |= f_brace; ch = getc(tv->fp); }
-           else f &= ~f_brace;
-           if (convert_hex(ch, &esc))
-             return (tvec_syntax(tv, ch, "hex digit"));
-           for (;;) {
-             ch = getc(tv->fp); if (convert_hex(ch, &i)) break;
-             esc = 8*esc + i;
-             if (esc > UCHAR_MAX)
-               return (tvec_error(tv,
-                                  "character code %d out of range", esc));
-           }
-           DPUTC(d, esc);
-           if (!(f&f_brace)) goto reinsert;
-           else if (ch != '}') return (tvec_syntax(tv, ch, "`}'"));
-           break;
-
-         default:
-           if ('0' <= ch && ch < '8') {
-             i = 1; esc = ch - '0';
-             for (;;) {
-               ch = getc(tv->fp);
-               if (i > 3 || '0' > ch || ch >= '8') break;
-               esc = 8*esc + ch - '0'; i++;
-             }
-             if (esc > UCHAR_MAX)
-               return (tvec_error(tv,
-                                  "character code %d out of range", esc));
-             DPUTC(d, esc);
-             goto reinsert;
-           }
-           return (tvec_syntax(tv, ch, "string escape"));
-       }
-       break;
-
+       ch = getc(tv->fp); if (ch == '\n') { tv->lno++; break; }
+       ungetc(ch, tv->fp); if (read_escape(&ch, tv)) return (-1);
+       goto ordinary;
       default:
        if (ch == quote) goto end;
       ordinary:
@@ -242,8 +461,43 @@ static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
 end:
   DPUTZ(d);
   return (0);
+}
 
-#undef f_brace
+#define FCF_BRACE 1u
+static void format_charesc(int ch, unsigned f,
+                          const struct gprintf_ops *gops, void *go)
+{
+  switch (ch) {
+    case '\a': gprintf(gops, go, "\\a"); break;
+    case '\b': gprintf(gops, go, "\\b"); break;
+    case '\x1b': gprintf(gops, go, "\\e"); break;
+    case '\f': gprintf(gops, go, "\\f"); break;
+    case '\r': gprintf(gops, go, "\\r"); break;
+    case '\n': gprintf(gops, go, "\\n"); break;
+    case '\t': gprintf(gops, go, "\\t"); break;
+    case '\v': gprintf(gops, go, "\\v"); break;
+    case '\'': gprintf(gops, go, "\\'"); break;
+    case '"': gprintf(gops, go, "\\\""); break;
+    default:
+      if (f&FCF_BRACE)
+       gprintf(gops, go, "\\x{%0*x}", hex_width(UCHAR_MAX), ch);
+      else
+       gprintf(gops, go, "\\x%0*x", hex_width(UCHAR_MAX), ch);
+      break;
+  }
+}
+
+static void format_char(int ch, const struct gprintf_ops *gops, void *go)
+{
+  if (ch == EOF)
+    gprintf(gops, go, "#eof");
+  else if (isprint(ch) && ch != '\'')
+    gprintf(gops, go, "'%c'", ch);
+  else {
+    gprintf(gops, go, "'");
+    format_charesc(ch, 0, gops, go);
+    gprintf(gops, go, "'");
+  }
 }
 
 enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 };
@@ -378,20 +632,18 @@ 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 int parse_...(union tvec_regval *rv, const struct tvec_regdef *rd,
+                    struct tvec_state *tv)
 static void dump_...(const union tvec_regval *rv,
                     const struct tvec_regdef *rd,
                     struct tvec_state *tv, unsigned style)
 
 const struct tvec_regty tvty_... = {
-  init_..., release_..., eq_..., measure_...,
+  init_..., release_..., eq_...,
   tobuf_..., frombuf_...,
   parse_..., dump_...
 };
@@ -416,10 +668,6 @@ static int eq_uint(const union tvec_regval *rv0,
                   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)); }
@@ -442,9 +690,12 @@ static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "signed integer")) { rc = -1; goto end; }
-  if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
-  if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, ";", "signed integer"))
+    { rc = -1; goto end; }
+  if (parse_signed(&rv->i, d.buf, rd->arg.p, tv))
+    { rc = -1; goto end; }
+  if (tvec_flushtoeol(tv, 0))
+    { rc = -1; goto end; }
   rc = 0;
 end:
   dstr_destroy(&d);
@@ -457,9 +708,12 @@ static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "unsigned integer")) { rc = -1; goto end; }
-  if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
-  if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, ";", "unsigned integer"))
+    { rc = -1; goto end; }
+  if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv))
+    { rc = -1; goto end; }
+  if (tvec_flushtoeol(tv, 0))
+    { rc = -1; goto end; }
   rc = 0;
 end:
   dstr_destroy(&d);
@@ -468,29 +722,37 @@ end:
 
 static void dump_int(const union tvec_regval *rv,
                     const struct tvec_regdef *rd,
-                    struct tvec_state *tv, unsigned style)
+                    unsigned style,
+                    const struct gprintf_ops *gops, void *go)
 {
   unsigned long u;
 
-  tvec_write(tv, "%ld", rv->i);
+  gprintf(gops, go, "%ld", rv->i);
   if (!(style&TVSF_COMPACT)) {
     if (rv->i >= 0) u = rv->i;
     else u = -(unsigned long)rv->i;
-    tvec_write(tv, " ; = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u);
+    gprintf(gops, go, " ; = %s0x%0*lx",
+           rv->i < 0 ? "-" : "", hex_width(u), u);
+    if (rv->i == EOF || (0 <= rv->i && rv->i < UCHAR_MAX))
+      { gprintf(gops, go, " = "); format_char(rv->i, gops, go); }
   }
 }
 
 static void dump_uint(const union tvec_regval *rv,
                      const struct tvec_regdef *rd,
-                     struct tvec_state *tv, unsigned style)
+                     unsigned style,
+                     const struct gprintf_ops *gops, void *go)
 {
-  tvec_write(tv, "%lu", rv->u);
-  if (!(style&TVSF_COMPACT))
-    tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
+  gprintf(gops, go, "%lu", rv->u);
+  if (!(style&TVSF_COMPACT)) {
+    gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
+    if (rv->u < UCHAR_MAX)
+      { gprintf(gops, go, " = "); format_char(rv->u, gops, go); }
+  }
 }
 
 const struct tvec_regty tvty_int = {
-  init_int, release_int, eq_int, measure_int,
+  init_int, release_int, eq_int,
   tobuf_int, frombuf_int,
   parse_int, dump_int
 };
@@ -505,7 +767,7 @@ const struct tvec_irange
   tvrange_i32 = { -2147483648, 2147483647 };
 
 const struct tvec_regty tvty_uint = {
-  init_uint, release_int, eq_uint, measure_int,
+  init_uint, release_int, eq_uint,
   tobuf_uint, frombuf_uint,
   parse_uint, dump_uint
 };
@@ -535,6 +797,77 @@ int tvec_claimeq_uint(struct tvec_state *tv,
   return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr));
 }
 
+/*----- Main code ---------------------------------------------------------*/
+
+static void init_float(union tvec_regval *rv, const struct tvec_regdef *rd)
+  { rv->f = 0.0; }
+static void release_float(union tvec_regval *rv,
+                         const struct tvec_regdef *rd)
+  { ; }
+
+static int eq_float(const union tvec_regval *rv0,
+                   const union tvec_regval *rv1,
+                   const struct tvec_regdef *rd)
+  { return (eqish_floating_p(rv0->f, rv1->f, rd->arg.p)); }
+
+static int tobuf_float(buf *b, const union tvec_regval *rv,
+                    const struct tvec_regdef *rd)
+  { return (buf_putf64l(b, rv->f)); }
+static int frombuf_float(buf *b, union tvec_regval *rv,
+                      const struct tvec_regdef *rd)
+  { return (buf_getf64l(b, &rv->f)); }
+
+static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
+                      struct tvec_state *tv)
+{
+  dstr d = DSTR_INIT;
+  int rc;
+
+  if (tvec_readword(tv, &d, ";", "floating-point number"))
+    { rc = -1; goto end; }
+  if (parse_floating(&rv->f, d.buf, rd->arg.p, tv))
+    { rc = -1; goto end; }
+  if (tvec_flushtoeol(tv, 0))
+    { rc = -1; goto end; }
+  rc = 0;
+end:
+  dstr_destroy(&d);
+  return (rc);
+}
+
+static void dump_float(const union tvec_regval *rv,
+                      const struct tvec_regdef *rd,
+                      unsigned style,
+                      const struct gprintf_ops *gops, void *go)
+  { format_floating(gops, go, rv->f); }
+
+const struct tvec_regty tvty_float = {
+  init_float, release_float, eq_float,
+  tobuf_float, frombuf_float,
+  parse_float, dump_float
+};
+
+int tvec_claimeqish_float(struct tvec_state *tv,
+                         double f0, double f1, unsigned f, double delta,
+                         const char *file, unsigned lno,
+                         const char *expr)
+{
+  struct tvec_floatinfo fi;
+  union tvec_misc arg;
+
+  fi.f = f; fi.min = fi.max = 0.0; fi.delta = delta; arg.p = &fi;
+  tv->in[0].v.f = f0; tv->in[1].v.f = f1;
+  return (tvec_claimeq(tv, &tvty_float, &arg, file, lno, expr));
+}
+int tvec_claimeq_float(struct tvec_state *tv,
+                      double f0, double f1,
+                      const char *file, unsigned lno,
+                      const char *expr)
+{
+  return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0,
+                               file, lno, expr));
+}
+
 /*----- Enumerations ------------------------------------------------------*/
 
 static void init_enum(union tvec_regval *rv, const struct tvec_regdef *rd)
@@ -555,12 +888,29 @@ static int eq_enum(const union tvec_regval *rv0,
                   const struct tvec_regdef *rd)
 {
   const struct tvec_enuminfo *ei = rd->arg.p;
+  const struct tvec_fenuminfo *fei;
 
   switch (ei->mv) {
 #define CASE(tag, ty, slot)                                            \
-       case TVMISC_##tag: return (rv0->slot == rv1->slot);
+       case TVMISC_##tag: PREP_##tag return (HANDLE_##tag);
+#define PREP_INT
+#define HANDLE_INT     rv0->i == rv1->i
+#define PREP_UINT
+#define HANDLE_UINT    rv0->u == rv1->u
+#define PREP_FLT       fei = (const struct tvec_fenuminfo *)ei;
+#define HANDLE_FLT     eqish_floating_p(rv0->f, rv1->f, fei->fi)
+#define PREP_PTR
+#define HANDLE_PTR     rv0->p == rv1->p
     TVEC_MISCSLOTS(CASE)
 #undef CASE
+#undef PREP_INT
+#undef HANDLE_INT
+#undef PREP_UINT
+#undef HANDLE_UINT
+#undef PREP_FLT
+#undef HANDLE_FLT
+#undef PREP_PTR
+#undef HANDLE_PTR
     default: abort();
   }
 }
@@ -569,17 +919,29 @@ static int tobuf_enum(buf *b, const union tvec_regval *rv,
                      const struct tvec_regdef *rd)
 {
   const struct tvec_enuminfo *ei = rd->arg.p;
+  const struct tvec_penuminfo *pei;
+  const struct tvec_passoc *pa;
+  long i;
 
   switch (ei->mv) {
 #define CASE(tag, ty, slot)                                            \
-       case TVMISC_##tag: return (HANDLE_##tag);
-#define HANDLE_INT     signed_to_buf(b, rv->i)
-#define HANDLE_UINT    unsigned_to_buf(b, rv->u)
-#define HANDLE_PTR     -1
+       case TVMISC_##tag: HANDLE_##tag
+#define HANDLE_INT     return (signed_to_buf(b, rv->i));
+#define HANDLE_UINT    return (unsigned_to_buf(b, rv->u));
+#define HANDLE_FLT     return (buf_putf64l(b, rv->f));
+#define HANDLE_PTR                                                     \
+       pei = (const struct tvec_penuminfo *)ei;                        \
+       for (pa = pei->av, i = 0; pa->tag; pa++, i++)                   \
+         if (pa->p == rv->p) goto found;                               \
+       if (!rv->p) i = -1;                                             \
+       else return (-1);                                               \
+      found:                                                           \
+       return (signed_to_buf(b, i));
     TVEC_MISCSLOTS(CASE)
 #undef CASE
 #undef HANDLE_INT
 #undef HANDLE_UINT
+#undef HANDLE_FLT
 #undef HANDLE_PTR
     default: abort();
   }
@@ -590,17 +952,29 @@ static int frombuf_enum(buf *b, union tvec_regval *rv,
                        const struct tvec_regdef *rd)
 {
   const struct tvec_enuminfo *ei = rd->arg.p;
+  const struct tvec_penuminfo *pei;
+  const struct tvec_passoc *pa;
+  long i, n;
 
   switch (ei->mv) {
 #define CASE(tag, ty, slot)                                            \
-       case TVMISC_##tag: return (HANDLE_##tag);
-#define HANDLE_INT     signed_from_buf(b, &rv->i)
-#define HANDLE_UINT    unsigned_from_buf(b, &rv->u)
-#define HANDLE_PTR     -1
+       case TVMISC_##tag: HANDLE_##tag
+#define HANDLE_INT     return (signed_from_buf(b, &rv->i));
+#define HANDLE_UINT    return (unsigned_from_buf(b, &rv->u));
+#define HANDLE_FLT     return (buf_getf64l(b, &rv->f));
+#define HANDLE_PTR                                                     \
+       pei = (const struct tvec_penuminfo *)ei;                        \
+       for (pa = pei->av, n = 0; pa->tag; pa++, n++);                  \
+       if (signed_from_buf(b, &i)) return (-1);                        \
+       if (0 <= i && i < n) rv->p = (/*unconst*/ void *)pei->av[i].p;  \
+       else if (i == -1) rv->p = 0;                                    \
+       else return (-1);                                               \
+       return (0);
     TVEC_MISCSLOTS(CASE)
 #undef CASE
 #undef HANDLE_INT
 #undef HANDLE_UINT
+#undef HANDLE_FLT
 #undef HANDLE_PTR
     default: abort();
   }
@@ -611,6 +985,7 @@ static int parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd,
 {
   const struct tvec_enuminfo *ei = rd->arg.p;
 #define DECLS(tag, ty, slot)                                           \
+       const struct tvec_##slot##enuminfo *slot##ei;                   \
        const struct tvec_##slot##assoc *slot##a;
   TVEC_MISCSLOTS(DECLS)
 #undef DECLS
@@ -620,39 +995,48 @@ static int parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd,
   if (tvec_readword(tv, &d, ";", "enumeration tag or literal integer"))
     { rc = -1; goto end; }
   switch (ei->mv) {
+
 #define CASE(tag_, ty, slot)                                           \
        case TVMISC_##tag_:                                             \
-         for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++)        \
+         slot##ei = (const struct tvec_##slot##enuminfo *)ei;          \
+         for (slot##a = slot##ei->av; slot##a->tag; slot##a++)         \
            if (STRCMP(d.buf, ==, slot##a->tag))                        \
-             { rv->slot = FETCH_##tag_; goto done; }
-#define FETCH_INT (ia->i)
-#define FETCH_UINT (ua->u)
-#define FETCH_PTR ((/*unconst*/ void *)(pa->p))
-    TVEC_MISCSLOTS(CASE)
-#undef CASE
-#undef FETCH_INT
-#undef FETCH_UINT
-#undef FETCH_PTR
-  }
+             { rv->slot = FETCH_##tag_; goto done; }                   \
+         HANDLE_##tag_ goto done;
 
-  switch (ei->mv) {
-#define CASE(tag, ty, slot)                                            \
-       case TVMISC_##tag: HANDLE_##tag goto done;
+#define FETCH_INT (ia->i)
 #define HANDLE_INT                                                     \
-       if (parse_signed(&rv->i, d.buf, ei->u.i.ir, tv))                \
+       if (parse_signed(&rv->i, d.buf, iei->ir, tv))                   \
          { rc = -1; goto end; }
+
+#define FETCH_UINT (ua->u)
 #define HANDLE_UINT                                                    \
-       if (parse_unsigned(&rv->u, d.buf, ei->u.u.ur, tv))              \
+       if (parse_unsigned(&rv->u, d.buf, uei->ur, tv))                 \
          { rc = -1; goto end; }
+
+#define FETCH_FLT (fa->f)
+#define HANDLE_FLT                                                     \
+       if (parse_floating(&rv->f, d.buf, fei->fi, tv))                 \
+         { rc = -1; goto end; }
+
+#define FETCH_PTR ((/*unconst*/ void *)(pa->p))
 #define HANDLE_PTR                                                     \
        if (STRCMP(d.buf, ==, "#nil")) rv->p = 0;                       \
        else goto tagonly;
+
     TVEC_MISCSLOTS(CASE)
+
 #undef CASE
+#undef FETCH_INT
 #undef HANDLE_INT
+#undef FETCH_UINT
 #undef HANDLE_UINT
+#undef FETCH_FLT
+#undef HANDLE_FLT
+#undef FETCH_PTR
 #undef HANDLE_PTR
-    default: tagonly:
+
+    tagonly:
       tvec_error(tv, "unknown `%s' value `%s'", ei->name, d.buf);
       rc = -1; goto end;
   }
@@ -667,10 +1051,12 @@ end:
 
 static void dump_enum(const union tvec_regval *rv,
                      const struct tvec_regdef *rd,
-                     struct tvec_state *tv, unsigned style)
+                     unsigned style,
+                     const struct gprintf_ops *gops, void *go)
 {
   const struct tvec_enuminfo *ei = rd->arg.p;
 #define DECLS(tag, ty, slot)                                           \
+       const struct tvec_##slot##enuminfo *slot##ei;                   \
        const struct tvec_##slot##assoc *slot##a;
   TVEC_MISCSLOTS(DECLS)
 #undef DECLS
@@ -682,7 +1068,8 @@ static void dump_enum(const union tvec_regval *rv,
   switch (ei->mv) {
 #define CASE(tag_, ty, slot)                                           \
        case TVMISC_##tag_:                                             \
-         for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++)        \
+         slot##ei = (const struct tvec_##slot##enuminfo *)ei;          \
+         for (slot##a = slot##ei->av; slot##a->tag; slot##a++)         \
            if (rv->slot == slot##a->slot)                              \
              { tag = slot##a->tag; goto found; }                       \
          break;
@@ -690,59 +1077,86 @@ static void dump_enum(const union tvec_regval *rv,
 #undef CASE
     default: abort();
   }
-  goto print_int;
+  goto print_raw;
 
 found:
   f |= f_known;
-  tvec_write(tv, "%s", tag);
+  gprintf(gops, go, "%s", tag);
   if (style&TVSF_COMPACT) return;
-  tvec_write(tv, " ; = ");
+  gprintf(gops, go, " ; = ");
 
-print_int:
+print_raw:
   switch (ei->mv) {
 #define CASE(tag, ty, slot)                                            \
        case TVMISC_##tag: HANDLE_##tag break;
-#define HANDLE_INT     tvec_write(tv, "%ld", rv->i);
-#define HANDLE_UINT    tvec_write(tv, "%lu", rv->u);
-#define HANDLE_PTR     if (!rv->p) tvec_write(tv, "#nil");             \
-                       else tvec_write(tv, "#<%s %p>", ei->name, rv->p);
+#define HANDLE_INT     gprintf(gops, go, "%ld", rv->i);
+#define HANDLE_UINT    gprintf(gops, go, "%lu", rv->u);
+#define HANDLE_FLT     format_floating(gops, go, rv->f);
+#define HANDLE_PTR     if (!rv->p) gprintf(gops, go, "#nil");          \
+                       else gprintf(gops, go, "#<%s %p>", ei->name, rv->p);
     TVEC_MISCSLOTS(CASE)
 #undef CASE
 #undef HANDLE_INT
 #undef HANDLE_UINT
+#undef HANDLE_FLT
 #undef HANDLE_PTR
   }
 
+  if (style&TVSF_COMPACT) return;
   switch (ei->mv) {
     case TVMISC_INT:
-      if (!(f&f_known)) tvec_write(tv, " ;");
+      if (!(f&f_known)) gprintf(gops, go, " ;");
       if (rv->i >= 0) u = rv->i;
       else u = -(unsigned long)rv->i;
-      tvec_write(tv, " = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u);
+      gprintf(gops, go, " = %s0x%0*lx",
+             rv->i < 0 ? "-" : "", hex_width(u), u);
+      if (rv->i == EOF || (0 <= rv->i && rv->i < UCHAR_MAX))
+       { gprintf(gops, go, " = "); format_char(rv->i, gops, go); }
       break;
     case TVMISC_UINT:
-      if (!(f&f_known)) tvec_write(tv, " ;");
-      tvec_write(tv, " = 0x%0*lx", hex_width(rv->u), rv->u);
+      if (!(f&f_known)) gprintf(gops, go, " ;");
+      gprintf(gops, go, " = 0x%0*lx", hex_width(rv->u), rv->u);
+      if (rv->u < UCHAR_MAX)
+       { gprintf(gops, go, " = "); format_char(rv->u, gops, go); }
       break;
   }
 }
 
 const struct tvec_regty tvty_enum = {
-  init_enum, release_int, eq_enum, measure_int,
+  init_enum, release_int, eq_enum,
   tobuf_enum, frombuf_enum,
   parse_enum, dump_enum
 };
 
+static const struct tvec_iassoc bool_assoc[] = {
+  { "nil",             0 },
+  { "false",           0 },
+  { "f",               0 },
+  { "no",              0 },
+  { "n",               0 },
+  { "off",             0 },
+
+  { "t",               1 },
+  { "true",            1 },
+  { "yes",             1 },
+  { "y",               1 },
+  { "on",              1 },
+
+  { 0,                 0 }
+};
+
+const struct tvec_ienuminfo tvenum_bool =
+  { { "bool", TVMISC_INT }, bool_assoc, &tvrange_int };
+
 #define DEFCLAIM(tag, ty, slot)                                                \
-       int tvec_claimeq_##slot##enum(struct tvec_state *tv,            \
-                                     const struct tvec_enuminfo *ei,   \
-                                     ty e0, ty e1,                     \
-                                     const char *file, unsigned lno,   \
-                                     const char *expr)                 \
+       int tvec_claimeq_##slot##enum                                   \
+         (struct tvec_state *tv,                                       \
+          const struct tvec_##slot##enuminfo *ei, ty e0, ty e1,        \
+          const char *file, unsigned lno, const char *expr)            \
        {                                                               \
          union tvec_misc arg;                                          \
                                                                        \
-         assert(ei->mv == TVMISC_##tag);                               \
+         assert(ei->_ei.mv == TVMISC_##tag);                           \
          arg.p = ei;                                                   \
          tv->in[0].v.slot = GET_##tag(e0);                             \
          tv->out[0].v.slot = GET_##tag(e1);                            \
@@ -750,11 +1164,13 @@ const struct tvec_regty tvty_enum = {
        }
 #define GET_INT(e) (e)
 #define GET_UINT(e) (e)
+#define GET_FLT(e) (e)
 #define GET_PTR(e) ((/*unconst*/ void *)(e))
 TVEC_MISCSLOTS(DEFCLAIM)
 #undef DEFCLAIM
 #undef GET_INT
 #undef GET_UINT
+#undef GET_FLT
 #undef GET_PTR
 
 /*----- Flag types --------------------------------------------------------*/
@@ -781,7 +1197,8 @@ static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
          { m |= f->m; v |= f->v; goto next; }
       }
 
-    if (parse_unsigned(&t, d.buf, fi->range, tv)) { rc = -1; goto end; }
+    if (parse_unsigned(&t, d.buf, fi->range, tv))
+      { rc = -1; goto end; }
     v |= t;
   next:
     if (tvec_nexttoken(tv)) break;
@@ -799,7 +1216,8 @@ end:
 
 static void dump_flags(const union tvec_regval *rv,
                       const struct tvec_regdef *rd,
-                      struct tvec_state *tv, unsigned style)
+                      unsigned style,
+                      const struct gprintf_ops *gops, void *go)
 {
   const struct tvec_flaginfo *fi = rd->arg.p;
   const struct tvec_flag *f;
@@ -808,18 +1226,18 @@ static void dump_flags(const union tvec_regval *rv,
 
   for (f = fi->fv, sep = ""; f->tag; f++)
     if ((m&f->m) && (v&f->m) == f->v) {
-      tvec_write(tv, "%s%s", sep, f->tag); m &= ~f->m;
+      gprintf(gops, go, "%s%s", sep, f->tag); m &= ~f->m;
       sep = style&TVSF_COMPACT ? "|" : " | ";
     }
 
-  if (v&m) tvec_write(tv, "%s0x%0*lx", sep, hex_width(v), v&m);
+  if (v&m) gprintf(gops, go, "%s0x%0*lx", sep, hex_width(v), v&m);
 
   if (!(style&TVSF_COMPACT))
-    tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
+    gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
 }
 
 const struct tvec_regty tvty_flags = {
-  init_uint, release_int, eq_uint, measure_int,
+  init_uint, release_int, eq_uint,
   tobuf_uint, frombuf_uint,
   parse_flags, dump_flags
 };
@@ -835,6 +1253,107 @@ int tvec_claimeq_flags(struct tvec_state *tv,
   return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr));
 }
 
+/*----- Characters --------------------------------------------------------*/
+
+static int tobuf_char(buf *b, const union tvec_regval *rv,
+                    const struct tvec_regdef *rd)
+{
+  uint32 u;
+  if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i;
+  else if (rv->i == EOF) u = MASK32;
+  else return (-1);
+  return (buf_putu32l(b, u));
+}
+
+static int frombuf_char(buf *b, union tvec_regval *rv,
+                      const struct tvec_regdef *rd)
+{
+  uint32 u;
+
+  if (buf_getu32l(b, &u)) return (-1);
+  if (0 <= u && u <= UCHAR_MAX) rv->i = u;
+  else if (u == MASK32) rv->i = EOF;
+  else return (-1);
+  return (0);
+}
+
+static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd,
+                     struct tvec_state *tv)
+{
+  dstr d = DSTR_INIT;
+  int ch, rc;
+  unsigned f = 0;
+#define f_quote 1u
+
+  ch = getc(tv->fp);
+  if (ch == '#') {
+    ungetc(ch, tv->fp);
+    if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; }
+    if (STRCMP(d.buf, ==, "#eof"))
+      rv->i = EOF;
+    else {
+      rc = tvec_error(tv, "unknown character name `%s'", d.buf);
+      goto end;
+    }
+    rc = 0; goto end;
+  }
+
+  if (ch == '\'') { f |= f_quote; ch = getc(tv->fp); }
+  switch (ch) {
+    case '\'':
+      if (!(f&f_quote)) goto plain;
+    case EOF: case '\n':
+      rc = tvec_syntax(tv, ch, "character"); goto end;
+    case '\\':
+      if (read_escape(&ch, tv)) return (-1);
+    default: plain:
+      rv->i = ch; break;
+  }
+  if (f&f_quote) {
+    ch = getc(tv->fp);
+    if (ch != '\'') { rc = tvec_syntax(tv, ch, "`''"); goto end; }
+  }
+  if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+  rc = 0;
+end:
+  dstr_destroy(&d);
+  return (rc);
+
+#undef f_quote
+}
+
+static void dump_char(const union tvec_regval *rv,
+                     const struct tvec_regdef *rd,
+                     unsigned style,
+                     const struct gprintf_ops *gops, void *go)
+{
+  unsigned u;
+
+  if ((style&TVSF_COMPACT) && isprint(rv->i) && rv->i != '\'')
+    gprintf(gops, go, "%c", (int)rv->i);
+  else
+    format_char(rv->i, gops, go);
+
+  if (!(style&TVSF_COMPACT)) {
+    u = rv->i < 0 ? -rv->i : rv->i;
+    gprintf(gops, go, " ; = %d = %s0x%0*x",
+           (int)rv->i, rv->i < 0 ? "-" : "", hex_width(u), u);
+  }
+}
+
+const struct tvec_regty tvty_char = {
+  init_int, release_int, eq_int,
+  tobuf_char, frombuf_char,
+  parse_char, dump_char
+};
+
+int tvec_claimeq_char(struct tvec_state *tv, int c0, int c1,
+                     const char *file, unsigned lno, const char *expr)
+{
+  tv->in[0].v.i = c0; tv->out[0].v.i = c1;
+  return (tvec_claimeq(tv, &tvty_char, 0, file, lno, expr));
+}
+
 /*----- Text and byte strings ---------------------------------------------*/
 
 void tvec_allocstring(union tvec_regval *rv, size_t sz)
@@ -881,14 +1400,6 @@ static int eq_bytes(const union tvec_regval *rv0,
           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)); }
@@ -953,59 +1464,35 @@ static int parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd,
 
 static void dump_string(const union tvec_regval *rv,
                        const struct tvec_regdef *rd,
-                       struct tvec_state *tv, unsigned style)
+                       unsigned style,
+                       const struct gprintf_ops *gops, void *go)
 {
   const unsigned char *p, *q, *l;
-  int ch;
   unsigned f = 0;
 #define f_nonword 1u
 #define f_newline 2u
 
-  if (!rv->str.sz) { tvec_write(tv, "\"\""); return; }
+  if (!rv->str.sz) { gprintf(gops, go, "\"\""); return; }
 
   p = (const unsigned char *)rv->str.p; l = p + rv->str.sz;
   if (*p == '!' || *p == ';' || *p == '"' || *p == '\'') goto quote;
   for (q = p; q < l; q++)
     if (*q == '\n' && q != l - 1) f |= f_newline;
     else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword;
-  if (f&f_newline) { tvec_write(tv, "\n\t"); goto quote; }
+  if (f&f_newline) { gprintf(gops, go, "\n\t"); goto quote; }
   else if (f&f_nonword) goto quote;
-  tv->output->ops->write(tv->output, (const char *)p, rv->str.sz); return;
+  gops->putm(go, (const char *)p, rv->str.sz); return;
 
 quote:
-  tvec_write(tv, "\"");
+  gprintf(gops, go, "\"");
   for (q = p; q < l; q++)
-    switch (*q) {
-      case '"': case '\\': ch = *q; goto escape;
-      case '\a': ch = 'a'; goto escape;
-      case '\b': ch = 'b'; goto escape;
-      case '\x1b': ch = 'e'; goto escape;
-      case '\f': ch = 'f'; goto escape;
-      case '\r': ch = 'r'; goto escape;
-      case '\t': ch = 't'; goto escape;
-      case '\v': ch = 'v'; goto escape;
-      escape:
-       if (p < q)
-         tv->output->ops->write(tv->output, (const char *)p, q - p);
-       tvec_write(tv, "\\%c", ch); p = q + 1;
-       break;
-
-      case '\n':
-       if (p < q)
-         tv->output->ops->write(tv->output, (const char *)p, q - p);
-       tvec_write(tv, "\\n"); p = q + 1;
-       if (!(style&TVSF_COMPACT) && q < l) tvec_write(tv, "\"\t\"");
-       break;
-
-      default:
-       if (isprint(*q)) break;
-       if (p < q)
-         tv->output->ops->write(tv->output, (const char *)p, q - p);
-       tvec_write(tv, "\\x{%0*x}", hex_width(UCHAR_MAX), *q); p = q + 1;
-       break;
+    if (!isprint(*q) || *q == '"') {
+      if (p < q) gops->putm(go, (const char *)p, q - p);
+      if (*q == '\n' && !(style&TVSF_COMPACT))gprintf(gops, go, "\\n\"\t\"");
+      else format_charesc(*q, FCF_BRACE, gops, go);
     }
-  if (p < q) tv->output->ops->write(tv->output, (const char *)p, q - p);
-  tvec_write(tv, "\"");
+  if (p < q) gops->putm(go, (const char *)p, q - p);
+  gprintf(gops, go, "\"");
 
 #undef f_nonword
 #undef f_newline
@@ -1013,7 +1500,8 @@ quote:
 
 static void dump_bytes(const union tvec_regval *rv,
                       const struct tvec_regdef *rd,
-                      struct tvec_state *tv, unsigned style)
+                      unsigned style,
+                      const struct gprintf_ops *gops, void *go)
 {
   const unsigned char *p = rv->bytes.p, *l = p + rv->bytes.sz;
   size_t off, sz = rv->bytes.sz;
@@ -1021,16 +1509,16 @@ static void dump_bytes(const union tvec_regval *rv,
   int wd;
 
   if (!sz) {
-    tvec_write(tv, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
+    gprintf(gops, go, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
     return;
   }
 
   if (style&TVSF_COMPACT) {
-    while (p < l) tvec_write(tv, "%02x", *p++);
+    while (p < l) gprintf(gops, go, "%02x", *p++);
     return;
   }
 
-  if (sz > 16) tvec_write(tv, "\n\t");
+  if (sz > 16) gprintf(gops, go, "\n\t");
 
   off = 0; wd = hex_width(sz);
   while (p < l) {
@@ -1038,27 +1526,27 @@ static void dump_bytes(const union tvec_regval *rv,
     else n = 16;
 
     for (i = 0; i < 16; i++) {
-      if (i < n) tvec_write(tv, "%02x", p[i]);
-      else tvec_write(tv, "  ");
-      if (i%4 == 3) tvec_write(tv, " ");
+      if (i < n) gprintf(gops, go, "%02x", p[i]);
+      else gprintf(gops, go, "  ");
+      if (i%4 == 3) gprintf(gops, go, " ");
     }
-    tvec_write(tv, " ; ");
-    if (sz > 16) tvec_write(tv, "[%0*lx] ", wd, (unsigned long)off);
+    gprintf(gops, go, " ; ");
+    if (sz > 16) gprintf(gops, go, "[%0*lx] ", wd, (unsigned long)off);
     for (i = 0; i < n; i++)
-      tvec_write(tv, "%c", isprint(p[i]) ? p[i] : '.');
+      gprintf(gops, go, "%c", isprint(p[i]) ? p[i] : '.');
     p += n; off += n;
-    if (p < l) tvec_write(tv, "\n\t");
+    if (p < l) gprintf(gops, go, "\n\t");
   }
 }
 
 const struct tvec_regty tvty_string = {
-  init_string, release_string, eq_string, measure_string,
+  init_string, release_string, eq_string,
   tobuf_string, frombuf_string,
   parse_string, dump_string
 };
 
 const struct tvec_regty tvty_bytes = {
-  init_bytes, release_bytes, eq_bytes, measure_bytes,
+  init_bytes, release_bytes, eq_bytes,
   tobuf_bytes, frombuf_bytes,
   parse_bytes, dump_bytes
 };
@@ -1175,21 +1663,22 @@ rangerr:
 
 static void dump_buffer(const union tvec_regval *rv,
                        const struct tvec_regdef *rd,
-                       struct tvec_state *tv, unsigned style)
+                       unsigned style,
+                       const struct gprintf_ops *gops, void *go)
 {
   const char *unit;
   unsigned long u = rv->bytes.sz;
 
   if (!u || u%1024)
-    tvec_write(tv, "%lu B", u);
+    gprintf(gops, go, "%lu B", u);
   else {
     for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++);
-    tvec_write(tv, "%lu %cB", u, *unit);
+    gprintf(gops, go, "%lu %cB", u, *unit);
   }
 }
 
 const struct tvec_regty tvty_buffer = {
-  init_bytes, release_bytes, eq_buffer, measure_int,
+  init_bytes, release_bytes, eq_buffer,
   tobuf_buffer, frombuf_buffer,
   parse_buffer, dump_buffer
 };