chiark / gitweb /
@@@ timeout wip
[mLib] / test / tvec-types.c
1 /* -*-c-*-
2  *
3  * Types for the test-vector framework
4  *
5  * (c) 2023 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the mLib utilities library.
11  *
12  * mLib is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU Library General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or (at
15  * your option) any later version.
16  *
17  * mLib is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
20  * License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with mLib.  If not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25  * USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <assert.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <float.h>
34 #include <limits.h>
35 #include <math.h>
36 #include <stdio.h>
37 #include <string.h>
38
39 #include "buf.h"
40 #include "codec.h"
41 #  include "base32.h"
42 #  include "base64.h"
43 #  include "hex.h"
44 #include "dstr.h"
45 #include "maths.h"
46 #include "tvec.h"
47
48 /*----- Preliminary utilities ---------------------------------------------*/
49
50 /* --- @trivial_release@ --- *
51  *
52  * Arguments:   @union tvec_regval *rv@ = a register value
53  *              @const struct tvec_regdef@ = the register definition
54  *
55  * Returns:     ---
56  *
57  * Use:         Does nothing.  Used for register values which don't retain
58  *              resources.
59  */
60
61 static void trivial_release(union tvec_regval *rv,
62                             const struct tvec_regdef *rd)
63   { ; }
64
65 /*----- Integer utilities -------------------------------------------------*/
66
67 /* --- @unsigned_to_buf@, @signed_to_buf@ --- *
68  *
69  * Arguments:   @buf *b@ = buffer to write on
70  *              @unsigned long u@ or @long i@ = integer to write
71  *
72  * Returns:     Zero on success, @-1@ on failure.
73  *
74  * Use:         Write @i@ to the buffer, in big-endian (two's-complement, it
75  *              signed) format.
76  */
77
78 static int unsigned_to_buf(buf *b, unsigned long u)
79   { kludge64 k; ASSIGN64(k, u); return (buf_putk64l(b, k)); }
80
81 static int signed_to_buf(buf *b, long i)
82 {
83   kludge64 k;
84   unsigned long u;
85
86   u = i;
87   if (i >= 0) ASSIGN64(k, u);
88   else { ASSIGN64(k, ~u); CPL64(k, k); }
89   return (buf_putk64l(b, k));
90 }
91
92 /* --- @unsigned_from_buf@, @signed_from_buf@ --- *
93  *
94  * Arguments:   @buf *b@ = buffer to write on
95  *              @unsigned long *u_out@ or @long *i_out@ = where to put the
96  *                      result
97  *
98  * Returns:     Zero on success, @-1@ on failure.
99  *
100  * Use:         Read an integer, in big-endian (two's-complement, if signed)
101  *              format, from the buffer.
102  */
103
104 static int unsigned_from_buf(buf *b, unsigned long *u_out)
105 {
106   kludge64 k, ulmax;
107
108   ASSIGN64(ulmax, ULONG_MAX);
109   if (buf_getk64l(b, &k)) return (-1);
110   if (CMP64(k, >, ulmax)) return (-1);
111   *u_out = GET64(unsigned long, k); return (0);
112 }
113
114 /* --- @hex_width@ --- *
115  *
116  * Arguments:   @unsigned long u@ = an integer
117  *
118  * Returns:     A suitable number of digits to use in order to display @u@ in
119  *              hex.  Currently, we select a power of two sufficient to show
120  *              the value, but at least 2.
121  */
122
123 static int hex_width(unsigned long u)
124 {
125   int wd;
126   unsigned long t;
127
128   for (t = u >> 4, wd = 4; t >>= wd, wd *= 2, t; );
129   return (wd/4);
130 }
131
132 /* --- @format_unsigned_hex@, @format_signed_hex@ --- *
133  *
134  * Arguments:   @const struct gprintf_ops *gops@ = print operations
135  *              @void *go@ = print destination
136  *              @unsigned long u@ or @long i@ = integer to print
137  *
138  * Returns:     ---
139  *
140  * Use:         Print an unsigned or signed integer in hexadecimal.
141  */
142
143 static void format_unsigned_hex(const struct gprintf_ops *gops, void *go,
144                                 unsigned long u)
145   { gprintf(gops, go, "0x%0*lx", hex_width(u), u); }
146
147 static void format_signed_hex(const struct gprintf_ops *gops, void *go,
148                               long i)
149 {
150   unsigned long u = i >= 0 ? i : -(unsigned long)i;
151   gprintf(gops, go, "%s0x%0*lx", i < 0 ? "-" : "", hex_width(u), u);
152 }
153
154 static int signed_from_buf(buf *b, long *i_out)
155 {
156   kludge64 k, lmax, not_lmin;
157
158   ASSIGN64(lmax, LONG_MAX); ASSIGN64(not_lmin, ~(unsigned long)LONG_MIN);
159   if (buf_getk64l(b, &k)) return (-1);
160   if (CMP64(k, <=, lmax)) *i_out = (long)GET64(unsigned long, k);
161   else {
162     CPL64(k, k);
163     if (CMP64(k, <=, not_lmin)) *i_out = -(long)GET64(unsigned long, k) - 1;
164     else return (-1);
165   }
166   return (0);
167 }
168
169 /* --- @check_unsigned_range@, @check_signed_range@ --- *
170  *
171  * Arguments:   @unsigned long u@ or @long i@ = an integer
172  *              @const struct tvec_urange *ur@ or
173  *                      @const struct tvec_irange *ir@ = range specification,
174  *                      or null
175  *              @struct tvec_state *tv@ = test vector state
176  *
177  * Returns:     Zero on success, or @-1@ on error.
178  *
179  * Use:         Check that the integer is within bounds.  If not, report a
180  *              suitable error and return a failure indication.
181  */
182
183 static int check_signed_range(long i,
184                               const struct tvec_irange *ir,
185                               struct tvec_state *tv)
186 {
187   if (ir && (ir->min > i || i > ir->max)) {
188     tvec_error(tv, "integer %ld out of range (must be in [%ld .. %ld])",
189                i, ir->min, ir->max);
190     return (-1);
191   }
192   return (0);
193 }
194
195 static int check_unsigned_range(unsigned long u,
196                                 const struct tvec_urange *ur,
197                                 struct tvec_state *tv)
198 {
199   if (ur && (ur->min > u || u > ur->max)) {
200     tvec_error(tv, "integer %lu out of range (must be in [%lu .. %lu])",
201                u, ur->min, ur->max);
202     return (-1);
203   }
204   return (0);
205 }
206
207 /* --- @chtodig@ --- *
208  *
209  * Arguments:   @int ch@ = a character
210  *
211  * Returns:     The numeric value of the character as a digit, or @-1@ if
212  *              it's not a digit.  Letters count as extended digits starting
213  *              with value 10; case is not significant.
214  */
215
216 static int chtodig(int ch)
217 {
218   if ('0' <= ch && ch <= '9') return (ch - '0');
219   else if ('a' <= ch && ch <= 'z') return (ch - 'a' + 10);
220   else if ('A' <= ch && ch <= 'Z') return (ch - 'A' + 10);
221   else return (-1);
222 }
223
224 /* --- @parse_unsigned_integer@, @parse_signed_integer@ --- *
225  *
226  * Arguments:   @unsigned long *u_out@, @long *i_out@ = where to put the
227  *                      result
228  *              @const char **q_out@ = where to put the end position
229  *              @const char *p@ = pointer to the string to parse
230  *
231  * Returns:     Zero on success, @-1@ on error.
232  *
233  * Use:         Parse an integer from a string in the test-vector format.
234  *              This is mostly extension of the traditional C @strtoul@
235  *              format: supported inputs include:
236  *
237  *                * NNN -- a decimal number (even if it starts with `0');
238  *                * 0xNNN -- hexadecimal;
239  *                * 0oNNN -- octal;
240  *                * 0bNNN -- binary;
241  *                * NNrNNN -- base NN.
242  *
243  *              Furthermore, single underscores are permitted internally as
244  *              an insignificant digit separator.
245  */
246
247 static int parse_unsigned_integer(unsigned long *u_out, const char **q_out,
248                                   const char *p)
249 {
250   unsigned long u;
251   int ch, d, r;
252   const char *q;
253   unsigned f = 0;
254 #define f_implicit 1u                   /* implicitly reading base 10 */
255 #define f_digit 2u                      /* read a real digit */
256 #define f_uscore 4u                     /* found an underscore */
257
258   /* Initial setup
259    *
260    * This will deal with the traditional `0[box]...' prefixes.  We'll leave
261    * our new `NNr...' syntax for later.
262    */
263   if (p[0] != '0' || !p[1]) {
264     d = chtodig(*p); if (0 > d || d >= 10) return (-1);
265     r = 10; u = d; p++; f |= f_implicit | f_digit;
266   } else {
267     u = 0; d = chtodig(p[2]);
268     if (d < 0) { r = 10; f |= f_implicit | f_digit; p++; }
269     else if ((p[1] == 'x' || p[1] == 'X') && d < 16) { r = 16; p += 2; }
270     else if ((p[1] == 'o' || p[1] == 'O') && d < 8) { r = 8; p += 2; }
271     else if ((p[1] == 'b' || p[1] == 'B') && d < 2) { r = 2; p += 2; }
272     else { r = 10; f |= f_digit; p++; }
273   }
274
275   q = p;
276   for (;;) {
277     /* Work through the string a character at a time. */
278
279     ch = *p; switch (ch) {
280
281       case '_':
282         /* An underscore is OK if we haven't just seen one. */
283
284         if (f&f_uscore) goto done;
285         p++; f = (f&~f_implicit) | f_uscore;
286         break;
287
288       case 'r': case 'R':
289         /* An `r' is OK if the number so far is small enough to be a sensible
290          * base, and we're scanning decimal implicitly.
291          */
292
293         if (!(f&f_implicit) || !u || u >= 36) goto done;
294         d = chtodig(p[1]); if (0 > d || d >= u) goto done;
295         r = u; u = d; f = (f&~f_implicit) | f_digit; p += 2; q = p;
296         break;
297
298       default:
299         /* Otherwise we expect a valid digit and accumulate it. */
300         d = chtodig(ch); if (d < 0 || d >= r) goto done;
301         if (u > ULONG_MAX/r) return (-1);
302         u *= r; if (u > ULONG_MAX - d) return (-1);
303         u += d; f = (f&~f_uscore) | f_digit; p++; q = p;
304         break;
305     }
306   }
307
308 done:
309   if (!(f&f_digit)) return (-1);
310   *u_out = u; *q_out = q; return (0);
311
312 #undef f_implicit
313 #undef f_digit
314 #undef f_uscore
315 }
316
317 static int parse_signed_integer(long *i_out, const char **q_out,
318                                 const char *p)
319 {
320   unsigned long u;
321   unsigned f = 0;
322 #define f_neg 1u
323
324   /* Read an initial sign. */
325   if (*p == '+') p++;
326   else if (*p == '-') { f |= f_neg; p++; }
327
328   /* Scan an unsigned number. */
329   if (parse_unsigned_integer(&u, q_out, p)) return (-1);
330
331   /* Check for signed overflow and apply the sign. */
332   if (!(f&f_neg)) {
333     if (u > LONG_MAX) return (-1);
334     *i_out = u;
335   } else {
336     if (u && u - 1 > -(LONG_MIN + 1)) return (-1);
337     *i_out = u ? -(long)(u - 1) - 1 : 0;
338   }
339
340   return (0);
341
342 #undef f_neg
343 }
344
345 /* --- @parse_unsigned@, @parse_signed@ --- *
346  *
347  * Arguments:   @unsigned long *u_out@ or @long *i_out@ = where to put the
348  *                      result
349  *              @const char *p@ = string to parse
350  *              @const struct tvec_urange *ur@ or
351  *                      @const struct tvec_irange *ir@ = range specification,
352  *                      or null
353  *              @struct tvec_state *tv@ = test vector state
354  *
355  * Returns:     Zero on success, @-1@ on error.
356  *
357  * Use:         Parse and range-check an integer.  Unlike @parse_(un)signed_
358  *              integer@, these functions check that there's no cruft
359  *              following the final digit, and report errors as they find
360  *              them rather than leaving that to the caller.
361  */
362
363 static int parse_unsigned(unsigned long *u_out, const char *p,
364                           const struct tvec_urange *ur,
365                           struct tvec_state *tv)
366 {
367   unsigned long u;
368   const char *q;
369
370   if (parse_unsigned_integer(&u, &q, p))
371     return (tvec_error(tv, "invalid unsigned integer `%s'", p));
372   if (*q) return (tvec_syntax(tv, *q, "end-of-line"));
373   if (check_unsigned_range(u, ur, tv)) return (-1);
374   *u_out = u; return (0);
375 }
376
377 static int parse_signed(long *i_out, const char *p,
378                         const struct tvec_irange *ir,
379                         struct tvec_state *tv)
380 {
381   long i;
382   const char *q;
383
384   if (parse_signed_integer(&i, &q, p))
385     return (tvec_error(tv, "invalid signed integer `%s'", p));
386   if (*q) return (tvec_syntax(tv, *q, "end-of-line"));
387   if (check_signed_range(i, ir, tv)) return (-1);
388   *i_out = i; return (0);
389 }
390
391 /*----- Floating-point utilities ------------------------------------------*/
392
393 /* --- @eqish_floating_p@ --- *
394  *
395  * Arguments:   @double x, y@ = two numbers to compare
396  *              @const struct tvec_floatinfo *fi@ = floating-point info
397  *
398  * Returns:     Nonzero if  the comparand @y@ is sufficiently close to the
399  *              reference @x@, or zero if it's definitely different.
400  */
401
402 static int eqish_floating_p(double x, double y,
403                             const struct tvec_floatinfo *fi)
404 {
405   double t;
406
407   if (NANP(x)) return (NANP(y)); else if (NANP(y)) return (0);
408   if (INFP(x)) return (x == y); else if (INFP(y)) return (0);
409
410   switch (fi ? fi->f&TVFF_EQMASK : TVFF_EXACT) {
411     case TVFF_EXACT:
412       return (x == y && NEGP(x) == NEGP(y));
413     case TVFF_ABSDELTA:
414       t = x - y; if (t < 0) t = -t; return (t < fi->delta);
415     case TVFF_RELDELTA:
416       t = 1.0 - y/x; if (t < 0) t = -t; return (t < fi->delta);
417     default:
418       abort();
419   }
420 }
421
422 /* --- @format_floating@ --- *
423  *
424  * Arguments:   @const struct gprintf_ops *gops@ = print operations
425  *              @void *go@ = print destination
426  *              @double x@ = number to print
427  *
428  * Returns:     ---
429  *
430  * Use:         Print a floating-point number, accurately.
431  */
432
433 static void format_floating(const struct gprintf_ops *gops, void *go,
434                             double x)
435 {
436   int prec;
437
438   if (NANP(x))
439     gprintf(gops, go, "#nan");
440   else if (INFP(x))
441     gprintf(gops, go, x > 0 ? "#+inf" : "#-inf");
442   else {
443     /* Ugh.  C doesn't provide any function for just printing a
444      * floating-point number /correctly/, i.e., so that you can read the
445      * result back and recover the number you first thought of.  There are
446      * complicated algorithms published for doing this, but I really don't
447      * want to get into that here.  So we have this.
448      *
449      * The sign doesn't cause significant difficulty so we're going to ignore
450      * it for now.  So suppose we're given a number %$x = f b^e$%, in
451      * base-%$b$% format, so %$f b^n$% and %$e$% are integers, with
452      * %$0 \le f < 1$%.  We're going to convert it into the nearest integer
453      * of the form %$X = F B^E$%, with similar conditions, only with the
454      * additional requirement that %$X$% is normalized, i.e., that %$X = 0$%
455      * or %$F \ge B^{-N}$%.
456      *
457      * We're rounding to the nearest such %$X$%.  If there is to be ambiguity
458      * in the conversion, then some %$x = f b^e$% and the next smallest
459      * representable number %$x' = x + b^{e-n}$% must both map to the same
460      * %$X$%, which means both %$x$% and %$x'$% must be nearer to %$X$% than
461      * any other number representable in the target system.  The nest larger
462      * number is %$X' = X + B^{E-N}$%; the next smaller number will normally
463      * be %$W = X - B^{E-N}$%, but if %$F = 1/B$ then the next smaller number
464      * is actually %$X - B^{E-N-1}$%.  We ignore this latter possibility in
465      * the pursuit of a conservative estimate (though actually it doesn't
466      * matter).
467      *
468      * If both %$x$% and %$x'$% map to %$X$% then we must have
469      * %$L = X - B^{E-N}/2 \le x$% and %$x + b^{e-n} \le R = X + B^{E-N}/2$%;
470      * so firstly %$f b^e = x \ge L = W + B^{E-N}/2 > W = (F - B^{-N}) B^E$%,
471      * and secondly %$b^{e-n} \le B^{E-N}$%.  Since these inequalities are in
472      * opposite senses, we can divide, giving
473      *
474      *         %$f b^e/b^{e-n} > (F - B^{-N}) B^E/B^{E-N}$% ,
475      *
476      * whence
477      *
478      *         %$f b^n > (F - B^{-N}) B^N = F B^N - 1$% .
479      *
480      * Now %$f \le 1 - b^{-n}$%, and %$F \ge B^{-1}$%, so, for this to be
481      * possible, it must be the case that
482      *
483      *         %$(1 - b^{-n}) b^n = b^n - 1 > B^{N-1} - 1$% .
484      *
485      * Then rearrange and take logarithms, obtaining
486      *
487      *         %$(N - 1) \log B < n \log b$% ,
488      *
489      * and so
490      *
491      *         %$N < n \log b/\log B + 1$% .
492      *
493      * Recall that this is a necessary condition for a collision to occur; we
494      * are therefore safe whenever
495      *
496      *         %$N \ge n \log b/\log B + 1$% ;
497      *
498      * so, taking ceilings,
499      *
500      *         %$N \ge \lceil n \log b/\log B \rceil + 1$% .
501      *
502      * So that's why we have this.
503      *
504      * I'm going to assume that @n = DBL_MANT_DIG@ is sufficiently small that
505      * we can calculate this without ending up on the wrong side of an
506      * integer boundary.
507      *
508      * In C11, we have @DBL_DECIMAL_DIG@, which should be the same value only
509      * as a constant.  Except that modern compilers are more than clever
510      * enough to work out that this is a constant anyway.
511      *
512      * This is sometimes an overestimate: we'll print out meaningless digits
513      * that don't represent anything we actually know about the number in
514      * question.  To fix that, we'd need a complicated algorithm like Steele
515      * and White's Dragon4, Gay's @dtoa@, or Burger and Dybvig's algorithm
516      * (note that Loitsch's Grisu2 is conservative, and Grisu3 hands off to
517      * something else in difficult situations).
518      */
519
520     prec = ceil(DBL_MANT_DIG*log(FLT_RADIX)/log(10)) + 1;
521     gprintf(gops, go, "%.*g", prec, x);
522   }
523 }
524
525 /* --- @parse_floating@ --- *
526  *
527  * Arguments:   @double *x_out@ = where to put the result
528  *              @const char *p@ = string to parse
529  *              @const struct tvec_floatinfo *fi@ = floating-point info
530  *              @struct tvec_state *tv@ = test vector state
531  *
532  * Returns:     Zero on success, @-1@ on error.
533  *
534  * Use:         Parse a floating-point number from a string.  Reports any
535  *              necessary errors.
536  */
537
538 static int parse_floating(double *x_out, const char *p,
539                           const struct tvec_floatinfo *fi,
540                           struct tvec_state *tv)
541 {
542   const char *pp; char *q;
543   dstr d = DSTR_INIT;
544   double x;
545   int olderr, rc;
546
547   /* Check for special tokens. */
548   if (STRCMP(p, ==, "#nan")) {
549 #ifdef NAN
550     x = NAN; rc = 0;
551 #else
552     tvec_error(tv, "NaN not supported on this system");
553     rc = -1; goto end;
554 #endif
555   }
556
557   else if (STRCMP(p, ==, "#inf") ||
558            STRCMP(p, ==, "#+inf") || STRCMP(p, ==, "+#inf")) {
559 #ifdef INFINITY
560     x = INFINITY; rc = 0;
561 #else
562     tvec_error(tv, "infinity not supported on this system");
563     rc = -1; goto end;
564 #endif
565   }
566
567   else if (STRCMP(p, ==, "#-inf") || STRCMP(p, ==, "-#inf")) {
568 #ifdef INFINITY
569     x = -INFINITY; rc = 0;
570 #else
571     tvec_error(tv, "infinity not supported on this system");
572     rc = -1; goto end;
573 #endif
574   }
575
576   /* Check that this looks like a number, so we can exclude `strtod'
577    * recognizing its own non-finite number tokens.
578    */
579   else {
580     pp = p;
581     if (*pp == '+' || *pp == '-') pp++;
582     if (*pp == '.') pp++;
583     if (!ISDIGIT(*pp)) {
584       tvec_syntax(tv, *p ? *p : fgetc(tv->fp), "floating-point number");
585       rc = -1; goto end;
586     }
587
588     /* Parse the number using the system parser. */
589     olderr = errno; errno = 0;
590     x = strtod(p, &q);
591     if (*q) {
592       tvec_syntax(tv, *q, "end-of-line");
593       rc = -1; goto end;
594     }
595     if (errno && (errno != ERANGE || (x > 0 ? -x : x) == HUGE_VAL)) {
596       tvec_error(tv, "invalid floating-point number `%s': %s",
597                  p, strerror(errno));
598       rc = -1; goto end;
599     }
600     errno = olderr;
601   }
602
603   /* Check that the number is acceptable. */
604   if (NANP(x) && fi && !(fi->f&TVFF_NANOK)) {
605     tvec_error(tv, "#nan not allowed here");
606     rc = -1; goto end;
607   }
608
609   if (fi && ((!(fi->f&TVFF_NOMIN) && x < fi->min) ||
610              (!(fi->f&TVFF_NOMAX) && x > fi->max))) {
611     dstr_puts(&d, "floating-point number ");
612     format_floating(&dstr_printops, &d, x);
613     dstr_puts(&d, " out of range (must be in ");
614     if (fi->f&TVFF_NOMIN)
615       dstr_puts(&d, "(#-inf");
616     else
617       { dstr_putc(&d, '['); format_floating(&dstr_printops, &d, fi->min); }
618     dstr_puts(&d, " .. ");
619     if (fi->f&TVFF_NOMAX)
620       dstr_puts(&d, "#+inf)");
621     else
622       { format_floating(&dstr_printops, &d, fi->max); dstr_putc(&d, ']'); }
623     dstr_putc(&d, ')'); dstr_putz(&d);
624     tvec_error(tv, "%s", d.buf); rc = -1; goto end;
625   }
626
627   /* All done. */
628   *x_out = x; rc = 0;
629 end:
630   dstr_destroy(&d);
631   return (rc);
632 }
633
634 /*----- String utilities --------------------------------------------------*/
635
636 /* Special character name table. */
637 static const struct chartab {
638   const char *name;                     /* character name */
639   int ch;                               /* character value */
640   unsigned f;                           /* flags: */
641 #define CTF_PREFER 1u                   /*   preferred name */
642 #define CTF_SHORT 2u                    /*   short name (compact style) */
643 } chartab[] = {
644   { "#eof",             EOF,    CTF_PREFER | CTF_SHORT },
645   { "#nul",             '\0',   CTF_PREFER },
646   { "#bell",            '\a',   CTF_PREFER },
647   { "#ding",            '\a',   0 },
648   { "#bel",             '\a',   CTF_SHORT },
649   { "#backspace",       '\b',   CTF_PREFER },
650   { "#bs",              '\b',   CTF_SHORT },
651   { "#escape",          '\x1b', CTF_PREFER },
652   { "#esc",             '\x1b', CTF_SHORT },
653   { "#formfeed",        '\f',   CTF_PREFER },
654   { "#ff",              '\f',   CTF_SHORT },
655   { "#newline",         '\n',   CTF_PREFER },
656   { "#linefeed",        '\n',   0 },
657   { "#lf",              '\n',   CTF_SHORT },
658   { "#nl",              '\n',   0 },
659   { "#return",          '\r',   CTF_PREFER },
660   { "#carriage-return", '\r',   0 },
661   { "#cr",              '\r',   CTF_SHORT },
662   { "#tab",             '\t',   CTF_PREFER | CTF_SHORT },
663   { "#horizontal-tab",  '\t',   0 },
664   { "#ht",              '\t',   0 },
665   { "#vertical-tab",    '\v',   CTF_PREFER },
666   { "#vt",              '\v',   CTF_SHORT },
667   { "#space",           ' ',    0 },
668   { "#spc",             ' ',    CTF_SHORT },
669   { "#delete",          '\x7f', CTF_PREFER },
670   { "#del",             '\x7f', CTF_SHORT },
671   { 0,                  0,      0 }
672 };
673
674 /* --- @find_charname@ --- *
675  *
676  * Arguments:   @int ch@ = character to match
677  *              @unsigned f@ = flags (@CTF_...@) to match
678  *
679  * Returns:     The name of the character, or null if no match is found.
680  *
681  * Use:         Looks up a name for a character.  Specifically, it returns
682  *              the first entry in the @chartab@ table which matches @ch@ and
683  *              which has one of the flags @f@ set.
684  */
685
686 static const char *find_charname(int ch, unsigned f)
687 {
688   const struct chartab *ct;
689
690   for (ct = chartab; ct->name; ct++)
691     if (ct->ch == ch && (ct->f&f)) return (ct->name);
692   return (0);
693 }
694
695 /* --- @read_charname@ --- *
696  *
697  * Arguments:   @int *ch_out@ = where to put the character
698  *              @const char *p@ = character name
699  *              @unsigned f@ = flags (@TCF_...@)
700  *
701  * Returns:     Zero if a match was found, @-1@ if not.
702  *
703  * Use:         Looks up a character by name.  If @RCF_EOFOK@ is set in @f@,
704  *              then the @EOF@ marker can be matched; otherwise it can't.
705  */
706
707 #define RCF_EOFOK 1u
708 static int read_charname(int *ch_out, const char *p, unsigned f)
709 {
710   const struct chartab *ct;
711
712   for (ct = chartab; ct->name; ct++)
713     if (STRCMP(p, ==, ct->name) && ((f&RCF_EOFOK) || ct->ch >= 0))
714       { *ch_out = ct->ch; return (0); }
715   return (-1);
716 }
717
718 /* --- @format_charesc@ --- *
719  *
720  * Arguments:   @const struct gprintf_ops *gops@ = print operations
721  *              @void *go@ = print destination
722  *              @int ch@ = character to format
723  *              @unsigned f@ = flags (@FCF_...@)
724  *
725  * Returns:     ---
726  *
727  * Use:         Format a character as an escape sequence, possibly as part of
728  *              a larger string.  If @FCF_BRACE@ is set in @f@, then put
729  *              braces around a `\x...'  code, so that it's suitable for use
730  *              in a longer string.
731  */
732
733 #define FCF_BRACE 1u
734 static void format_charesc(const struct gprintf_ops *gops, void *go,
735                            int ch, unsigned f)
736 {
737   switch (ch) {
738     case '\a': gprintf(gops, go, "\\a"); break;
739     case '\b': gprintf(gops, go, "\\b"); break;
740     case '\x1b': gprintf(gops, go, "\\e"); break;
741     case '\f': gprintf(gops, go, "\\f"); break;
742     case '\r': gprintf(gops, go, "\\r"); break;
743     case '\n': gprintf(gops, go, "\\n"); break;
744     case '\t': gprintf(gops, go, "\\t"); break;
745     case '\v': gprintf(gops, go, "\\v"); break;
746     case '\\': gprintf(gops, go, "\\\\"); break;
747     case '\'': gprintf(gops, go, "\\'"); break;
748     case '\0':
749       if (f&FCF_BRACE) gprintf(gops, go, "\\{0}");
750       else gprintf(gops, go, "\\0");
751       break;
752     default:
753       if (f&FCF_BRACE)
754         gprintf(gops, go, "\\x{%0*x}", hex_width(UCHAR_MAX), ch);
755       else
756         gprintf(gops, go, "\\x%0*x", hex_width(UCHAR_MAX), ch);
757       break;
758   }
759 }
760
761 /* --- @format_char@ --- *
762  *
763  * Arguments:   @const struct gprintf_ops *gops@ = print operations
764  *              @void *go@ = print destination
765  *              @int ch@ = character to format
766  *
767  * Returns:     ---
768  *
769  * Use:         Format a single character.
770  */
771
772 static void format_char(const struct gprintf_ops *gops, void *go, int ch)
773 {
774   switch (ch) {
775     case '\\': case '\'': escape:
776       gprintf(gops, go, "'");
777       format_charesc(gops, go, ch, 0);
778       gprintf(gops, go, "'");
779       break;
780     default:
781       if (!isprint(ch)) goto escape;
782       gprintf(gops, go, "'%c'", ch);
783       break;
784   }
785 }
786
787 /* --- @maybe_format_unsigned_char@, @maybe_format_signed_char@ --- *
788  *
789  * Arguments:   @const struct gprintf_ops *gops@ = print operations
790  *              @void *go@ = print destination
791  *              @unsigned long u@ or @long i@ = an integer
792  *
793  * Returns:     ---
794  *
795  * Use:         Format a (signed or unsigned) integer as a character, if it's
796  *              in range, printing something like `= 'q''.  It's assumed that
797  *              a comment marker has already been output.
798  */
799
800 static void maybe_format_unsigned_char
801   (const struct gprintf_ops *gops, void *go, unsigned long u)
802 {
803   const char *p;
804
805   p = find_charname(u, CTF_PREFER);
806   if (p) gprintf(gops, go, " = %s", p);
807   if (u < UCHAR_MAX)
808     { gprintf(gops, go, " = "); format_char(gops, go, u); }
809 }
810
811 static void maybe_format_signed_char
812   (const struct gprintf_ops *gops, void *go, long i)
813 {
814   const char *p;
815
816   p = find_charname(i, CTF_PREFER);
817   if (p) gprintf(gops, go, " = %s", p);
818   if (0 <= i && i < UCHAR_MAX)
819     { gprintf(gops, go, " = "); format_char(gops, go, i); }
820 }
821
822 /* --- @read_charesc@ --- *
823  *
824  * Arguments:   @int *ch_out@ = where to put the result
825  *              @struct tvec_state *tv@ = test vector state
826  *
827  * Returns:     Zero on success, @-1@ on error.
828  *
829  * Use:         Parse and convert an escape sequence from @tv@'s input
830  *              stream, assuming that the initial `\' has already been read.
831  *              Reports errors as appropriate.
832  */
833
834 static int read_charesc(int *ch_out, struct tvec_state *tv)
835 {
836   int ch, i, esc;
837   unsigned f = 0;
838 #define f_brace 1u
839
840   ch = getc(tv->fp);
841   switch (ch) {
842
843     /* Things we shouldn't find. */
844     case EOF: case '\n': return (tvec_syntax(tv, ch, "string escape"));
845
846     /* Single-character escapes. */
847     case '\'': *ch_out = '\''; break;
848     case '\\': *ch_out = '\\'; break;
849     case '"': *ch_out = '"'; break;
850     case 'a': *ch_out = '\a'; break;
851     case 'b': *ch_out = '\b'; break;
852     case 'e': *ch_out = '\x1b'; break;
853     case 'f': *ch_out = '\f'; break;
854     case 'n': *ch_out = '\n'; break;
855     case 'r': *ch_out = '\r'; break;
856     case 't': *ch_out = '\t'; break;
857     case 'v': *ch_out = '\v'; break;
858
859     /* Hex escapes, with and without braces. */
860     case 'x':
861       ch = getc(tv->fp);
862       if (ch == '{') { f |= f_brace; ch = getc(tv->fp); }
863       else f &= ~f_brace;
864       esc = chtodig(ch);
865       if (esc < 0 || esc >= 16) return (tvec_syntax(tv, ch, "hex digit"));
866       for (;;) {
867         ch = getc(tv->fp); i = chtodig(ch); if (i < 0 || i >= 16) break;
868         esc = 16*esc + i;
869         if (esc > UCHAR_MAX)
870           return (tvec_error(tv,
871                              "character code %d out of range", esc));
872       }
873       if (!(f&f_brace)) ungetc(ch, tv->fp);
874       else if (ch != '}') return (tvec_syntax(tv, ch, "`}'"));
875       *ch_out = esc;
876       break;
877
878     /* Other things, primarily octal escapes. */
879     case '{':
880       f |= f_brace; ch = getc(tv->fp);
881       /* fall through */
882     default:
883       if ('0' <= ch && ch < '8') {
884         i = 1; esc = ch - '0';
885         for (;;) {
886           ch = getc(tv->fp);
887           if ('0' > ch || ch >= '8') { ungetc(ch, tv->fp); break; }
888           esc = 8*esc + ch - '0';
889           i++; if (i >= 3) break;
890         }
891         if (f&f_brace) {
892           ch = getc(tv->fp);
893           if (ch != '}') return (tvec_syntax(tv, ch, "`}'"));
894         }
895         if (esc > UCHAR_MAX)
896           return (tvec_error(tv,
897                              "character code %d out of range", esc));
898         *ch_out = esc; break;
899       } else
900         return (tvec_syntax(tv, ch, "string escape"));
901   }
902
903   /* Done. */
904   return (0);
905
906 #undef f_brace
907 }
908
909 /* --- @read_quoted_string@ --- *
910  *
911  * Arguments:   @dstr *d@ = string to write to
912  *              @int quote@ = initial quote, `'' or `"'
913  *              @struct tvec_state *tv@ = test vector state
914  *
915  * Returns:     Zero on success, @-1@ on error.
916  *
917  * Use:         Read the rest of a quoted string into @d@, reporting errors
918  *              as appropriate.
919  *
920  *              A single-quoted string is entirely literal.  A double-quoted
921  *              string may contain C-like escapes.
922  */
923
924 static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
925 {
926   int ch;
927
928   for (;;) {
929     ch = getc(tv->fp);
930     switch (ch) {
931       case EOF: case '\n':
932         return (tvec_syntax(tv, ch, "`%c'", quote));
933       case '\\':
934         if (quote == '\'') goto ordinary;
935         ch = getc(tv->fp); if (ch == '\n') { tv->lno++; break; }
936         ungetc(ch, tv->fp); if (read_charesc(&ch, tv)) return (-1);
937         goto ordinary;
938       default:
939         if (ch == quote) goto end;
940       ordinary:
941         DPUTC(d, ch);
942         break;
943     }
944   }
945
946 end:
947   DPUTZ(d);
948   return (0);
949 }
950
951 /* --- @collect_bare@ --- *
952  *
953  * Arguments:   @dstr *d@ = string to write to
954  *              @struct tvec_state *tv@ = test vector state
955  *
956  * Returns:     Zero on success, @-1@ on error.
957  *
958  * Use:         Read barewords and the whitespace between them.  Stop when we
959  *              encounter something which can't start a bareword.
960  */
961
962 static int collect_bare(dstr *d, struct tvec_state *tv)
963 {
964   size_t pos = d->len;
965   enum { WORD, SPACE, ESCAPE }; unsigned s = WORD;
966   int ch, rc;
967
968   for (;;) {
969     ch = getc(tv->fp);
970     switch (ch) {
971       case EOF:
972         tvec_syntax(tv, ch, "bareword");
973         rc = -1; goto end;
974       case '\n':
975         if (s == ESCAPE) { tv->lno++; goto addch; }
976         if (s == WORD) pos = d->len;
977         ungetc(ch, tv->fp); if (tvec_nexttoken(tv)) { rc = -1; goto end; }
978         DPUTC(d, ' '); s = SPACE;
979         break;
980       case '"': case '\'': case '!': case '#': case ')': case '}': case ']':
981         if (s == SPACE) { ungetc(ch, tv->fp); goto done; }
982         goto addch;
983       case '\\':
984         s = ESCAPE;
985         break;
986       default:
987         if (s != ESCAPE && isspace(ch)) {
988           if (s == WORD) pos = d->len;
989           DPUTC(d, ch); s = SPACE;
990           break;
991         }
992       addch:
993         DPUTC(d, ch); s = WORD;
994     }
995   }
996
997 done:
998   if (s == SPACE) d->len = pos;
999   DPUTZ(d); rc = 0;
1000 end:
1001   return (rc);
1002 }
1003
1004 /* --- @set_up_encoding@ --- *
1005  *
1006  * Arguments:   @const codec_class **ccl_out@ = where to put the class
1007  *              @unsigned *f_out@ = where to put the flags
1008  *              @unsigned code@ = the coding scheme to use (@TVEC_...@)
1009  *
1010  * Returns:     ---
1011  *
1012  * Use:         Helper for @read_compound_string@ below.
1013  *
1014  *              Return the appropriate codec class and flags for @code@.
1015  *              Leaves @*ccl_out@ null if the coding scheme doesn't have a
1016  *              backing codec class (e.g., @TVCODE_BARE@).
1017  */
1018
1019 enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 };
1020 static void set_up_encoding(const codec_class **ccl_out, unsigned *f_out,
1021                             unsigned code)
1022 {
1023   switch (code) {
1024     case TVCODE_BARE:
1025       *ccl_out = 0; *f_out = 0;
1026       break;
1027     case TVCODE_HEX:
1028       *ccl_out = &hex_class; *f_out = CDCF_IGNCASE;
1029       break;
1030     case TVCODE_BASE32:
1031       *ccl_out = &base32_class; *f_out = CDCF_IGNCASE | CDCF_IGNEQPAD;
1032       break;
1033     case TVCODE_BASE64:
1034       *ccl_out = &base64_class; *f_out = CDCF_IGNEQPAD;
1035       break;
1036     default:
1037       abort();
1038   }
1039 }
1040
1041 /* --- @flush_codec@ --- *
1042  *
1043  * Arguments:   @codec *cdc@ = a codec, or null
1044  *              @dstr *d@ = output string
1045  *              @struct tvec_state *tv@ = test vector state
1046  *
1047  * Returns:     Zero on success, @-1@ on error.
1048  *
1049  * Use:         Helper for @read_compound_string@ below.
1050  *
1051  *              Flush out any final buffered material from @cdc@, and check
1052  *              that it's in a good state.  Frees the codec on success.  Does
1053  *              nothing if @cdc@ is null.
1054  */
1055
1056 static int flush_codec(codec *cdc, dstr *d, struct tvec_state *tv)
1057 {
1058   int err;
1059
1060   if (cdc) {
1061     err = cdc->ops->code(cdc, 0, 0, d);
1062     if (err)
1063       return (tvec_error(tv, "invalid %s sequence end: %s",
1064                          cdc->ops->c->name, codec_strerror(err)));
1065     cdc->ops->destroy(cdc);
1066   }
1067   return (0);
1068 }
1069
1070 /* --- @read_compound_string@ --- *
1071  *
1072  * Arguments:   @void **p_inout@ = address of output buffer pointer
1073  *              @size_t *sz_inout@ = address of buffer size
1074  *              @unsigned code@ = initial interpretation of barewords
1075  *              @unsigned f@ = other flags (@RCSF_...@)
1076  *              @struct tvec_state *tv@ = test vector state
1077  *
1078  * Returns:     Zero on success, @-1@ on error.
1079  *
1080  * Use:         Parse a compound string, i.e., a sequence of stringish pieces
1081  *              which might be quoted strings, character names, or barewords
1082  *              to be decoded accoding to @code@, interspersed with
1083  *              additional directives.
1084  *
1085  *              If the initial buffer pointer is non-null and sufficiently
1086  *              large, then it will be reused; otherwise, it is freed and a
1087  *              fresh, sufficiently large buffer is allocated and returned.
1088  */
1089
1090 #define RCSF_NESTED 1u
1091 static int read_compound_string(void **p_inout, size_t *sz_inout,
1092                                 unsigned code, unsigned f,
1093                                 struct tvec_state *tv)
1094 {
1095   const codec_class *ccl; unsigned cdf;
1096   codec *cdc;
1097   dstr d = DSTR_INIT, w = DSTR_INIT;
1098   char *p;
1099   const char *q;
1100   void *pp = 0; size_t sz;
1101   unsigned long n;
1102   int ch, err, rc;
1103
1104   set_up_encoding(&ccl, &cdf, code); cdc = 0;
1105
1106   if (tvec_nexttoken(tv)) return (tvec_syntax(tv, fgetc(tv->fp), "string"));
1107   do {
1108     ch = getc(tv->fp);
1109     switch (ch) {
1110
1111       case ')': case ']': case '}':
1112         /* Close brackets.  Leave these for recursive caller if there is one,
1113          * or just complain.
1114          */
1115
1116         if (!(f&RCSF_NESTED))
1117           { rc = tvec_syntax(tv, ch, "string"); goto end; }
1118         ungetc(ch, tv->fp); goto done;
1119
1120       case '"': case '\'':
1121         /* Quotes.  Read a quoted string. */
1122
1123         if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
1124         cdc = 0;
1125         if (read_quoted_string(&d, ch, tv)) { rc = -1; goto end; }
1126         break;
1127
1128       case '#':
1129         /* A named character. */
1130
1131         ungetc(ch, tv->fp);
1132         if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
1133         cdc = 0;
1134         DRESET(&w); tvec_readword(tv, &w, ";", "character name");
1135         if (read_charname(&ch, w.buf, RCF_EOFOK)) {
1136           rc = tvec_error(tv, "unknown character name `%s'", d.buf);
1137           goto end;
1138         }
1139         DPUTC(&d, ch); break;
1140
1141       case '!':
1142         /* A magic keyword. */
1143
1144         if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
1145         cdc = 0;
1146         ungetc(ch, tv->fp);
1147         DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword");
1148
1149         /* Change bareword coding system. */
1150         if (STRCMP(w.buf, ==, "!bare"))
1151           { code = TVCODE_BARE; set_up_encoding(&ccl, &cdf, code); }
1152         else if (STRCMP(w.buf, ==, "!hex"))
1153           { code = TVCODE_HEX; set_up_encoding(&ccl, &cdf, code); }
1154         else if (STRCMP(w.buf, ==, "!base32"))
1155           { code = TVCODE_BASE32; set_up_encoding(&ccl, &cdf, code); }
1156         else if (STRCMP(w.buf, ==, "!base64"))
1157           { code = TVCODE_BASE64; set_up_encoding(&ccl, &cdf, code); }
1158
1159         /* Repeated substrings. */
1160         else if (STRCMP(w.buf, ==, "!repeat")) {
1161           if (tvec_nexttoken(tv)) {
1162             rc = tvec_syntax(tv, fgetc(tv->fp), "repeat count");
1163             goto end;
1164           }
1165           DRESET(&w);
1166           if (tvec_readword(tv, &w, ";{", "repeat count"))
1167             { rc = -1; goto end;  }
1168           if (parse_unsigned_integer(&n, &q, w.buf)) {
1169             rc = tvec_error(tv, "invalid repeat count `%s'", w.buf);
1170             goto end;
1171           }
1172           if (*q) { rc = tvec_syntax(tv, *q, "`{'"); goto end; }
1173           if (tvec_nexttoken(tv))
1174             { rc = tvec_syntax(tv, fgetc(tv->fp), "`{'"); goto end; }
1175           ch = getc(tv->fp); if (ch != '{')
1176             { rc = tvec_syntax(tv, ch, "`{'"); goto end; }
1177           sz = 0;
1178           if (read_compound_string(&pp, &sz, code, f | RCSF_NESTED, tv))
1179             { rc = -1; goto end; }
1180           ch = getc(tv->fp); if (ch != '}')
1181             { rc = tvec_syntax(tv, ch, "`}'"); goto end; }
1182           if (sz) {
1183             if (n > (size_t)-1/sz)
1184               { rc = tvec_error(tv, "repeat size out of range"); goto end; }
1185             dstr_ensure(&d, n*sz);
1186             if (sz == 1)
1187               { memset(d.buf + d.len, *(unsigned char *)pp, n); d.len += n; }
1188             else
1189               for (; n--; d.len += sz) memcpy(d.buf + d.len, pp, sz);
1190           }
1191           xfree(pp); pp = 0;
1192         }
1193
1194         /* Anything else is an error. */
1195         else {
1196           tvec_error(tv, "unknown string keyword `%s'", w.buf);
1197           rc = -1; goto end;
1198         }
1199         break;
1200
1201       default:
1202         /* A bareword.  Process it according to the current coding system. */
1203
1204         switch (code) {
1205           case TVCODE_BARE:
1206             ungetc(ch, tv->fp);
1207             if (collect_bare(&d, tv)) goto done;
1208             break;
1209           default:
1210             assert(ccl);
1211             ungetc(ch, tv->fp); DRESET(&w);
1212             if (tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name))
1213               { rc = -1; goto end; }
1214             if (!cdc) cdc = ccl->decoder(cdf);
1215             err = cdc->ops->code(cdc, w.buf, w.len, &d);
1216             if (err) {
1217               tvec_error(tv, "invalid %s fragment `%s': %s",
1218                          ccl->name, w.buf, codec_strerror(err));
1219               rc = -1; goto end;
1220             }
1221             break;
1222         }
1223         break;
1224     }
1225   } while (!tvec_nexttoken(tv));
1226
1227 done:
1228   /* Wrap things up. */
1229   if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
1230   cdc = 0;
1231   if (*sz_inout <= d.len)
1232     { xfree(*p_inout); *p_inout = xmalloc(d.len + 1); }
1233   p = *p_inout; memcpy(p, d.buf, d.len); p[d.len] = 0; *sz_inout = d.len;
1234   rc = 0;
1235
1236 end:
1237   /* Clean up any debris. */
1238   if (cdc) cdc->ops->destroy(cdc);
1239   if (pp) xfree(pp);
1240   dstr_destroy(&d); dstr_destroy(&w);
1241   return (rc);
1242 }
1243
1244 /*----- Skeleton ----------------------------------------------------------*/
1245 /*
1246 static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd)
1247 static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd)
1248 static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1,
1249                   const struct tvec_regdef *rd)
1250 static int tobuf_...(buf *b, const union tvec_regval *rv,
1251                      const struct tvec_regdef *rd)
1252 static int frombuf_...(buf *b, union tvec_regval *rv,
1253                        const struct tvec_regdef *rd)
1254 static int parse_...(union tvec_regval *rv, const struct tvec_regdef *rd,
1255                      struct tvec_state *tv)
1256 static void dump_...(const union tvec_regval *rv,
1257                      const struct tvec_regdef *rd,
1258                      struct tvec_state *tv, unsigned style)
1259
1260 const struct tvec_regty tvty_... = {
1261   init_..., release_..., eq_...,
1262   tobuf_..., frombuf_...,
1263   parse_..., dump_...
1264 };
1265 */
1266 /*----- Signed and unsigned integer types ---------------------------------*/
1267
1268 static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd)
1269   { rv->i = 0; }
1270
1271 static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd)
1272   { rv->u = 0; }
1273
1274 static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1,
1275                   const struct tvec_regdef *rd)
1276   { return (rv0->i == rv1->i); }
1277
1278 static int eq_uint(const union tvec_regval *rv0,
1279                    const union tvec_regval *rv1,
1280                    const struct tvec_regdef *rd)
1281   { return (rv0->u == rv1->u); }
1282
1283 static int tobuf_int(buf *b, const union tvec_regval *rv,
1284                      const struct tvec_regdef *rd)
1285   { return (signed_to_buf(b, rv->i)); }
1286
1287 static int tobuf_uint(buf *b, const union tvec_regval *rv,
1288                        const struct tvec_regdef *rd)
1289   { return (unsigned_to_buf(b, rv->u)); }
1290
1291 static int frombuf_int(buf *b, union tvec_regval *rv,
1292                        const struct tvec_regdef *rd)
1293   { return (signed_from_buf(b, &rv->i)); }
1294
1295 static int frombuf_uint(buf *b, union tvec_regval *rv,
1296                         const struct tvec_regdef *rd)
1297   { return (unsigned_from_buf(b, &rv->u)); }
1298
1299 static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
1300                      struct tvec_state *tv)
1301 {
1302   dstr d = DSTR_INIT;
1303   int rc;
1304
1305   if (tvec_readword(tv, &d, ";", "signed integer"))
1306     { rc = -1; goto end; }
1307   if (parse_signed(&rv->i, d.buf, rd->arg.p, tv))
1308     { rc = -1; goto end; }
1309   if (tvec_flushtoeol(tv, 0))
1310     { rc = -1; goto end; }
1311   rc = 0;
1312 end:
1313   dstr_destroy(&d);
1314   return (rc);
1315 }
1316
1317 static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
1318                       struct tvec_state *tv)
1319 {
1320   dstr d = DSTR_INIT;
1321   int rc;
1322
1323   if (tvec_readword(tv, &d, ";", "unsigned integer"))
1324     { rc = -1; goto end; }
1325   if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv))
1326     { rc = -1; goto end; }
1327   if (tvec_flushtoeol(tv, 0))
1328     { rc = -1; goto end; }
1329   rc = 0;
1330 end:
1331   dstr_destroy(&d);
1332   return (rc);
1333 }
1334
1335 static void dump_int(const union tvec_regval *rv,
1336                      const struct tvec_regdef *rd,
1337                      unsigned style,
1338                      const struct gprintf_ops *gops, void *go)
1339 {
1340
1341   gprintf(gops, go, "%ld", rv->i);
1342   if (!(style&TVSF_COMPACT)) {
1343     gprintf(gops, go, " ; = ");
1344     format_signed_hex(gops, go, rv->i);
1345     maybe_format_signed_char(gops, go, rv->i);
1346   }
1347 }
1348
1349 static void dump_uint(const union tvec_regval *rv,
1350                       const struct tvec_regdef *rd,
1351                       unsigned style,
1352                       const struct gprintf_ops *gops, void *go)
1353 {
1354   gprintf(gops, go, "%lu", rv->u);
1355   if (!(style&TVSF_COMPACT)) {
1356     gprintf(gops, go, " ; = ");
1357     format_unsigned_hex(gops, go, rv->u);
1358     maybe_format_unsigned_char(gops, go, rv->u);
1359   }
1360 }
1361
1362 const struct tvec_regty tvty_int = {
1363   init_int, trivial_release, eq_int,
1364   tobuf_int, frombuf_int,
1365   parse_int, dump_int
1366 };
1367
1368 const struct tvec_irange
1369   tvrange_schar = { SCHAR_MIN, SCHAR_MAX },
1370   tvrange_short = { SHRT_MIN, SHRT_MAX },
1371   tvrange_int = { INT_MIN, INT_MAX },
1372   tvrange_long = { LONG_MIN, LONG_MAX },
1373   tvrange_sbyte = { -128, 127 },
1374   tvrange_i16 = { -32768, +32767 },
1375   tvrange_i32 = { -2147483648, 2147483647 };
1376
1377 const struct tvec_regty tvty_uint = {
1378   init_uint, trivial_release, eq_uint,
1379   tobuf_uint, frombuf_uint,
1380   parse_uint, dump_uint
1381 };
1382
1383 const struct tvec_urange
1384   tvrange_uchar = { 0, UCHAR_MAX },
1385   tvrange_ushort = { 0, USHRT_MAX },
1386   tvrange_uint = { 0, UINT_MAX },
1387   tvrange_ulong = { 0, ULONG_MAX },
1388   tvrange_size = { 0, (size_t)-1 },
1389   tvrange_byte = { 0, 255 },
1390   tvrange_u16 = { 0, 65535 },
1391   tvrange_u32 = { 0, 4294967296 };
1392
1393 /* --- @tvec_claimeq_int@ --- *
1394  *
1395  * Arguments:   @struct tvec_state *tv@ = test-vector state
1396  *              @long i0, i1@ = two signed integers
1397  *              @const char *file@, @unsigned @lno@ = calling file and line
1398  *              @const char *expr@ = the expression to quote on failure
1399  *
1400  * Returns:     Nonzero if @i0@ and @i1@ are equal, otherwise zero.
1401  *
1402  * Use:         Check that values of @i0@ and @i1@ are equal.  As for
1403  *              @tvec_claim@ above, a test case is automatically begun and
1404  *              ended if none is already underway.  If the values are
1405  *              unequal, then @tvec_fail@ is called, quoting @expr@, and the
1406  *              mismatched values are dumped: @i0@ is printed as the output
1407  *              value and @i1@ is printed as the input reference.
1408  */
1409
1410 int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1,
1411                      const char *file, unsigned lno, const char *expr)
1412 {
1413   tv->out[0].v.i = i0; tv->in[0].v.i = i1;
1414   return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr));
1415 }
1416
1417 /* --- @tvec_claimeq_uint@ --- *
1418  *
1419  * Arguments:   @struct tvec_state *tv@ = test-vector state
1420  *              @unsigned long u0, u1@ = two unsigned integers
1421  *              @const char *file@, @unsigned @lno@ = calling file and line
1422  *              @const char *expr@ = the expression to quote on failure
1423  *
1424  * Returns:     Nonzero if @u0@ and @u1@ are equal, otherwise zero.
1425  *
1426  * Use:         Check that values of @u0@ and @u1@ are equal.  As for
1427  *              @tvec_claim@ above, a test case is automatically begun and
1428  *              ended if none is already underway.  If the values are
1429  *              unequal, then @tvec_fail@ is called, quoting @expr@, and the
1430  *              mismatched values are dumped: @u0@ is printed as the output
1431  *              value and @u1@ is printed as the input reference.
1432  */
1433
1434 int tvec_claimeq_uint(struct tvec_state *tv,
1435                       unsigned long u0, unsigned long u1,
1436                       const char *file, unsigned lno, const char *expr)
1437 {
1438   tv->out[0].v.u = u0; tv->in[0].v.u = u1;
1439   return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr));
1440 }
1441
1442 /*----- Floating-point type -----------------------------------------------*/
1443
1444 static void init_float(union tvec_regval *rv, const struct tvec_regdef *rd)
1445   { rv->f = 0.0; }
1446
1447 static int eq_float(const union tvec_regval *rv0,
1448                     const union tvec_regval *rv1,
1449                     const struct tvec_regdef *rd)
1450   { return (eqish_floating_p(rv0->f, rv1->f, rd->arg.p)); }
1451
1452 static int tobuf_float(buf *b, const union tvec_regval *rv,
1453                      const struct tvec_regdef *rd)
1454   { return (buf_putf64l(b, rv->f)); }
1455 static int frombuf_float(buf *b, union tvec_regval *rv,
1456                        const struct tvec_regdef *rd)
1457   { return (buf_getf64l(b, &rv->f)); }
1458
1459 static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
1460                        struct tvec_state *tv)
1461 {
1462   dstr d = DSTR_INIT;
1463   int rc;
1464
1465   if (tvec_readword(tv, &d, ";", "floating-point number"))
1466     { rc = -1; goto end; }
1467   if (parse_floating(&rv->f, d.buf, rd->arg.p, tv))
1468     { rc = -1; goto end; }
1469   if (tvec_flushtoeol(tv, 0))
1470     { rc = -1; goto end; }
1471   rc = 0;
1472 end:
1473   dstr_destroy(&d);
1474   return (rc);
1475 }
1476
1477 static void dump_float(const union tvec_regval *rv,
1478                        const struct tvec_regdef *rd,
1479                        unsigned style,
1480                        const struct gprintf_ops *gops, void *go)
1481   { format_floating(gops, go, rv->f); }
1482
1483 const struct tvec_regty tvty_float = {
1484   init_float, trivial_release, eq_float,
1485   tobuf_float, frombuf_float,
1486   parse_float, dump_float
1487 };
1488
1489 /* --- @tvec_claimeqish_float@ --- *
1490  *
1491  * Arguments:   @struct tvec_state *tv@ = test-vector state
1492  *              @double f0, f1@ = two floating-point numbers
1493  *              @unsigned f@ = flags (@TVFF_...@)
1494  *              @double delta@ = maximum tolerable difference
1495  *              @const char *file@, @unsigned @lno@ = calling file and line
1496  *              @const char *expr@ = the expression to quote on failure
1497  *
1498  * Returns:     Nonzero if @f0@ and @u1@ are sufficiently close, otherwise
1499  *              zero.
1500  *
1501  * Use:         Check that values of @f0@ and @f1@ are sufficiently close.
1502  *              As for @tvec_claim@ above, a test case is automatically begun
1503  *              and ended if none is already underway.  If the values are
1504  *              too far apart, then @tvec_fail@ is called, quoting @expr@,
1505  *              and the mismatched values are dumped: @f0@ is printed as the
1506  *              output value and @f1@ is printed as the input reference.
1507  *
1508  *              The details for the comparison are as follows.
1509  *
1510  *                * A NaN value matches any other NaN, and nothing else.
1511  *
1512  *                * An infinity matches another infinity of the same sign,
1513  *                  and nothing else.
1514  *
1515  *                * If @f&TVFF_EQMASK@ is @TVFF_EXACT@, then any
1516  *                  representable number matches only itself: in particular,
1517  *                  positive and negative zero are considered distinct.
1518  *                  (This allows tests to check that they land on the correct
1519  *                  side of branch cuts, for example.)
1520  *
1521  *                * If @f&TVFF_EQMASK@ is @TVFF_ABSDELTA@, then %$x$% matches
1522  *                  %$y$% when %$|x - y| < \delta$%.
1523  *
1524  *                * If @f&TVFF_EQMASK@ is @TVFF_RELDELTA@, then %$x$% matches
1525  *                  %$y$% when %$|1 - y/x| < \delta$%.  (Note that this
1526  *                  criterion is asymmetric FIXME
1527  */
1528
1529 int tvec_claimeqish_float(struct tvec_state *tv,
1530                           double f0, double f1, unsigned f, double delta,
1531                           const char *file, unsigned lno,
1532                           const char *expr)
1533 {
1534   struct tvec_floatinfo fi;
1535   union tvec_misc arg;
1536
1537   fi.f = f; fi.min = fi.max = 0.0; fi.delta = delta; arg.p = &fi;
1538   tv->out[0].v.f = f0; tv->in[0].v.f = f1;
1539   return (tvec_claimeq(tv, &tvty_float, &arg, file, lno, expr));
1540 }
1541
1542 /* --- @tvec_claimeq_float@ --- *
1543  *
1544  * Arguments:   @struct tvec_state *tv@ = test-vector state
1545  *              @double f0, f1@ = two floating-point numbers
1546  *              @const char *file@, @unsigned @lno@ = calling file and line
1547  *              @const char *expr@ = the expression to quote on failure
1548  *
1549  * Returns:     Nonzero if @f0@ and @u1@ are identical, otherwise zero.
1550  *
1551  * Use:         Check that values of @f0@ and @f1@ are identical.  The
1552  *              function is exactly equivalent to @tvec_claimeqish_float@
1553  *              with @f == TVFF_EXACT@.
1554  */
1555
1556 int tvec_claimeq_float(struct tvec_state *tv,
1557                        double f0, double f1,
1558                        const char *file, unsigned lno,
1559                        const char *expr)
1560 {
1561   return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0,
1562                                 file, lno, expr));
1563 }
1564
1565 const struct tvec_floatinfo
1566   tvflt_finite = { TVFF_EXACT, -DBL_MAX, DBL_MAX, 0.0 },
1567   tvflt_nonneg = { TVFF_EXACT, 0, DBL_MAX, 0.0 };
1568
1569 /*----- Enumerations ------------------------------------------------------*/
1570
1571 #define init_ienum init_int
1572 #define init_uenum init_uint
1573 #define init_fenum init_float
1574 static void init_penum(union tvec_regval *rv, const struct tvec_regdef *rd)
1575   { rv->p = 0; }
1576
1577 #define eq_ienum eq_int
1578 #define eq_uenum eq_uint
1579 static int eq_fenum(const union tvec_regval *rv0,
1580                     const union tvec_regval *rv1,
1581                     const struct tvec_regdef *rd)
1582 {
1583   const struct tvec_fenuminfo *ei = rd->arg.p;
1584   return (eqish_floating_p(rv0->f, rv1->f, ei->fi));
1585 }
1586 static int eq_penum(const union tvec_regval *rv0,
1587                     const union tvec_regval *rv1,
1588                     const struct tvec_regdef *rd)
1589   { return (rv0->p == rv1->p); }
1590
1591 #define tobuf_ienum tobuf_int
1592 #define tobuf_uenum tobuf_uint
1593 #define tobuf_fenum tobuf_float
1594 static int tobuf_penum(buf *b, const union tvec_regval *rv,
1595                        const struct tvec_regdef *rd)
1596 {
1597   const struct tvec_penuminfo *pei = rd->arg.p;
1598   const struct tvec_passoc *pa;
1599   long i;
1600
1601   for (pa = pei->av, i = 0; pa->tag; pa++, i++)
1602     if (pa->p == rv->p) goto found;
1603   if (!rv->p) i = -1;
1604   else return (-1);
1605 found:
1606   return (signed_to_buf(b, i));
1607 }
1608
1609 #define frombuf_ienum frombuf_int
1610 #define frombuf_uenum frombuf_uint
1611 #define frombuf_fenum frombuf_float
1612 static int frombuf_penum(buf *b, union tvec_regval *rv,
1613                         const struct tvec_regdef *rd)
1614 {
1615   const struct tvec_penuminfo *pei = rd->arg.p;
1616   const struct tvec_passoc *pa;
1617   long i, n;
1618
1619   for (pa = pei->av, n = 0; pa->tag; pa++, n++);
1620   if (signed_from_buf(b, &i)) return (-1);
1621   if (0 <= i && i < n) rv->p = (/*unconst*/ void *)pei->av[i].p;
1622   else if (i == -1) rv->p = 0;
1623   else return (-1);
1624   return (0);
1625 }
1626
1627 #define DEFPARSE_ENUM(tag_, ty, slot)                                   \
1628   static int parse_##slot##enum(union tvec_regval *rv,                  \
1629                                 const struct tvec_regdef *rd,           \
1630                                 struct tvec_state *tv)                  \
1631   {                                                                     \
1632     const struct tvec_##slot##enuminfo *ei = rd->arg.p;                 \
1633     const struct tvec_##slot##assoc *a;                                 \
1634     dstr d = DSTR_INIT;                                                 \
1635     int rc;                                                             \
1636                                                                         \
1637     if (tvec_readword(tv, &d, ";", "enumeration tag or " LITSTR_##tag_)) \
1638       { rc = -1; goto end; }                                            \
1639     for (a = ei->av; a->tag; a++)                                       \
1640       if (STRCMP(a->tag, ==, d.buf)) { FOUND_##tag_ goto done; }        \
1641     MISSING_##tag_                                                      \
1642     done:                                                               \
1643     if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }                  \
1644     rc = 0;                                                             \
1645   end:                                                                  \
1646     dstr_destroy(&d);                                                   \
1647     return (rc);                                                        \
1648   }
1649
1650 #define LITSTR_INT      "literal signed integer"
1651 #define FOUND_INT       rv->i = a->i;
1652 #define MISSING_INT     if (parse_signed(&rv->i, d.buf, ei->ir, tv))    \
1653                           { rc = -1; goto end; }
1654
1655 #define LITSTR_UINT     "literal unsigned integer"
1656 #define FOUND_UINT      rv->u = a->u;
1657 #define MISSING_UINT    if (parse_unsigned(&rv->u, d.buf, ei->ur, tv))  \
1658                           { rc = -1; goto end; }
1659
1660 #define LITSTR_FLT      "literal floating-point number, "               \
1661                           "`#-inf', `#+inf', or `#nan'"
1662 #define FOUND_FLT       rv->f = a->f;
1663 #define MISSING_FLT     if (parse_floating(&rv->f, d.buf, ei->fi, tv))  \
1664                           { rc = -1; goto end; }
1665
1666 #define LITSTR_PTR      "`#nil'"
1667 #define FOUND_PTR       rv->p = (/*unconst*/ void *)a->p;
1668 #define MISSING_PTR     if (STRCMP(d.buf, ==, "#nil"))                  \
1669                           rv->p = 0;                                    \
1670                         else {                                          \
1671                           tvec_error(tv, "unknown `%s' value `%s'",     \
1672                                      ei->name, d.buf);                  \
1673                           rc = -1; goto end;                            \
1674                         }
1675
1676 TVEC_MISCSLOTS(DEFPARSE_ENUM)
1677
1678 #undef LITSTR_INT
1679 #undef FOUND_INT
1680 #undef MISSING_INT
1681
1682 #undef LITSTR_UINT
1683 #undef FOUND_UINT
1684 #undef MISSING_UINT
1685
1686 #undef LITSTR_FLT
1687 #undef FOUND_FLT
1688 #undef MISSING_FLT
1689
1690 #undef LITSTR_PTR
1691 #undef FOUND_PTR
1692 #undef MISSING_PTR
1693
1694 #undef DEFPARSE_ENUM
1695
1696 #define DEFDUMP_ENUM(tag_, ty, slot)                                    \
1697   static void dump_##slot##enum(const union tvec_regval *rv,            \
1698                                 const struct tvec_regdef *rd,           \
1699                                 unsigned style,                         \
1700                                 const struct gprintf_ops *gops, void *go) \
1701   {                                                                     \
1702     const struct tvec_##slot##enuminfo *ei = rd->arg.p;                 \
1703     const struct tvec_##slot##assoc *a;                                 \
1704                                                                         \
1705     for (a = ei->av; a->tag; a++)                                       \
1706       if (rv->slot == a->slot) {                                        \
1707         gprintf(gops, go, "%s", a->tag);                                \
1708         if (style&TVSF_COMPACT) return;                                 \
1709         gprintf(gops, go, " ; = "); break;                              \
1710       }                                                                 \
1711                                                                         \
1712     PRINTRAW_##tag_                                                     \
1713   }
1714
1715 #define MAYBE_PRINT_EXTRA                                               \
1716         if (style&TVSF_COMPACT) ;                                       \
1717         else if (!a->tag) { gprintf(gops, go, " ; = "); goto _extra; }  \
1718         else if (1) { gprintf(gops, go, " = "); goto _extra; }          \
1719         else _extra:
1720
1721 #define PRINTRAW_INT    gprintf(gops, go, "%ld", rv->i);                \
1722                         MAYBE_PRINT_EXTRA {                             \
1723                           format_signed_hex(gops, go, rv->i);           \
1724                           maybe_format_signed_char(gops, go, rv->i);    \
1725                         }
1726
1727 #define PRINTRAW_UINT   gprintf(gops, go, "%lu", rv->u);                \
1728                         MAYBE_PRINT_EXTRA {                             \
1729                           format_unsigned_hex(gops, go, rv->u);         \
1730                           maybe_format_unsigned_char(gops, go, rv->u);  \
1731                         }
1732
1733 #define PRINTRAW_FLT    format_floating(gops, go, rv->f);
1734
1735 #define PRINTRAW_PTR    if (!rv->p) gprintf(gops, go, "#nil");          \
1736                         else gprintf(gops, go, "#<%s %p>", ei->name, rv->p);
1737
1738 TVEC_MISCSLOTS(DEFDUMP_ENUM)
1739
1740 #undef PRINTRAW_INT
1741 #undef PRINTRAW_UINT
1742 #undef PRINTRAW_FLT
1743 #undef PRINTRAW_PTR
1744
1745 #undef MAYBE_PRINT_EXTRA
1746 #undef DEFDUMP_ENUM
1747
1748 #define DEFTY_ENUM(tag, ty, slot)                                       \
1749   const struct tvec_regty tvty_##slot##enum = {                         \
1750     init_##slot##enum, trivial_release, eq_##slot##enum,                \
1751     tobuf_##slot##enum, frombuf_##slot##enum,                           \
1752     parse_##slot##enum, dump_##slot##enum                               \
1753   };
1754 TVEC_MISCSLOTS(DEFTY_ENUM)
1755 #undef DEFTY_ENUM
1756
1757 static const struct tvec_iassoc bool_assoc[] = {
1758   { "nil",              0 },
1759   { "false",            0 },
1760   { "f",                0 },
1761   { "no",               0 },
1762   { "n",                0 },
1763   { "off",              0 },
1764
1765   { "t",                1 },
1766   { "true",             1 },
1767   { "yes",              1 },
1768   { "y",                1 },
1769   { "on",               1 },
1770
1771   TVEC_ENDENUM
1772 };
1773
1774 const struct tvec_ienuminfo tvenum_bool =
1775   { "bool", bool_assoc, &tvrange_int };
1776
1777 static const struct tvec_iassoc cmp_assoc[] = {
1778   { "<",                -1 },
1779   { "less",             -1 },
1780   { "lt",               -1 },
1781
1782   { "=",                 0 },
1783   { "equal",             0 },
1784   { "eq",                0 },
1785
1786   { ">",                +1 },
1787   { "greater",          +1 },
1788   { "gt",               +1 },
1789
1790   TVEC_ENDENUM
1791 };
1792
1793 const struct tvec_ienuminfo tvenum_cmp =
1794   { "cmp", cmp_assoc, &tvrange_int };
1795
1796 /* --- @tvec_claimeq_tenum@ --- *
1797  *
1798  * Arguments:   @struct tvec_state *tv@ = test-vector state
1799  *              @const struct tvec_typeenuminfo *ei@ = enumeration type info
1800  *              @ty t0, t1@ = two values
1801  *              @const char *file@, @unsigned @lno@ = calling file and line
1802  *              @const char *expr@ = the expression to quote on failure
1803  *
1804  * Returns:     Nonzero if @t0@ and @t1@ are equal, otherwise zero.
1805  *
1806  * Use:         Check that values of @t0@ and @t1@ are equal.  As for
1807  *              @tvec_claim@ above, a test case is automatically begun and
1808  *              ended if none is already underway.  If the values are
1809  *              unequal, then @tvec_fail@ is called, quoting @expr@, and the
1810  *              mismatched values are dumped: @t0@ is printed as the output
1811  *              value and @t1@ is printed as the input reference.
1812  */
1813
1814 #define DEFCLAIM(tag, ty, slot)                                         \
1815         int tvec_claimeq_##slot##enum                                   \
1816           (struct tvec_state *tv,                                       \
1817            const struct tvec_##slot##enuminfo *ei, ty e0, ty e1,        \
1818            const char *file, unsigned lno, const char *expr)            \
1819         {                                                               \
1820           union tvec_misc arg;                                          \
1821                                                                         \
1822           arg.p = ei;                                                   \
1823           tv->out[0].v.slot = GET_##tag(e0);                            \
1824           tv->in[0].v.slot = GET_##tag(e1);                             \
1825           return (tvec_claimeq(tv, &tvty_##slot##enum, &arg,            \
1826                                file, lno, expr));                       \
1827         }
1828 #define GET_INT(e) (e)
1829 #define GET_UINT(e) (e)
1830 #define GET_FLT(e) (e)
1831 #define GET_PTR(e) ((/*unconst*/ void *)(e))
1832 TVEC_MISCSLOTS(DEFCLAIM)
1833 #undef DEFCLAIM
1834 #undef GET_INT
1835 #undef GET_UINT
1836 #undef GET_FLT
1837 #undef GET_PTR
1838
1839 /*----- Flag types --------------------------------------------------------*/
1840
1841 static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
1842                        struct tvec_state *tv)
1843 {
1844   const struct tvec_flaginfo *fi = rd->arg.p;
1845   const struct tvec_flag *f;
1846   unsigned long m = 0, v = 0, t;
1847   dstr d = DSTR_INIT;
1848   int ch, rc;
1849
1850   for (;;) {
1851     DRESET(&d);
1852     if (tvec_readword(tv, &d, "|;", "flag name or integer"))
1853       { rc = -1; goto end; }
1854
1855     for (f = fi->fv; f->tag; f++)
1856       if (STRCMP(f->tag, ==, d.buf)) {
1857         if (m&f->m)
1858           { tvec_error(tv, "colliding flag setting"); rc = -1; goto end; }
1859         else
1860           { m |= f->m; v |= f->v; goto next; }
1861       }
1862
1863     if (parse_unsigned(&t, d.buf, fi->range, tv))
1864       { rc = -1; goto end; }
1865     v |= t;
1866   next:
1867     if (tvec_nexttoken(tv)) break;
1868     ch = getc(tv->fp);
1869       if (ch != '|') { tvec_syntax(tv, ch, "`|'"); rc = -1; goto end; }
1870     if (tvec_nexttoken(tv))
1871       { tvec_syntax(tv, '\n', "flag name or integer"); rc = -1; goto end; }
1872   }
1873   rv->u = v;
1874   rc = 0;
1875 end:
1876   dstr_destroy(&d);
1877   return (rc);
1878 }
1879
1880 static void dump_flags(const union tvec_regval *rv,
1881                        const struct tvec_regdef *rd,
1882                        unsigned style,
1883                        const struct gprintf_ops *gops, void *go)
1884 {
1885   const struct tvec_flaginfo *fi = rd->arg.p;
1886   const struct tvec_flag *f;
1887   unsigned long m = ~(unsigned long)0, v = rv->u;
1888   const char *sep;
1889
1890   for (f = fi->fv, sep = ""; f->tag; f++)
1891     if ((m&f->m) && (v&f->m) == f->v) {
1892       gprintf(gops, go, "%s%s", sep, f->tag); m &= ~f->m;
1893       sep = style&TVSF_COMPACT ? "|" : " | ";
1894     }
1895
1896   if (v&m) gprintf(gops, go, "%s0x%0*lx", sep, hex_width(v), v&m);
1897
1898   if (!(style&TVSF_COMPACT))
1899     gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
1900 }
1901
1902 const struct tvec_regty tvty_flags = {
1903   init_uint, trivial_release, eq_uint,
1904   tobuf_uint, frombuf_uint,
1905   parse_flags, dump_flags
1906 };
1907
1908 /* --- @tvec_claimeq_flags@ --- *
1909  *
1910  * Arguments:   @struct tvec_state *tv@ = test-vector state
1911  *              @const struct tvec_flaginfo *fi@ = flags type info
1912  *              @unsigned long f0, f1@ = two values
1913  *              @const char *file@, @unsigned @lno@ = calling file and line
1914  *              @const char *expr@ = the expression to quote on failure
1915  *
1916  * Returns:     Nonzero if @f0@ and @f1@ are equal, otherwise zero.
1917  *
1918  * Use:         Check that values of @f0@ and @f1@ are equal.  As for
1919  *              @tvec_claim@ above, a test case is automatically begun and
1920  *              ended if none is already underway.  If the values are
1921  *              unequal, then @tvec_fail@ is called, quoting @expr@, and the
1922  *              mismatched values are dumped: @f0@ is printed as the output
1923  *              value and @f1@ is printed as the input reference.
1924  */
1925
1926 int tvec_claimeq_flags(struct tvec_state *tv,
1927                        const struct tvec_flaginfo *fi,
1928                        unsigned long f0, unsigned long f1,
1929                        const char *file, unsigned lno, const char *expr)
1930 {
1931   union tvec_misc arg;
1932
1933   arg.p = fi; tv->out[0].v.u = f0; tv->in[0].v.u = f1;
1934   return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr));
1935 }
1936
1937 /*----- Characters --------------------------------------------------------*/
1938
1939 static int tobuf_char(buf *b, const union tvec_regval *rv,
1940                       const struct tvec_regdef *rd)
1941 {
1942   uint32 u;
1943   if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i;
1944   else if (rv->i == EOF) u = MASK32;
1945   else return (-1);
1946   return (buf_putu32l(b, u));
1947 }
1948
1949 static int frombuf_char(buf *b, union tvec_regval *rv,
1950                         const struct tvec_regdef *rd)
1951 {
1952   uint32 u;
1953
1954   if (buf_getu32l(b, &u)) return (-1);
1955   if (0 <= u && u <= UCHAR_MAX) rv->i = u;
1956   else if (u == MASK32) rv->i = EOF;
1957   else return (-1);
1958   return (0);
1959 }
1960
1961 static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd,
1962                       struct tvec_state *tv)
1963 {
1964   dstr d = DSTR_INIT;
1965   int ch, rc;
1966   unsigned f = 0;
1967 #define f_quote 1u
1968
1969   ch = getc(tv->fp);
1970   if (ch == '#') {
1971     ungetc(ch, tv->fp);
1972     if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; }
1973     if (read_charname(&ch, d.buf, RCF_EOFOK)) {
1974       rc = tvec_error(tv, "unknown character name `%s'", d.buf);
1975       goto end;
1976     }
1977     if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
1978     rv->i = ch; rc = 0; goto end;
1979   }
1980
1981   if (ch == '\'') { f |= f_quote; ch = getc(tv->fp); }
1982   switch (ch) {
1983     case ';':
1984       if (!(f&f_quote)) { rc = tvec_syntax(tv, ch, "character"); goto end; }
1985       goto plain;
1986     case '\n':
1987       if (f&f_quote)
1988         { f &= ~f_quote; ungetc(ch, tv->fp); ch = '\''; goto plain; }
1989     case EOF:
1990       if (f&f_quote) { f &= ~f_quote; ch = '\''; goto plain; }
1991       /* fall through */
1992     case '\'':
1993       rc = tvec_syntax(tv, ch, "character"); goto end;
1994     case '\\':
1995       if (read_charesc(&ch, tv)) return (-1);
1996     default: plain:
1997       rv->i = ch; break;
1998   }
1999   if (f&f_quote) {
2000     ch = getc(tv->fp);
2001     if (ch != '\'') { rc = tvec_syntax(tv, ch, "`''"); goto end; }
2002   }
2003   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
2004   rc = 0;
2005 end:
2006   dstr_destroy(&d);
2007   return (rc);
2008
2009 #undef f_quote
2010 }
2011
2012 static void dump_char(const union tvec_regval *rv,
2013                       const struct tvec_regdef *rd,
2014                       unsigned style,
2015                       const struct gprintf_ops *gops, void *go)
2016 {
2017   const char *p;
2018   unsigned f = 0;
2019 #define f_semi 1u
2020
2021   p = find_charname(rv->i, (style&TVSF_COMPACT) ? CTF_SHORT : CTF_PREFER);
2022   if (p) {
2023     gprintf(gops, go, "%s", p);
2024     if (style&TVSF_COMPACT) return;
2025     else { gprintf(gops, go, " ;"); f |= f_semi; }
2026   }
2027
2028   if (rv->i >= 0) {
2029     if (f&f_semi) gprintf(gops, go, " = ");
2030     switch (rv->i) {
2031       case ' ': case '\\': case '\'': quote:
2032         format_char(gops, go, rv->i);
2033         break;
2034       default:
2035         if (!(style&TVSF_COMPACT) || !isprint(rv->i)) goto quote;
2036         gprintf(gops, go, "%c", (int)rv->i);
2037         return;
2038     }
2039   }
2040
2041   if (!(style&TVSF_COMPACT)) {
2042     if (!(f&f_semi)) gprintf(gops, go, " ;");
2043     gprintf(gops, go, " = %ld = ", rv->i);
2044     format_signed_hex(gops, go, rv->i);
2045   }
2046
2047 #undef f_semi
2048 }
2049
2050 const struct tvec_regty tvty_char = {
2051   init_int, trivial_release, eq_int,
2052   tobuf_char, frombuf_char,
2053   parse_char, dump_char
2054 };
2055
2056 /* --- @tvec_claimeq_char@ --- *
2057  *
2058  * Arguments:   @struct tvec_state *tv@ = test-vector state
2059  *              @int ch0, ch1@ = two character codes
2060  *              @const char *file@, @unsigned @lno@ = calling file and line
2061  *              @const char *expr@ = the expression to quote on failure
2062  *
2063  * Returns:     Nonzero if @ch0@ and @ch1@ are equal, otherwise zero.
2064  *
2065  * Use:         Check that values of @ch0@ and @ch1@ are equal.  As for
2066  *              @tvec_claim@ above, a test case is automatically begun and
2067  *              ended if none is already underway.  If the values are
2068  *              unequal, then @tvec_fail@ is called, quoting @expr@, and the
2069  *              mismatched values are dumped: @ch0@ is printed as the output
2070  *              value and @ch1@ is printed as the input reference.
2071  */
2072
2073 int tvec_claimeq_char(struct tvec_state *tv, int c0, int c1,
2074                       const char *file, unsigned lno, const char *expr)
2075 {
2076   tv->out[0].v.i = c0; tv->in[0].v.i = c1;
2077   return (tvec_claimeq(tv, &tvty_char, 0, file, lno, expr));
2078 }
2079
2080 /*----- Text and byte strings ---------------------------------------------*/
2081
2082 static void init_string(union tvec_regval *rv, const struct tvec_regdef *rd)
2083   { rv->str.p = 0; rv->str.sz = 0; }
2084
2085 static void init_bytes(union tvec_regval *rv, const struct tvec_regdef *rd)
2086   { rv->bytes.p = 0; rv->bytes.sz = 0; }
2087
2088 static void release_string(union tvec_regval *rv,
2089                           const struct tvec_regdef *rd)
2090   { xfree(rv->str.p); }
2091
2092 static void release_bytes(union tvec_regval *rv,
2093                           const struct tvec_regdef *rd)
2094   { xfree(rv->bytes.p); }
2095
2096 static int eq_string(const union tvec_regval *rv0,
2097                      const union tvec_regval *rv1,
2098                      const struct tvec_regdef *rd)
2099 {
2100   return (rv0->str.sz == rv1->str.sz &&
2101           (!rv0->bytes.sz ||
2102            MEMCMP(rv0->str.p, ==, rv1->str.p, rv1->str.sz)));
2103 }
2104
2105 static int eq_bytes(const union tvec_regval *rv0,
2106                     const union tvec_regval *rv1,
2107                     const struct tvec_regdef *rd)
2108 {
2109   return (rv0->bytes.sz == rv1->bytes.sz &&
2110           (!rv0->bytes.sz ||
2111            MEMCMP(rv0->bytes.p, ==, rv1->bytes.p, rv1->bytes.sz)));
2112 }
2113
2114 static int tobuf_string(buf *b, const union tvec_regval *rv,
2115                         const struct tvec_regdef *rd)
2116   { return (buf_putmem32l(b, rv->str.p, rv->str.sz)); }
2117
2118 static int tobuf_bytes(buf *b, const union tvec_regval *rv,
2119                        const struct tvec_regdef *rd)
2120   { return (buf_putmem32l(b, rv->bytes.p, rv->bytes.sz)); }
2121
2122 static int frombuf_string(buf *b, union tvec_regval *rv,
2123                           const struct tvec_regdef *rd)
2124 {
2125   const void *p;
2126   size_t sz;
2127
2128   p = buf_getmem32l(b, &sz); if (!p) return (-1);
2129   tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz); rv->str.p[sz] = 0;
2130   return (0);
2131 }
2132
2133 static int frombuf_bytes(buf *b, union tvec_regval *rv,
2134                          const struct tvec_regdef *rd)
2135 {
2136   const void *p;
2137   size_t sz;
2138
2139   p = buf_getmem32l(b, &sz); if (!p) return (-1);
2140   tvec_allocbytes(rv, sz); memcpy(rv->bytes.p, p, sz);
2141   return (0);
2142 }
2143
2144 static int check_string_length(size_t sz, const struct tvec_urange *ur,
2145                                struct tvec_state *tv)
2146 {
2147   if (ur && (ur->min > sz || sz > ur->max))
2148     return (tvec_error(tv,
2149                        "invalid string length %lu; must be in [%lu .. %lu]",
2150                        (unsigned long)sz, ur->min, ur->max));
2151   return (0);
2152 }
2153
2154 static int parse_string(union tvec_regval *rv, const struct tvec_regdef *rd,
2155                         struct tvec_state *tv)
2156 {
2157   void *p = rv->str.p;
2158
2159   if (read_compound_string(&p, &rv->str.sz, TVCODE_BARE, 0, tv))
2160     return (-1);
2161   rv->str.p = p;
2162   if (check_string_length(rv->str.sz, rd->arg.p, tv)) return (-1);
2163   return (0);
2164 }
2165
2166 static int parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd,
2167                        struct tvec_state *tv)
2168 {
2169   void *p = rv->bytes.p;
2170
2171   if (read_compound_string(&p, &rv->bytes.sz, TVCODE_HEX, 0, tv))
2172     return (-1);
2173   rv->bytes.p = p;
2174   if (check_string_length(rv->bytes.sz, rd->arg.p, tv)) return (-1);
2175   return (0);
2176 }
2177
2178 static void dump_string(const union tvec_regval *rv,
2179                         const struct tvec_regdef *rd,
2180                         unsigned style,
2181                         const struct gprintf_ops *gops, void *go)
2182 {
2183   const unsigned char *p, *q, *l;
2184   unsigned f = 0;
2185 #define f_nonword 1u
2186 #define f_newline 2u
2187
2188   if (!rv->str.sz) { gprintf(gops, go, "\"\""); return; }
2189
2190   p = (const unsigned char *)rv->str.p; l = p + rv->str.sz;
2191   switch (*p) {
2192     case '!': case '#': case ';': case '"': case '\'':
2193     case '(': case '{': case '[': case ']': case '}': case ')':
2194       f |= f_nonword; break;
2195   }
2196   for (q = p; q < l; q++)
2197     if (*q == '\n' && q != l - 1) f |= f_newline;
2198     else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword;
2199   if (f&f_newline) { gprintf(gops, go, "\n\t"); goto quote; }
2200   else if (f&f_nonword) goto quote;
2201
2202   gops->putm(go, (const char *)p, rv->str.sz);
2203   return;
2204
2205 quote:
2206   gprintf(gops, go, "\"");
2207   for (q = p; q < l; q++)
2208     if (!isprint(*q) || *q == '"') {
2209       if (p < q) gops->putm(go, (const char *)p, q - p);
2210       if (*q != '\n' || (style&TVSF_COMPACT))
2211         format_charesc(gops, go, *q, FCF_BRACE);
2212       else {
2213         if (q + 1 == l) { gprintf(gops, go, "\\n\""); return; }
2214         else gprintf(gops, go, "\\n\"\n\t\"");
2215       }
2216       p = q + 1;
2217     }
2218   if (p < q) gops->putm(go, (const char *)p, q - p);
2219   gprintf(gops, go, "\"");
2220
2221 #undef f_nonword
2222 #undef f_newline
2223 }
2224
2225 static void dump_bytes(const union tvec_regval *rv,
2226                        const struct tvec_regdef *rd,
2227                        unsigned style,
2228                        const struct gprintf_ops *gops, void *go)
2229 {
2230   const unsigned char *p = rv->bytes.p, *l = p + rv->bytes.sz;
2231   size_t off, sz = rv->bytes.sz;
2232   unsigned i, n;
2233   int wd;
2234
2235   if (!sz) {
2236     gprintf(gops, go, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
2237     return;
2238   }
2239
2240   if (style&TVSF_COMPACT) {
2241     while (p < l) gprintf(gops, go, "%02x", *p++);
2242     return;
2243   }
2244
2245   if (sz > 16) gprintf(gops, go, "\n\t");
2246
2247   off = 0; wd = hex_width(sz);
2248   while (p < l) {
2249     if (l - p < 16) n = l - p;
2250     else n = 16;
2251
2252     for (i = 0; i < n; i++) {
2253       if (i < n) gprintf(gops, go, "%02x", p[i]);
2254       else gprintf(gops, go, "  ");
2255       if (i < n - 1 && i%4 == 3) gprintf(gops, go, " ");
2256     }
2257     gprintf(gops, go, " ; ");
2258     if (sz > 16) gprintf(gops, go, "[%0*lx] ", wd, (unsigned long)off);
2259     for (i = 0; i < n; i++)
2260       gprintf(gops, go, "%c", isprint(p[i]) ? p[i] : '.');
2261     p += n; off += n;
2262     if (p < l) gprintf(gops, go, "\n\t");
2263   }
2264 }
2265
2266 const struct tvec_regty tvty_string = {
2267   init_string, release_string, eq_string,
2268   tobuf_string, frombuf_string,
2269   parse_string, dump_string
2270 };
2271
2272 const struct tvec_regty tvty_bytes = {
2273   init_bytes, release_bytes, eq_bytes,
2274   tobuf_bytes, frombuf_bytes,
2275   parse_bytes, dump_bytes
2276 };
2277
2278 /* --- @tvec_claimeq_string@ --- *
2279  *
2280  * Arguments:   @struct tvec_state *tv@ = test-vector state
2281  *              @const char *p0@, @size_t sz0@ = first string with length
2282  *              @const char *p1@, @size_t sz1@ = second string with length
2283  *              @const char *file@, @unsigned @lno@ = calling file and line
2284  *              @const char *expr@ = the expression to quote on failure
2285  *
2286  * Returns:     Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
2287  *              zero.
2288  *
2289  * Use:         Check that strings at @p0@ and @p1@ are equal.  As for
2290  *              @tvec_claim@ above, a test case is automatically begun and
2291  *              ended if none is already underway.  If the values are
2292  *              unequal, then @tvec_fail@ is called, quoting @expr@, and the
2293  *              mismatched values are dumped: @p0@ is printed as the output
2294  *              value and @p1@ is printed as the input reference.
2295  */
2296
2297 int tvec_claimeq_string(struct tvec_state *tv,
2298                         const char *p0, size_t sz0,
2299                         const char *p1, size_t sz1,
2300                         const char *file, unsigned lno, const char *expr)
2301 {
2302   tv->out[0].v.str.p = (/*unconst*/ char *)p0; tv->out[0].v.str.sz = sz0;
2303   tv->in[0].v.str.p =(/*unconst*/ char *) p1; tv->in[0].v.str.sz = sz1;
2304   return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
2305 }
2306
2307 /* --- @tvec_claimeq_strz@ --- *
2308  *
2309  * Arguments:   @struct tvec_state *tv@ = test-vector state
2310  *              @const char *p0, *p1@ = two strings to compare
2311  *              @const char *file@, @unsigned @lno@ = calling file and line
2312  *              @const char *expr@ = the expression to quote on failure
2313  *
2314  * Returns:     Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
2315  *              zero.
2316  *
2317  * Use:         Check that strings at @p0@ and @p1@ are equal, as for
2318  *              @tvec_claimeq_string@, except that the strings are assumed
2319  *              null-terminated, so their lengths don't need to be supplied
2320  *              explicitly.
2321  */
2322
2323 int tvec_claimeq_strz(struct tvec_state *tv,
2324                       const char *p0, const char *p1,
2325                       const char *file, unsigned lno, const char *expr)
2326 {
2327   tv->out[0].v.str.p = (/*unconst*/ char *)p0;
2328     tv->out[0].v.str.sz = strlen(p0);
2329   tv->in[0].v.str.p = (/*unconst*/ char *)p1;
2330     tv->in[0].v.str.sz = strlen(p1);
2331   return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
2332 }
2333
2334 /* --- @tvec_claimeq_bytes@ --- *
2335  *
2336  * Arguments:   @struct tvec_state *tv@ = test-vector state
2337  *              @const void *p0@, @size_t sz0@ = first string with length
2338  *              @const void *p1@, @size_t sz1@ = second string with length
2339  *              @const char *file@, @unsigned @lno@ = calling file and line
2340  *              @const char *expr@ = the expression to quote on failure
2341  *
2342  * Returns:     Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
2343  *              zero.
2344  *
2345  * Use:         Check that binary strings at @p0@ and @p1@ are equal.  As for
2346  *              @tvec_claim@ above, a test case is automatically begun and
2347  *              ended if none is already underway.  If the values are
2348  *              unequal, then @tvec_fail@ is called, quoting @expr@, and the
2349  *              mismatched values are dumped: @p0@ is printed as the output
2350  *              value and @p1@ is printed as the input reference.
2351  */
2352
2353 int tvec_claimeq_bytes(struct tvec_state *tv,
2354                        const void *p0, size_t sz0,
2355                        const void *p1, size_t sz1,
2356                        const char *file, unsigned lno, const char *expr)
2357 {
2358   tv->out[0].v.bytes.p = (/*unconst*/ void *)p0;
2359     tv->out[0].v.bytes.sz = sz0;
2360   tv->in[0].v.bytes.p = (/*unconst*/ void *)p1;
2361     tv->in[0].v.bytes.sz = sz1;
2362   return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr));
2363 }
2364
2365 /* --- @tvec_allocstring@, @tvec_allocbytes@ --- *
2366  *
2367  * Arguments:   @union tvec_regval *rv@ = register value
2368  *              @size_t sz@ = required size
2369  *
2370  * Returns:     ---
2371  *
2372  * Use:         Allocated space in a text or binary string register.  If the
2373  *              current register size is sufficient, its buffer is left
2374  *              alone; otherwise, the old buffer, if any, is freed and a
2375  *              fresh buffer allocated.  These functions are not intended to
2376  *              be used to adjust a buffer repeatedly, e.g., while building
2377  *              output incrementally: (a) they will perform badly, and (b)
2378  *              the old buffer contents are simply discarded if reallocation
2379  *              is necessary.  Instead, use a @dbuf@ or @dstr@.
2380  *
2381  *              The @tvec_allocstring@ function sneakily allocates an extra
2382  *              byte for a terminating zero.  The @tvec_allocbytes@ function
2383  *              doesn't do this.
2384  */
2385
2386 void tvec_allocstring(union tvec_regval *rv, size_t sz)
2387 {
2388   if (rv->str.sz <= sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz + 1); }
2389   rv->str.sz = sz;
2390 }
2391
2392 void tvec_allocbytes(union tvec_regval *rv, size_t sz)
2393 {
2394   if (rv->bytes.sz < sz) { xfree(rv->bytes.p); rv->bytes.p = xmalloc(sz); }
2395   rv->bytes.sz = sz;
2396 }
2397
2398 /*----- Buffer type -------------------------------------------------------*/
2399
2400 static int eq_buffer(const union tvec_regval *rv0,
2401                      const union tvec_regval *rv1,
2402                      const struct tvec_regdef *rd)
2403   { return (rv0->bytes.sz == rv1->bytes.sz); }
2404
2405 static int tobuf_buffer(buf *b, const union tvec_regval *rv,
2406                          const struct tvec_regdef *rd)
2407   { return (unsigned_to_buf(b, rv->bytes.sz)); }
2408
2409 static int frombuf_buffer(buf *b, union tvec_regval *rv,
2410                           const struct tvec_regdef *rd)
2411 {
2412   unsigned long u;
2413
2414   if (unsigned_from_buf(b, &u)) return (-1);
2415   if (u > (size_t)-1) return (-1);
2416   tvec_allocbytes(rv, u); memset(rv->bytes.p, '!', u);
2417   return (0);
2418 }
2419
2420 static const char units[] = "kMGTPEZY";
2421
2422 static int parse_buffer(union tvec_regval *rv,
2423                         const struct tvec_regdef *rd,
2424                         struct tvec_state *tv)
2425 {
2426   dstr d = DSTR_INIT;
2427   const char *q, *unit;
2428   size_t pos;
2429   unsigned long u, t;
2430   int rc;
2431   unsigned f = 0;
2432 #define f_range 1u
2433
2434   if (tvec_readword(tv, &d, ";", "buffer length")) { rc = -1; goto end; }
2435   if (parse_unsigned_integer(&u, &q, d.buf)) goto bad;
2436   if (!*q) {
2437     tvec_skipspc(tv); pos = d.len;
2438     if (!tvec_readword(tv, &d, ";", 0)) pos++;
2439     q = d.buf + pos;
2440   }
2441
2442   if (u > (size_t)-1) goto rangerr;
2443   for (t = u, unit = units; *unit; unit++) {
2444     if (t > (size_t)-1/1024) f |= f_range;
2445     else t *= 1024;
2446     if (*q == *unit) {
2447       if (f&f_range) goto rangerr;
2448       u = t; q++; break;
2449     }
2450   }
2451   if (*q == 'B') q++;
2452   if (*q) goto bad;
2453   if (check_string_length(u, rd->arg.p, tv)) { rc = -1; goto end; }
2454
2455   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
2456   tvec_allocbytes(rv, u); memset(rv->bytes.p, '?', u);
2457   rc = 0;
2458 end:
2459   DDESTROY(&d); return (rc);
2460
2461 bad:
2462   tvec_error(tv, "invalid buffer length `%s'", d.buf);
2463   rc = -1; goto end;
2464
2465 rangerr:
2466   tvec_error(tv, "buffer length `%s' out of range", d.buf);
2467   rc = -1; goto end;
2468
2469 #undef f_range
2470 }
2471
2472 static void dump_buffer(const union tvec_regval *rv,
2473                         const struct tvec_regdef *rd,
2474                         unsigned style,
2475                         const struct gprintf_ops *gops, void *go)
2476 {
2477   const char *unit;
2478   unsigned long u = rv->bytes.sz;
2479
2480   if (!u || u%1024)
2481     gprintf(gops, go, "%lu B", u);
2482   else {
2483     for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++);
2484     gprintf(gops, go, "%lu %cB", u, *unit);
2485   }
2486 }
2487
2488 const struct tvec_regty tvty_buffer = {
2489   init_bytes, release_bytes, eq_buffer,
2490   tobuf_buffer, frombuf_buffer,
2491   parse_buffer, dump_buffer
2492 };
2493
2494 /*----- That's all, folks -------------------------------------------------*/